From 05eb2e45438214a7e5cf908b95994cc230d1be39 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:52:05 -0500 Subject: [PATCH 01/17] Block Capturing System --- .../features/0032-Block-Capture-System.patch | 2142 +++++++++++++++++ .../util/capture/BlockPlacementPredictor.java | 35 + .../paper/util/capture/CaptureRecordMap.java | 174 ++ .../LayeredBlockPlacementPredictor.java | 50 + .../util/capture/LiveBlockPlacementLayer.java | 35 + .../util/capture/MinecraftCaptureBridge.java | 477 ++++ .../capture/PaperCapturingWorldLevel.java | 32 + .../ServerLevelPaperCapturingWorldLevel.java | 56 + .../util/capture/SimpleBlockCapture.java | 69 + .../SimpleBlockPlacementPredictor.java | 121 + .../paper/util/capture/WorldCapturer.java | 36 + .../org/bukkit/craftbukkit/CraftWorld.java | 41 +- .../bukkit/craftbukkit/block/CraftBlock.java | 65 +- .../craftbukkit/event/CraftEventFactory.java | 51 +- .../io/papermc/testplugin/TestPlugin.java | 54 + 15 files changed, 3385 insertions(+), 53 deletions(-) create mode 100644 paper-server/patches/features/0032-Block-Capture-System.patch create mode 100644 paper-server/src/main/java/io/papermc/paper/util/capture/BlockPlacementPredictor.java create mode 100644 paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java create mode 100644 paper-server/src/main/java/io/papermc/paper/util/capture/LayeredBlockPlacementPredictor.java create mode 100644 paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java create mode 100644 paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java create mode 100644 paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java create mode 100644 paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java create mode 100644 paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java create mode 100644 paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java create mode 100644 paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java diff --git a/paper-server/patches/features/0032-Block-Capture-System.patch b/paper-server/patches/features/0032-Block-Capture-System.patch new file mode 100644 index 000000000000..4a719af678e5 --- /dev/null +++ b/paper-server/patches/features/0032-Block-Capture-System.patch @@ -0,0 +1,2142 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Thu, 5 Feb 2026 16:31:13 -0500 +Subject: [PATCH] Block Capture System + +ATM Fixes: +this one: https://github.com/PaperMC/Paper/issues/11728 +https://github.com/PaperMC/Paper/issues/6458 (better fix) +bee hive issue +https://github.com/PaperMC/Paper/issues/12114 (tripwire hook duping) +https://github.com/PaperMC/Paper/issues/13586 (working towards) +end portal counting towards block place event +any block modification in item interaction counting as block place event +fixes edge cases required by random block types +https://github.com/PaperMC/Paper/issues/13536 root dupe +fixes edge cases with physics being fired on structure placement despite being canceled + +big todos: +- Dispenser related stuff +- Bone meal related stuff, we need to pass more context in order to get the tree event properly working + + + +diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java +index bfefb5031544caa59230f0073e8880c2b39ebf4d..f1f35c09854bda9e7895e09f440e39c3e47d337f 100644 +--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java ++++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java +@@ -394,56 +394,56 @@ public interface DispenseItemBehavior { + return item; + } + }); +- DispenserBlock.registerBehavior(Items.BONE_MEAL, new OptionalDispenseItemBehavior() { +- @Override +- protected ItemStack execute(BlockSource blockSource, ItemStack item) { +- this.setSuccess(true); +- Level level = blockSource.level(); +- BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING)); +- // Paper start - Call BlockDispenseEvent +- ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(blockSource, blockPos, item, this); +- if (result != null) { +- this.setSuccess(false); +- return result; +- } +- // Paper end - Call BlockDispenseEvent +- level.captureTreeGeneration = true; // CraftBukkit +- if (!BoneMealItem.growCrop(item, level, blockPos) && !BoneMealItem.growWaterPlant(item, level, blockPos, null)) { +- this.setSuccess(false); +- } else if (!level.isClientSide()) { +- level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, blockPos, 15); +- } +- // CraftBukkit start +- level.captureTreeGeneration = false; +- if (!level.capturedBlockStates.isEmpty()) { +- org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; +- net.minecraft.world.level.block.SaplingBlock.treeType = null; +- org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level); +- List states = new java.util.ArrayList<>(level.capturedBlockStates.values()); +- level.capturedBlockStates.clear(); +- org.bukkit.event.world.StructureGrowEvent structureEvent = null; +- if (treeType != null) { +- structureEvent = new org.bukkit.event.world.StructureGrowEvent(location, treeType, false, null, states); +- org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent); +- } +- +- org.bukkit.event.block.BlockFertilizeEvent fertilizeEvent = new org.bukkit.event.block.BlockFertilizeEvent(location.getBlock(), null, states); +- fertilizeEvent.setCancelled(structureEvent != null && structureEvent.isCancelled()); +- org.bukkit.Bukkit.getPluginManager().callEvent(fertilizeEvent); +- +- if (!fertilizeEvent.isCancelled()) { +- for (org.bukkit.block.BlockState state : states) { +- org.bukkit.craftbukkit.block.CraftBlockState craftBlockState = (org.bukkit.craftbukkit.block.CraftBlockState) state; +- craftBlockState.place(craftBlockState.getFlags()); +- blockSource.level().checkCapturedTreeStateForObserverNotify(blockPos, craftBlockState); // Paper - notify observers even if grow failed +- } +- } +- } +- // CraftBukkit end +- +- return item; +- } +- }); ++// DispenserBlock.registerBehavior(Items.BONE_MEAL, new OptionalDispenseItemBehavior() { ++// @Override ++// protected ItemStack execute(BlockSource blockSource, ItemStack item) { ++// this.setSuccess(true); ++// Level level = blockSource.level(); ++// BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING)); ++// // Paper start - Call BlockDispenseEvent ++// ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(blockSource, blockPos, item, this); ++// if (result != null) { ++// this.setSuccess(false); ++// return result; ++// } ++// // Paper end - Call BlockDispenseEvent ++// level.captureTreeGeneration = true; // CraftBukkit ++// if (!BoneMealItem.growCrop(item, level, blockPos) && !BoneMealItem.growWaterPlant(item, level, blockPos, null)) { ++// this.setSuccess(false); ++// } else if (!level.isClientSide()) { ++// level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, blockPos, 15); ++// } ++// // CraftBukkit start ++// level.captureTreeGeneration = false; ++// if (!level.capturedBlockStates.isEmpty()) { ++// org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; ++// net.minecraft.world.level.block.SaplingBlock.treeType = null; ++// org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level); ++// List states = new java.util.ArrayList<>(level.capturedBlockStates.values()); ++// level.capturedBlockStates.clear(); ++// org.bukkit.event.world.StructureGrowEvent structureEvent = null; ++// if (treeType != null) { ++// structureEvent = new org.bukkit.event.world.StructureGrowEvent(location, treeType, false, null, states); ++// org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent); ++// } ++// ++// org.bukkit.event.block.BlockFertilizeEvent fertilizeEvent = new org.bukkit.event.block.BlockFertilizeEvent(location.getBlock(), null, states); ++// fertilizeEvent.setCancelled(structureEvent != null && structureEvent.isCancelled()); ++// org.bukkit.Bukkit.getPluginManager().callEvent(fertilizeEvent); ++// ++// if (!fertilizeEvent.isCancelled()) { ++// for (org.bukkit.block.BlockState state : states) { ++// org.bukkit.craftbukkit.block.CraftBlockState craftBlockState = (org.bukkit.craftbukkit.block.CraftBlockState) state; ++// craftBlockState.place(craftBlockState.getFlags()); ++// blockSource.level().checkCapturedTreeStateForObserverNotify(blockPos, craftBlockState); // Paper - notify observers even if grow failed ++// } ++// } ++// } ++// // CraftBukkit end ++// ++// return item; ++// } ++// }); + DispenserBlock.registerBehavior(Blocks.TNT, new OptionalDispenseItemBehavior() { + @Override + protected ItemStack execute(BlockSource blockSource, ItemStack item) { +diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java +index dc65503a2d785d64d37b76b0303f51cf66d9769a..9ea4dc4d45f4060955e24b0c80143b852539027d 100644 +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -181,7 +181,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter; + import org.jspecify.annotations.Nullable; + import org.slf4j.Logger; + +-public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevelReader, ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel { // Paper - rewrite chunk system // Paper - chunk tick iteration ++public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevelReader, ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel, io.papermc.paper.util.capture.ServerLevelPaperCapturingWorldLevel { // Paper - rewrite chunk system // Paper - chunk tick iteration // Paper - + public static final BlockPos END_SPAWN_POINT = new BlockPos(100, 50, 0); + public static final IntProvider RAIN_DELAY = UniformInt.of(12000, 180000); + public static final IntProvider RAIN_DURATION = UniformInt.of(12000, 24000); +@@ -1857,13 +1857,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + return; + } + // CraftBukkit end +- if (captureBlockStates) { return; } // Paper - Cancel all physics during placement + this.updateNeighborsAt(pos, block, ExperimentalRedstoneUtils.initialOrientation(this, null, null)); + } + + @Override + public void updateNeighborsAt(BlockPos pos, Block block, @Nullable Orientation orientation) { +- if (captureBlockStates) { return; } // Paper - Cancel all physics during placement + this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, null, orientation); + } + +@@ -2669,6 +2667,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + return this.randomSequences; + } + ++ @Override ++ public ServerLevel handle() { ++ return this; ++ } ++ + public GameRules getGameRules() { + return this.serverLevelData.getGameRules(); + } +@@ -2683,17 +2686,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe + return range <= 0 ? 64.0 * 64.0 : range * range; // 64 is taken from default in ServerLevel#levelEvent + } + // Paper end - respect global sound events gamerule +- // Paper start - notify observers even if grow failed +- @Deprecated +- public void checkCapturedTreeStateForObserverNotify(final BlockPos pos, final org.bukkit.craftbukkit.block.CraftBlockState craftBlockState) { +- // notify observers if the block state is the same and the Y level equals the original y level (for mega trees) +- // blocks at the same Y level with the same state can be assumed to be saplings which trigger observers regardless of if the +- // tree grew or not +- if (craftBlockState.getPosition().getY() == pos.getY() && this.getBlockState(craftBlockState.getPosition()) == craftBlockState.getHandle()) { +- this.notifyAndUpdatePhysics(craftBlockState.getPosition(), null, craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getFlags(), 512); +- } +- } +- // Paper end - notify observers even if grow failed + + @Override + public CrashReportCategory fillReportDetails(CrashReport report) { +diff --git a/net/minecraft/world/entity/ai/behavior/UseBonemeal.java b/net/minecraft/world/entity/ai/behavior/UseBonemeal.java +index d815149ba20d815ec11fd7d4c203aa407e7aa8b2..78d5ab312a806b94403ddf38babea69a307690c1 100644 +--- a/net/minecraft/world/entity/ai/behavior/UseBonemeal.java ++++ b/net/minecraft/world/entity/ai/behavior/UseBonemeal.java +@@ -113,7 +113,7 @@ public class UseBonemeal extends Behavior { + } + } + +- if (!itemStack.isEmpty() && BoneMealItem.growCrop(itemStack, level, blockPos)) { ++ if (!itemStack.isEmpty() && BoneMealItem.growCrop(itemStack, level, blockPos, null)) { + level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, blockPos, 15); + this.cropPos = this.pickNextTarget(level, owner); + this.setCurrentCropAsTarget(owner); +diff --git a/net/minecraft/world/entity/animal/bee/Bee.java b/net/minecraft/world/entity/animal/bee/Bee.java +index 0cbcf23b6edba2305dfbbc95abb06a90a6edd42b..ca943ed049d3a454333fc4633ff75c47e7b12c6a 100644 +--- a/net/minecraft/world/entity/animal/bee/Bee.java ++++ b/net/minecraft/world/entity/animal/bee/Bee.java +@@ -1005,7 +1005,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { + } else if (blockState.is(Blocks.CAVE_VINES) || blockState.is(Blocks.CAVE_VINES_PLANT)) { + BonemealableBlock bonemealableBlock = (BonemealableBlock)blockState.getBlock(); + if (bonemealableBlock.isValidBonemealTarget(Bee.this.level(), blockPos, blockState)) { +- bonemealableBlock.performBonemeal((ServerLevel)Bee.this.level(), Bee.this.random, blockPos, blockState); ++ bonemealableBlock.performBonemeal(null, (ServerLevel)Bee.this.level(), Bee.this.random, blockPos, blockState); // Paper + blockState1 = Bee.this.level().getBlockState(blockPos); + } + } +diff --git a/net/minecraft/world/item/BedItem.java b/net/minecraft/world/item/BedItem.java +index 2818c7ad02a861270283384ceb7ecd4d44f6d624..f2cd7765da1ee5138aba9505c59354447ad061f8 100644 +--- a/net/minecraft/world/item/BedItem.java ++++ b/net/minecraft/world/item/BedItem.java +@@ -10,7 +10,7 @@ public class BedItem extends BlockItem { + } + + @Override +- protected boolean placeBlock(BlockPlaceContext context, BlockState state) { +- return context.getLevel().setBlock(context.getClickedPos(), state, Block.UPDATE_CLIENTS | Block.UPDATE_IMMEDIATE | Block.UPDATE_KNOWN_SHAPE); ++ protected boolean placeBlock(BlockPlaceContext context, BlockState state, io.papermc.paper.util.capture.PaperCapturingWorldLevel paperCapturingWorldLevel) { ++ return paperCapturingWorldLevel.setBlock(context.getClickedPos(), state, Block.UPDATE_CLIENTS | Block.UPDATE_IMMEDIATE | Block.UPDATE_KNOWN_SHAPE); + } + } +diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java +index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..12774a0efe6e3a3619dea1a5d21cd714767f69f1 100644 +--- a/net/minecraft/world/item/BlockItem.java ++++ b/net/minecraft/world/item/BlockItem.java +@@ -1,13 +1,18 @@ + package net.minecraft.world.item; + + import java.util.Map; ++ ++import io.papermc.paper.util.capture.PaperCapturingWorldLevel; ++import io.papermc.paper.util.capture.SimpleBlockCapture; + import net.minecraft.advancements.CriteriaTriggers; + import net.minecraft.core.BlockPos; + import net.minecraft.core.component.DataComponents; ++import net.minecraft.server.level.ServerLevel; + import net.minecraft.server.level.ServerPlayer; + import net.minecraft.server.permissions.Permissions; + import net.minecraft.sounds.SoundEvent; + import net.minecraft.sounds.SoundSource; ++import net.minecraft.world.InteractionHand; + import net.minecraft.world.InteractionResult; + import net.minecraft.world.entity.item.ItemEntity; + import net.minecraft.world.entity.player.Player; +@@ -57,59 +62,31 @@ public class BlockItem extends Item { + 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)) { +- return InteractionResult.FAIL; +- } else { ++ } ++ // Paper start ++ try ( SimpleBlockCapture capture = context.getLevel().capturer.createCaptureSession();) { ++ boolean placeRes = this.placeBlock(blockPlaceContext, placementState, capture.capturingWorldLevel()); ++ if (!placeRes) { ++ return InteractionResult.FAIL; ++ } ++ // Paper end ++ + BlockPos clickedPos = blockPlaceContext.getClickedPos(); +- Level level = blockPlaceContext.getLevel(); ++ io.papermc.paper.util.capture.PaperCapturingWorldLevel level = capture.capturingWorldLevel(); // Paper + Player player = blockPlaceContext.getPlayer(); + ItemStack itemInHand = blockPlaceContext.getItemInHand(); + 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(); +- +- player.containerMenu.forceHeldSlot(blockPlaceContext.getHand()); +- return InteractionResult.FAIL; +- } +- } +- // CraftBukkit end +- if (player instanceof ServerPlayer) { +- CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer)player, clickedPos, itemInHand); +- } ++ // Paper - move trigger + } + + 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, +@@ -119,8 +96,41 @@ public class BlockItem extends Item { + soundType.getPitch() * 0.8F + ); + level.gameEvent(GameEvent.BLOCK_PLACE, clickedPos, GameEvent.Context.of(player, blockState)); ++ // Paper start ++ InteractionHand hand = blockPlaceContext.getHand(); ++ ServerLevel serverLevel = (ServerLevel) blockPlaceContext.getLevel(); ++ org.bukkit.event.block.BlockPlaceEvent placeEvent = null; ++ ++ var newStates = capture.getCapturedBlockStates(); ++ ++ java.util.List blocks = new java.util.ArrayList<>(newStates.size()); ++ newStates.values().forEach((state) -> { ++ blocks.add(state.getBlock().getState()); ++ }); ++ ++ capture.openLegacySupport(); ++ ++ if (blocks.size() > 1) { ++ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, clickedPos); ++ } else if (blocks.size() == 1) { ++ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(serverLevel, player, hand, blocks.getFirst(), clickedPos); ++ } ++ ++ if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) { ++ // PAIL: Remove this when MC-99075 fixed ++ player.containerMenu.forceHeldSlot(hand); ++ return InteractionResult.FAIL; ++ } else { ++ // We are good! ++ capture.finalizePlacement(); ++ if (player instanceof ServerPlayer) { ++ CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer)player, clickedPos, itemInHand); ++ } ++ } ++ // Paper end ++ + itemInHand.consume(1, player); +- return InteractionResult.SUCCESS.configurePaper(e -> e.placedBlockAt(clickedPos.immutable())); // Paper - track placed block position from block item ++ return InteractionResult.SUCCESS; + } + } + } +@@ -134,7 +144,7 @@ public class BlockItem extends Item { + return context; + } + +- private static void updateBlockEntityComponents(Level level, BlockPos pos, ItemStack stack) { ++ private static void updateBlockEntityComponents(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, ItemStack stack) { // Paper - Canceling block placement + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity != null) { + blockEntity.applyComponentsFromItemStack(stack); +@@ -142,7 +152,7 @@ public class BlockItem extends Item { + } + } + +- protected boolean updateCustomBlockEntityTag(BlockPos pos, Level level, @Nullable Player player, ItemStack stack, BlockState state) { ++ protected boolean updateCustomBlockEntityTag(BlockPos pos, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, @Nullable Player player, ItemStack stack, BlockState state) { // Paper - Canceling block placement + return updateCustomBlockEntityTag(level, player, pos, stack); + } + +@@ -151,7 +161,7 @@ public class BlockItem extends Item { + return stateForPlacement != null && this.canPlace(context, stateForPlacement) ? stateForPlacement : null; + } + +- private BlockState updateBlockStateFromTag(BlockPos pos, Level level, ItemStack stack, BlockState state) { ++ private BlockState updateBlockStateFromTag(BlockPos pos, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ItemStack stack, BlockState state) { // Paper - Canceling block placement + BlockItemStateProperties blockItemStateProperties = stack.getOrDefault(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY); + if (blockItemStateProperties.isEmpty()) { + return state; +@@ -186,11 +196,11 @@ public class BlockItem extends Item { + return true; + } + +- protected boolean placeBlock(BlockPlaceContext context, BlockState state) { +- return context.getLevel().setBlock(context.getClickedPos(), state, Block.UPDATE_ALL_IMMEDIATE); ++ protected boolean placeBlock(BlockPlaceContext context, BlockState state, PaperCapturingWorldLevel paperCapturingWorldLevel) { ++ return paperCapturingWorldLevel.setBlock(context.getClickedPos(), state, Block.UPDATE_ALL_IMMEDIATE); + } + +- public static boolean updateCustomBlockEntityTag(Level level, @Nullable Player player, BlockPos pos, ItemStack stack) { ++ public static boolean updateCustomBlockEntityTag(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, @Nullable Player player, BlockPos pos, ItemStack stack) { // Paper - Canceling block placement + if (level.isClientSide()) { + return false; + } else { +diff --git a/net/minecraft/world/item/BoneMealItem.java b/net/minecraft/world/item/BoneMealItem.java +index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..55b6e1aab280676a37f0c70faa457535f6353038 100644 +--- a/net/minecraft/world/item/BoneMealItem.java ++++ b/net/minecraft/world/item/BoneMealItem.java +@@ -6,11 +6,13 @@ import net.minecraft.core.Holder; + import net.minecraft.core.particles.ParticleTypes; + import net.minecraft.core.registries.BuiltInRegistries; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.tags.BiomeTags; + import net.minecraft.tags.BlockTags; + import net.minecraft.util.ParticleUtils; + import net.minecraft.util.RandomSource; + import net.minecraft.world.InteractionResult; ++import net.minecraft.world.entity.player.Player; + import net.minecraft.world.item.context.UseOnContext; + import net.minecraft.world.level.Level; + import net.minecraft.world.level.LevelAccessor; +@@ -44,7 +46,7 @@ public class BoneMealItem extends Item { + BlockPos clickedPos = context.getClickedPos(); + BlockPos blockPos = clickedPos.relative(context.getClickedFace()); + ItemStack itemInHand = context.getItemInHand(); +- if (growCrop(itemInHand, level, clickedPos)) { ++ if (growCrop(itemInHand, level, clickedPos, context.getPlayer())) { // Paper + if (!level.isClientSide()) { + if (context.getPlayer() != null) itemInHand.causeUseVibration(context.getPlayer(), GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518 + level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, clickedPos, 15); +@@ -54,7 +56,15 @@ public class BoneMealItem extends Item { + } else { + BlockState blockState = level.getBlockState(clickedPos); + boolean isFaceSturdy = blockState.isFaceSturdy(level, clickedPos, context.getClickedFace()); +- if (isFaceSturdy && growWaterPlant(itemInHand, level, blockPos, context.getClickedFace())) { ++ ++ boolean result = org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( ++ (ServerLevel) level, ++ (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), ++ blockPos, ++ (res) -> growWaterPlant(itemInHand, res, blockPos, context.getClickedFace(), context.getPlayer()) ++ ); ++ ++ if (isFaceSturdy && result) { + if (!level.isClientSide()) { + if (context.getPlayer() != null) itemInHand.causeUseVibration(context.getPlayer(), GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518 + level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, blockPos, 15); +@@ -67,12 +77,17 @@ public class BoneMealItem extends Item { + } + } + +- public static boolean growCrop(ItemStack stack, Level level, BlockPos pos) { ++ public static boolean growCrop(ItemStack stack, Level level, BlockPos pos, @Nullable Player player) { // Paper + BlockState blockState = level.getBlockState(pos); + if (blockState.getBlock() instanceof BonemealableBlock bonemealableBlock && bonemealableBlock.isValidBonemealTarget(level, pos, blockState)) { + if (level instanceof ServerLevel) { + if (bonemealableBlock.isBonemealSuccess(level, level.random, pos, blockState)) { +- bonemealableBlock.performBonemeal((ServerLevel)level, level.random, pos, blockState); ++ org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( ++ (ServerLevel) level, ++ (org.bukkit.entity.Player) player.getBukkitEntity(), ++ pos, ++ (res) -> bonemealableBlock.performBonemeal((ServerPlayer) player, res, level.random, pos, blockState) ++ ); + } + + stack.shrink(1); +@@ -84,7 +99,7 @@ public class BoneMealItem extends Item { + } + } + +- public static boolean growWaterPlant(ItemStack stack, Level level, BlockPos pos, @Nullable Direction clickedSide) { ++ public static boolean growWaterPlant(ItemStack stack, LevelAccessor level, BlockPos pos, @Nullable Direction clickedSide, @Nullable Player player) { + if (level.getBlockState(pos).is(Blocks.WATER) && level.getFluidState(pos).getAmount() == 8) { + if (!(level instanceof ServerLevel)) { + return true; +@@ -107,7 +122,7 @@ public class BoneMealItem extends Item { + if (biome.is(BiomeTags.PRODUCES_CORALS_FROM_BONEMEAL)) { + if (i == 0 && clickedSide != null && clickedSide.getAxis().isHorizontal()) { + blockState = BuiltInRegistries.BLOCK +- .getRandomElementOf(BlockTags.WALL_CORALS, level.random) ++ .getRandomElementOf(BlockTags.WALL_CORALS, level.getRandom()) // Paper + .map(holder -> holder.value().defaultBlockState()) + .orElse(blockState); + if (blockState.hasProperty(BaseCoralWallFanBlock.FACING)) { +@@ -115,7 +130,7 @@ public class BoneMealItem extends Item { + } + } else if (random.nextInt(4) == 0) { + blockState = BuiltInRegistries.BLOCK +- .getRandomElementOf(BlockTags.UNDERWATER_BONEMEALS, level.random) ++ .getRandomElementOf(BlockTags.UNDERWATER_BONEMEALS, level.getRandom()) // Paper + .map(holder -> holder.value().defaultBlockState()) + .orElse(blockState); + } +@@ -134,7 +149,7 @@ public class BoneMealItem extends Item { + } else if (blockState1.is(Blocks.SEAGRASS) + && ((BonemealableBlock)Blocks.SEAGRASS).isValidBonemealTarget(level, blockPos, blockState1) + && random.nextInt(10) == 0) { +- ((BonemealableBlock)Blocks.SEAGRASS).performBonemeal((ServerLevel)level, random, blockPos, blockState1); ++ ((BonemealableBlock)Blocks.SEAGRASS).performBonemeal((ServerPlayer) player, (ServerLevel)level, random, blockPos, blockState1); + } + } + } +diff --git a/net/minecraft/world/item/DoubleHighBlockItem.java b/net/minecraft/world/item/DoubleHighBlockItem.java +index e6406e4d7ed3c340e3e3137165e9a2fa7e4f3656..f5faccc8951718b1bfe3fd644fcfe3993b6b5665 100644 +--- a/net/minecraft/world/item/DoubleHighBlockItem.java ++++ b/net/minecraft/world/item/DoubleHighBlockItem.java +@@ -13,11 +13,11 @@ public class DoubleHighBlockItem extends BlockItem { + } + + @Override +- protected boolean placeBlock(BlockPlaceContext context, BlockState state) { ++ protected boolean placeBlock(BlockPlaceContext context, BlockState state, io.papermc.paper.util.capture.PaperCapturingWorldLevel paperCapturingWorldLevel) { + Level level = context.getLevel(); + BlockPos blockPos = context.getClickedPos().above(); +- BlockState blockState = level.isWaterAt(blockPos) ? Blocks.WATER.defaultBlockState() : Blocks.AIR.defaultBlockState(); +- level.setBlock(blockPos, blockState, Block.UPDATE_ALL_IMMEDIATE | Block.UPDATE_KNOWN_SHAPE); +- return super.placeBlock(context, state); ++ BlockState blockState = paperCapturingWorldLevel.isWaterAt(blockPos) ? Blocks.WATER.defaultBlockState() : Blocks.AIR.defaultBlockState(); ++ paperCapturingWorldLevel.setBlock(blockPos, blockState, Block.UPDATE_ALL_IMMEDIATE | Block.UPDATE_KNOWN_SHAPE); ++ return super.placeBlock(context, state, paperCapturingWorldLevel); + } + } +diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java +index ed06cffe8a5eba2ca4a34ade81f8185e21d7b651..25a094bc33546bc71bbe4ad348bd954a0bb6e0e3 100644 +--- a/net/minecraft/world/item/ItemStack.java ++++ b/net/minecraft/world/item/ItemStack.java +@@ -379,166 +379,10 @@ public final class ItemStack implements DataComponentHolder { + return InteractionResult.PASS; + } else { + Item item = this.getItem(); +- // CraftBukkit start - handle all block place event logic here +- DataComponentPatch previousPatch = this.components.asPatch(); +- int oldCount = this.getCount(); +- ServerLevel serverLevel = (ServerLevel) context.getLevel(); +- +- if (!(item instanceof BucketItem/* || item instanceof SolidBucketItem*/)) { // if not bucket // Paper - Fix cancelled powdered snow bucket placement +- serverLevel.captureBlockStates = true; +- // special case bonemeal +- if (item == Items.BONE_MEAL) { +- serverLevel.captureTreeGeneration = true; +- } +- } +- InteractionResult interactionResult; +- try { +- interactionResult = item.useOn(context); +- } finally { +- serverLevel.captureBlockStates = false; +- } +- DataComponentPatch newPatch = this.components.asPatch(); +- int newCount = this.getCount(); +- this.setCount(oldCount); +- this.restorePatch(previousPatch); +- if (interactionResult.consumesAction() && serverLevel.captureTreeGeneration && !serverLevel.capturedBlockStates.isEmpty()) { +- serverLevel.captureTreeGeneration = false; +- org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(clickedPos, serverLevel); +- org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; +- net.minecraft.world.level.block.SaplingBlock.treeType = null; +- List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values()); +- serverLevel.capturedBlockStates.clear(); +- org.bukkit.event.world.StructureGrowEvent structureEvent = null; +- if (treeType != null) { +- boolean isBonemeal = this.getItem() == Items.BONE_MEAL; +- structureEvent = new org.bukkit.event.world.StructureGrowEvent(location, treeType, isBonemeal, (org.bukkit.entity.Player) player.getBukkitEntity(), (List) (List) blocks); +- org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent); +- } +- +- org.bukkit.event.block.BlockFertilizeEvent fertilizeEvent = new org.bukkit.event.block.BlockFertilizeEvent(org.bukkit.craftbukkit.block.CraftBlock.at(serverLevel, clickedPos), (org.bukkit.entity.Player) player.getBukkitEntity(), (List) (List) blocks); +- fertilizeEvent.setCancelled(structureEvent != null && structureEvent.isCancelled()); +- org.bukkit.Bukkit.getPluginManager().callEvent(fertilizeEvent); +- +- if (!fertilizeEvent.isCancelled()) { +- // Change the stack to its new contents if it hasn't been tampered with. +- if (this.getCount() == oldCount && Objects.equals(this.components.asPatch(), previousPatch)) { +- this.restorePatch(newPatch); +- this.setCount(newCount); +- } +- for (org.bukkit.craftbukkit.block.CraftBlockState snapshot : blocks) { +- // SPIGOT-7572 - Move fix for SPIGOT-7248 to CapturedBlockState, to allow bees in bee nest +- snapshot.place(snapshot.getFlags()); +- serverLevel.checkCapturedTreeStateForObserverNotify(clickedPos, snapshot); // Paper - notify observers even if grow failed +- } +- player.awardStat(Stats.ITEM_USED.get(item)); // SPIGOT-7236 - award stat +- } +- +- SignItem.openSign = null; // SPIGOT-6758 - Reset on early return +- return interactionResult; +- } +- serverLevel.captureTreeGeneration = false; ++ InteractionResult interactionResult = item.useOn(context); + if (player != null && interactionResult instanceof InteractionResult.Success success && success.wasItemInteraction()) { +- InteractionHand hand = context.getHand(); +- org.bukkit.event.block.BlockPlaceEvent placeEvent = null; +- List blocks = new java.util.ArrayList<>(serverLevel.capturedBlockStates.values()); +- serverLevel.capturedBlockStates.clear(); +- if (blocks.size() > 1) { +- placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, clickedPos); +- } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - Fix cancelled powdered snow bucket placement +- placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(serverLevel, player, hand, blocks.getFirst(), clickedPos); +- } +- +- if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) { +- interactionResult = InteractionResult.FAIL; // cancel placement +- // PAIL: Remove this when MC-99075 fixed +- player.containerMenu.forceHeldSlot(hand); +- serverLevel.capturedTileEntities.clear(); // Paper - Allow chests to be placed with NBT data; clear out block entities as chests and such will pop loot +- // revert back all captured blocks +- for (org.bukkit.block.BlockState blockstate : blocks) { +- ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).revertPlace(); +- } +- +- SignItem.openSign = null; // SPIGOT-6758 - Reset on early return +- } else { +- // Change the stack to its new contents if it hasn't been tampered with. +- if (this.getCount() == oldCount && Objects.equals(this.components.asPatch(), previousPatch)) { +- this.restorePatch(newPatch); +- this.setCount(newCount); +- } +- +- for (java.util.Map.Entry e : serverLevel.capturedTileEntities.entrySet()) { +- serverLevel.setBlockEntity(e.getValue()); +- } +- +- for (org.bukkit.block.BlockState blockstate : blocks) { +- int updateFlags = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getFlags(); +- net.minecraft.world.level.block.state.BlockState oldBlock = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getHandle(); +- BlockPos newPos = ((org.bukkit.craftbukkit.block.CraftBlockState) blockstate).getPosition(); +- net.minecraft.world.level.block.state.BlockState block = serverLevel.getBlockState(newPos); +- +- if (!(block.getBlock() instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // Containers get placed automatically +- block.onPlace(serverLevel, newPos, oldBlock, true, context); +- } +- +- serverLevel.notifyAndUpdatePhysics(newPos, null, oldBlock, block, serverLevel.getBlockState(newPos), updateFlags, net.minecraft.world.level.block.Block.UPDATE_LIMIT); // send null chunk as chunk.k() returns false by this point +- } +- +- if (this.item == Items.WITHER_SKELETON_SKULL) { // Special case skulls to allow wither spawns to be cancelled +- BlockPos bp = clickedPos; +- if (!serverLevel.getBlockState(clickedPos).canBeReplaced()) { +- if (!serverLevel.getBlockState(clickedPos).isSolid()) { +- bp = null; +- } else { +- bp = bp.relative(context.getClickedFace()); +- } +- } +- if (bp != null) { +- net.minecraft.world.level.block.entity.BlockEntity te = serverLevel.getBlockEntity(bp); +- if (te instanceof net.minecraft.world.level.block.entity.SkullBlockEntity) { +- net.minecraft.world.level.block.WitherSkullBlock.checkSpawn(serverLevel, bp, (net.minecraft.world.level.block.entity.SkullBlockEntity) te); +- } +- } +- } +- +- // SPIGOT-4678 +- if (this.item instanceof SignItem && SignItem.openSign != null) { +- try { +- if (serverLevel.getBlockEntity(SignItem.openSign) instanceof net.minecraft.world.level.block.entity.SignBlockEntity blockEntity) { +- if (serverLevel.getBlockState(SignItem.openSign).getBlock() instanceof net.minecraft.world.level.block.SignBlock signBlock) { +- signBlock.openTextEdit(player, blockEntity, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE); // CraftBukkit // Paper - Add PlayerOpenSignEvent +- } +- } +- } finally { +- SignItem.openSign = null; +- } +- } +- +- // SPIGOT-7315: Moved from BedBlock#setPlacedBy +- if (placeEvent != null && this.item instanceof BedItem) { +- BlockPos pos = ((org.bukkit.craftbukkit.block.CraftBlock) placeEvent.getBlock()).getPosition(); +- net.minecraft.world.level.block.state.BlockState state = serverLevel.getBlockState(pos); +- +- if (state.getBlock() instanceof net.minecraft.world.level.block.BedBlock) { +- serverLevel.updateNeighborsAt(pos, net.minecraft.world.level.block.Blocks.AIR); +- state.updateNeighbourShapes(serverLevel, pos, net.minecraft.world.level.block.Block.UPDATE_ALL); +- } +- } +- +- // SPIGOT-1288 - play sound stripped from BlockItem +- if (this.item instanceof BlockItem && success.paperSuccessContext().placedBlockPosition() != null) { +- // Paper start - Fix spigot sound playing for BlockItem ItemStacks +- net.minecraft.world.level.block.state.BlockState state = serverLevel.getBlockState(success.paperSuccessContext().placedBlockPosition()); +- net.minecraft.world.level.block.SoundType soundType = state.getSoundType(); +- // Paper end - Fix spigot sound playing for BlockItem ItemStacks +- serverLevel.playSound(player, clickedPos, soundType.getPlaceSound(), net.minecraft.sounds.SoundSource.BLOCKS, (soundType.getVolume() + 1.0F) / 2.0F, soundType.getPitch() * 0.8F); +- } +- +- player.awardStat(Stats.ITEM_USED.get(item)); +- } ++ player.awardStat(Stats.ITEM_USED.get(item)); + } +- serverLevel.capturedTileEntities.clear(); +- serverLevel.capturedBlockStates.clear(); +- // CraftBukkit end + + return interactionResult; + } +diff --git a/net/minecraft/world/item/SignItem.java b/net/minecraft/world/item/SignItem.java +index 3a41cf80e347ae3b30858879fb91f719625c8bb6..8e08155f4c3168994aa31452a7f4f2370ebf3b71 100644 +--- a/net/minecraft/world/item/SignItem.java ++++ b/net/minecraft/world/item/SignItem.java +@@ -11,7 +11,7 @@ import net.minecraft.world.level.block.state.BlockState; + import org.jspecify.annotations.Nullable; + + public class SignItem extends StandingAndWallBlockItem { +- public static BlockPos openSign; // CraftBukkit ++ + public SignItem(Block standingBlock, Block wallBlock, Item.Properties properties) { + super(standingBlock, wallBlock, Direction.DOWN, properties); + } +@@ -21,17 +21,14 @@ public class SignItem extends StandingAndWallBlockItem { + } + + @Override +- protected boolean updateCustomBlockEntityTag(BlockPos pos, Level level, @Nullable Player player, ItemStack stack, BlockState state) { ++ protected boolean updateCustomBlockEntityTag(BlockPos pos, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, @Nullable Player player, ItemStack stack, BlockState state) { // Paper + boolean flag = super.updateCustomBlockEntityTag(pos, level, player, stack, state); + if (!level.isClientSide() + && !flag + && player != null + && level.getBlockEntity(pos) instanceof SignBlockEntity signBlockEntity + && level.getBlockState(pos).getBlock() instanceof SignBlock signBlock) { +- // CraftBukkit start - SPIGOT-4678 +- // signBlock.openTextEdit(player, signBlockEntity, true); +- SignItem.openSign = pos; +- // CraftBukkit end ++ level.addTask((serverLevel) -> signBlock.openTextEdit(player, signBlockEntity, true)); // Paper + } + + return flag; +diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java +index 579bbba4e823d4d0318e58759ca732b7c8e4d865..0b7aacb48cf0b0f974fd24d90f0910a3a72fcde9 100644 +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -143,10 +143,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + private final CraftWorld world; + public org.bukkit.generator.@Nullable ChunkGenerator generator; + +- public boolean captureBlockStates = false; +- public boolean captureTreeGeneration = false; +- public Map capturedBlockStates = new java.util.LinkedHashMap<>(); // Paper +- public Map capturedTileEntities = new java.util.LinkedHashMap<>(); // Paper - Retain block place order when capturing blockstates ++ public final io.papermc.paper.util.capture.WorldCapturer capturer = new io.papermc.paper.util.capture.WorldCapturer(this); + @Nullable + public List captureDrops; + public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>(); +@@ -978,14 +975,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + @Override + @Nullable + public final BlockState getBlockStateIfLoaded(BlockPos pos) { +- // CraftBukkit start - tree generation +- if (this.captureTreeGeneration) { +- CraftBlockState previous = this.capturedBlockStates.get(pos); +- if (previous != null) { +- return previous.getHandle(); ++ // Paper start - Perf: Optimize capturedTileEntities lookup ++ if (this.capturer.isCapturing() && this.capturer.getCapture().isViewingCaptureState()) { ++ BlockState guess = this.capturer.getCapture().getCaptureBlockStateIfLoaded(pos); ++ if (guess != null) { ++ return guess; + } + } +- // CraftBukkit end ++ // Paper end - Perf: Optimize capturedTileEntities lookup + if (this.isOutsideBuildHeight(pos)) { + return Blocks.VOID_AIR.defaultBlockState(); + } else { +@@ -1043,22 +1040,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + @Override + public boolean setBlock(BlockPos pos, BlockState state, @Block.UpdateFlags int flags, int recursionLeft) { +- // CraftBukkit start - tree generation +- if (this.captureTreeGeneration) { +- // Paper start - Protect Bedrock and End Portal/Frames from being destroyed +- BlockState type = getBlockState(pos); +- if (!type.isDestroyable()) return false; +- // Paper end - Protect Bedrock and End Portal/Frames from being destroyed +- CraftBlockState blockstate = this.capturedBlockStates.get(pos); +- if (blockstate == null) { +- blockstate = org.bukkit.craftbukkit.block.CapturedBlockState.getTreeBlockState(this, pos, flags); +- this.capturedBlockStates.put(pos.immutable(), blockstate); +- } +- blockstate.setData(state); +- blockstate.setFlags(flags); +- return true; ++ // Paper start - Perf: Optimize capturedTileEntities lookup ++ if (this.capturer.isCapturing() && this.capturer.getCapture().isViewingCaptureState()) { ++ return this.capturer.getCapture().capturingWorldLevel().setBlockSilent(pos, state, flags, recursionLeft); + } +- // CraftBukkit end ++ // Paper end - Perf: Optimize capturedTileEntities lookup + if (!this.isInValidBounds(pos)) { + return false; + } else if (!this.isClientSide() && this.isDebug()) { +@@ -1066,40 +1052,20 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + } else { + LevelChunk chunkAt = this.getChunkAt(pos); + Block block = state.getBlock(); +- // CraftBukkit start - capture blockstates +- boolean captured = false; +- if (this.captureBlockStates) { +- final CraftBlockState snapshot; +- if (!this.capturedBlockStates.containsKey(pos)) { +- snapshot = (CraftBlockState) org.bukkit.craftbukkit.block.CraftBlock.at(this, pos).getState(); // Paper - use CB getState to get a suitable snapshot +- this.capturedBlockStates.put(pos.immutable(), snapshot); +- captured = true; +- } else { +- snapshot = this.capturedBlockStates.get(pos); +- } +- snapshot.setFlags(flags); // Paper - always set the flag of the most recent call to mitigate issues with multiple update at the same pos with different flags +- } + BlockState blockState = chunkAt.setBlockState(pos, state, flags); + this.chunkPacketBlockController.onBlockChange(this, pos, state, blockState, flags, recursionLeft); // Paper - Anti-Xray +- // CraftBukkit end + if (blockState == null) { +- // CraftBukkit start - remove blockstate if failed (or the same) +- if (this.captureBlockStates && captured) { +- this.capturedBlockStates.remove(pos); +- } +- // CraftBukkit end + return false; + } else { + BlockState blockState1 = this.getBlockState(pos); +- /* + if (blockState1 == state) { + if (blockState != blockState1) { + this.setBlocksDirty(pos, blockState, blockState1); + } + + if ((flags & Block.UPDATE_CLIENTS) != 0 +- && (!this.isClientSide() || (flags & Block.UPDATE_INVISIBLE) == 0) +- && (this.isClientSide() || chunkAt.getFullStatus() != null && chunkAt.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING))) { ++ && (!this.isClientSide() || (flags & Block.UPDATE_INVISIBLE) == 0) ++ && (this.isClientSide() || chunkAt.getFullStatus() != null && chunkAt.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING))) { + this.sendBlockUpdated(pos, blockState, state, flags); + } + +@@ -1112,74 +1078,27 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + + if ((flags & Block.UPDATE_KNOWN_SHAPE) == 0 && recursionLeft > 0) { + int i = flags & ~(Block.UPDATE_SUPPRESS_DROPS | Block.UPDATE_NEIGHBORS); +- blockState.updateIndirectNeighbourShapes(this, pos, i, recursionLeft - 1); ++ // CraftBukkit start ++ blockState.updateIndirectNeighbourShapes(this, pos, i, recursionLeft - 1); // Don't call an event for the old block to limit event spam ++ boolean cancelledUpdates = false; // Paper - Fix block place logic ++ if (((ServerLevel)this).hasPhysicsEvent) { // Paper - BlockPhysicsEvent ++ org.bukkit.event.block.BlockPhysicsEvent event = new org.bukkit.event.block.BlockPhysicsEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this, pos), CraftBlockData.fromData(state)); ++ cancelledUpdates = !event.callEvent(); // Paper - Fix block place logic ++ } ++ // CraftBukkit end ++ if (!cancelledUpdates) { // Paper - Fix block place logic + state.updateNeighbourShapes(this, pos, i, recursionLeft - 1); + state.updateIndirectNeighbourShapes(this, pos, i, recursionLeft - 1); ++ } // Paper - Fix block place logic + } + + this.updatePOIOnBlockStateChange(pos, blockState, blockState1); + } +- */ +- +- // CraftBukkit start +- if (!this.captureBlockStates) { // Don't notify clients or update physics while capturing blockstates +- // Modularize client and physic updates +- // Spigot start +- try { +- this.notifyAndUpdatePhysics(pos, chunkAt, blockState, state, blockState1, flags, recursionLeft); +- } catch (StackOverflowError ex) { +- Level.lastPhysicsProblem = pos.immutable(); +- } +- // Spigot end +- } +- // CraftBukkit end + + return true; + } + } + } +- +- // CraftBukkit start - Split off from above in order to directly send client and physic updates +- public void notifyAndUpdatePhysics(BlockPos pos, LevelChunk chunkAt, BlockState oldState, BlockState newState, BlockState currentState, @Block.UpdateFlags int flags, int recursionLeft) { +- BlockState state = newState; +- BlockState blockState = oldState; +- BlockState blockState1 = currentState; +- if (blockState1 == state) { +- if (blockState != blockState1) { +- this.setBlocksDirty(pos, blockState, blockState1); +- } +- +- if ((flags & Block.UPDATE_CLIENTS) != 0 && (!this.isClientSide() || (flags & Block.UPDATE_INVISIBLE) == 0) && (this.isClientSide() || chunkAt == null || (chunkAt.getFullStatus() != null && chunkAt.getFullStatus().isOrAfter(FullChunkStatus.FULL)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement // Paper - rewrite chunk system - change from ticking to full +- this.sendBlockUpdated(pos, blockState, state, flags); +- } +- +- if ((flags & Block.UPDATE_NEIGHBORS) != 0) { +- this.updateNeighborsAt(pos, blockState.getBlock()); +- if (!this.isClientSide() && state.hasAnalogOutputSignal()) { +- this.updateNeighbourForOutputSignal(pos, newState.getBlock()); +- } +- } +- +- if ((flags & Block.UPDATE_KNOWN_SHAPE) == 0 && recursionLeft > 0) { +- int i = flags & ~(Block.UPDATE_SUPPRESS_DROPS | Block.UPDATE_NEIGHBORS); +- +- // CraftBukkit start +- blockState.updateIndirectNeighbourShapes(this, pos, i, recursionLeft - 1); // Don't call an event for the old block to limit event spam +- boolean cancelledUpdates = false; // Paper - Fix block place logic +- if (((ServerLevel)this).hasPhysicsEvent) { // Paper - BlockPhysicsEvent +- org.bukkit.event.block.BlockPhysicsEvent event = new org.bukkit.event.block.BlockPhysicsEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this, pos), CraftBlockData.fromData(state)); +- cancelledUpdates = !event.callEvent(); // Paper - Fix block place logic +- } +- // CraftBukkit end +- if (!cancelledUpdates) { // Paper - Fix block place logic +- state.updateNeighbourShapes(this, pos, i, recursionLeft - 1); +- state.updateIndirectNeighbourShapes(this, pos, i, recursionLeft - 1); +- } // Paper - Fix block place logic +- } +- +- this.updatePOIOnBlockStateChange(pos, blockState, blockState1); +- } +- } + // CraftBukkit end + public void updatePOIOnBlockStateChange(BlockPos pos, BlockState oldState, BlockState newState) { + } +@@ -1288,12 +1207,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + @Override + public BlockState getBlockState(BlockPos pos) { + // CraftBukkit start - tree generation +- if (this.captureTreeGeneration) { +- CraftBlockState previous = this.capturedBlockStates.get(pos); // Paper +- if (previous != null) { +- return previous.getHandle(); ++ // Paper start - Perf: Optimize capturedTileEntities lookup ++ if (this.capturer.isCapturing() && this.capturer.getCapture().isViewingCaptureState()) { ++ BlockState guess = this.capturer.getCapture().getCaptureBlockState(pos); ++ if (guess != null) { ++ return guess; + } + } ++ // Paper end - Perf: Optimize capturedTileEntities lookup + // CraftBukkit end + if (!this.isInValidBounds(pos)) { + return Blocks.VOID_AIR.defaultBlockState(); +@@ -1576,9 +1497,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + @Override + public @Nullable BlockEntity getBlockEntity(BlockPos pos) { + // Paper start - Perf: Optimize capturedTileEntities lookup +- net.minecraft.world.level.block.entity.BlockEntity blockEntity; +- if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(pos)) != null) { +- return blockEntity; ++ if (this.capturer.isCapturing() && this.capturer.getCapture().isViewingCaptureState()) { ++ var guess = this.capturer.getCapture().getCaptureBlockEntity(pos); ++ if (guess != null) { ++ return guess.orElse(null); ++ } + } + // Paper end - Perf: Optimize capturedTileEntities lookup + if (!this.isInValidBounds(pos)) { +@@ -1593,12 +1516,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public void setBlockEntity(BlockEntity blockEntity) { + BlockPos blockPos = blockEntity.getBlockPos(); + if (this.isInValidBounds(blockPos)) { +- // CraftBukkit start +- if (this.captureBlockStates) { +- this.capturedTileEntities.put(blockPos.immutable(), blockEntity); ++ // Paper start - Perf: Optimize capturedTileEntities lookup ++ if (this.capturer.isCapturing() && this.capturer.getCapture().isViewingCaptureState()) { ++ this.capturer.getCapture().capturingWorldLevel().setBlockEntity(blockEntity); + return; + } +- // CraftBukkit end ++ // Paper end - Perf: Optimize capturedTileEntities lookup ++ + this.getChunkAt(blockPos).addAndRegisterBlockEntity(blockEntity); + } + } +diff --git a/net/minecraft/world/level/WorldGenLevel.java b/net/minecraft/world/level/WorldGenLevel.java +index d8f3ed6f5e5cbc045399e38664cd062737481f7b..83336865ad6ab30c3494361c5fa6088607126b6b 100644 +--- a/net/minecraft/world/level/WorldGenLevel.java ++++ b/net/minecraft/world/level/WorldGenLevel.java +@@ -13,4 +13,5 @@ public interface WorldGenLevel extends ServerLevelAccessor { + + default void setCurrentlyGenerating(@Nullable Supplier currentlyGenerating) { + } ++ + } +diff --git a/net/minecraft/world/level/block/AzaleaBlock.java b/net/minecraft/world/level/block/AzaleaBlock.java +index 435a455ad2ec3dfb142d570a51a720bc6c49dac3..5841a0a3decc7fabd206f5d017ddc68db464556c 100644 +--- a/net/minecraft/world/level/block/AzaleaBlock.java ++++ b/net/minecraft/world/level/block/AzaleaBlock.java +@@ -49,8 +49,8 @@ public class AzaleaBlock extends VegetationBlock implements BonemealableBlock { + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { +- TreeGrower.AZALEA.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ TreeGrower.AZALEA.growTree(level, level.getChunkSource().getGenerator(), pos, state, random, new java.util.concurrent.atomic.AtomicReference<>()); // Paper + } + + @Override +diff --git a/net/minecraft/world/level/block/BambooSaplingBlock.java b/net/minecraft/world/level/block/BambooSaplingBlock.java +index 88c204ad9d4ead792eb618dbb8611cca863e798c..36a60db022271496e851106d19aa303f16af2cb9 100644 +--- a/net/minecraft/world/level/block/BambooSaplingBlock.java ++++ b/net/minecraft/world/level/block/BambooSaplingBlock.java +@@ -84,11 +84,11 @@ public class BambooSaplingBlock extends Block implements BonemealableBlock { + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + this.growBamboo(level, pos); + } + +- protected void growBamboo(Level level, BlockPos state) { ++ protected void growBamboo(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos state) { // Paper + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, state, state.above(), Blocks.BAMBOO.defaultBlockState().setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), Block.UPDATE_ALL); // CraftBukkit - BlockSpreadEvent + } + } +diff --git a/net/minecraft/world/level/block/BambooStalkBlock.java b/net/minecraft/world/level/block/BambooStalkBlock.java +index 81e2a279d4c29f5fe52387875489239515a8c82b..a9c033d5bf4a78162bbcc7baf46626f259f65296 100644 +--- a/net/minecraft/world/level/block/BambooStalkBlock.java ++++ b/net/minecraft/world/level/block/BambooStalkBlock.java +@@ -166,7 +166,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + int heightAboveUpToMax = this.getHeightAboveUpToMax(level, pos); + int heightBelowUpToMax = this.getHeightBelowUpToMax(level, pos); + int i = heightAboveUpToMax + heightBelowUpToMax + 1; +@@ -185,7 +185,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { + } + } + +- protected void growBamboo(BlockState state, Level level, BlockPos pos, RandomSource random, int age) { ++ protected void growBamboo(BlockState state, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, RandomSource random, int age) { // Paper + BlockState blockState = level.getBlockState(pos.below()); + BlockPos blockPos = pos.below(2); + BlockState blockState1 = level.getBlockState(blockPos); +diff --git a/net/minecraft/world/level/block/BedBlock.java b/net/minecraft/world/level/block/BedBlock.java +index 7bfc62120cae4c5e83cb0d86b759b7ffb2336a95..45ee613f2021f769c9f940524a1a61db352decd5 100644 +--- a/net/minecraft/world/level/block/BedBlock.java ++++ b/net/minecraft/world/level/block/BedBlock.java +@@ -330,16 +330,11 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { + super.setPlacedBy(level, pos, state, placer, stack); + if (!level.isClientSide()) { + BlockPos blockPos = pos.relative(state.getValue(FACING)); + level.setBlock(blockPos, state.setValue(PART, BedPart.HEAD), Block.UPDATE_ALL); +- // CraftBukkit start - SPIGOT-7315: Don't updated if we capture block states +- if (level.captureBlockStates) { +- return; +- } +- // CraftBukkit end + level.updateNeighborsAt(pos, Blocks.AIR); + state.updateNeighbourShapes(level, pos, Block.UPDATE_ALL); + } +diff --git a/net/minecraft/world/level/block/BeetrootBlock.java b/net/minecraft/world/level/block/BeetrootBlock.java +index dbc912d514120a33f22959d6dc36ccf6ebc6be80..6fb29d9c58a4b304591463f9937f616f6c4d68e5 100644 +--- a/net/minecraft/world/level/block/BeetrootBlock.java ++++ b/net/minecraft/world/level/block/BeetrootBlock.java +@@ -8,6 +8,7 @@ import net.minecraft.world.item.Items; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.ItemLike; + import net.minecraft.world.level.Level; ++import net.minecraft.world.level.LevelAccessor; + import net.minecraft.world.level.block.state.BlockBehaviour; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.block.state.StateDefinition; +@@ -54,7 +55,7 @@ public class BeetrootBlock extends CropBlock { + } + + @Override +- protected int getBonemealAgeIncrease(Level level) { ++ protected int getBonemealAgeIncrease(LevelAccessor level) { // Paper + return super.getBonemealAgeIncrease(level) / 3; + } + +diff --git a/net/minecraft/world/level/block/BigDripleafBlock.java b/net/minecraft/world/level/block/BigDripleafBlock.java +index c2ef71dd9ee070f80d32b8829b80240ff22f0f7f..66a41f4198551d22714d74330cad8938c228e9f5 100644 +--- a/net/minecraft/world/level/block/BigDripleafBlock.java ++++ b/net/minecraft/world/level/block/BigDripleafBlock.java +@@ -177,7 +177,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + BlockPos blockPos = pos.above(); + BlockState blockState = level.getBlockState(blockPos); + if (canPlaceAt(level, blockPos, blockState)) { +diff --git a/net/minecraft/world/level/block/BigDripleafStemBlock.java b/net/minecraft/world/level/block/BigDripleafStemBlock.java +index 0ce3e60a94dff03874cad51a936f247d5c3d65ce..425c367b60413c6f664be7b43933b06c8358cb4f 100644 +--- a/net/minecraft/world/level/block/BigDripleafStemBlock.java ++++ b/net/minecraft/world/level/block/BigDripleafStemBlock.java +@@ -119,7 +119,7 @@ public class BigDripleafStemBlock extends HorizontalDirectionalBlock implements + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + Optional topConnectedBlock = BlockUtil.getTopConnectedBlock(level, pos, state.getBlock(), Direction.UP, Blocks.BIG_DRIPLEAF); + if (!topConnectedBlock.isEmpty()) { + BlockPos blockPos = topConnectedBlock.get(); +diff --git a/net/minecraft/world/level/block/Block.java b/net/minecraft/world/level/block/Block.java +index 94b4143449c99ee35db44ab8e2a766d924aa6410..9b9cc245b1fc42d9747de68049189935f2df99b0 100644 +--- a/net/minecraft/world/level/block/Block.java ++++ b/net/minecraft/world/level/block/Block.java +@@ -508,7 +508,7 @@ public class Block extends BlockBehaviour implements ItemLike { + } // Paper - fix drops not preventing stats/food exhaustion + } + +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing + } + + public boolean isPossibleToRespawnInThis(BlockState state) { +diff --git a/net/minecraft/world/level/block/BonemealableBlock.java b/net/minecraft/world/level/block/BonemealableBlock.java +index 676cc6cb2ea0ac25c4e4bd6a32856f07784c7b49..a858eb2cc530cd55a9a98e001fa0b11e0cc039ad 100644 +--- a/net/minecraft/world/level/block/BonemealableBlock.java ++++ b/net/minecraft/world/level/block/BonemealableBlock.java +@@ -15,14 +15,14 @@ public interface BonemealableBlock { + + boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state); + +- void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state); ++ void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state); // Paper + + static boolean hasSpreadableNeighbourPos(LevelReader level, BlockPos pos, BlockState state) { + return getSpreadableNeighbourPos(Direction.Plane.HORIZONTAL.stream().toList(), level, pos, state).isPresent(); + } + +- static Optional findSpreadableNeighbourPos(Level level, BlockPos pos, BlockState state) { +- return getSpreadableNeighbourPos(Direction.Plane.HORIZONTAL.shuffledCopy(level.random), level, pos, state); ++ static Optional findSpreadableNeighbourPos(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state) { // Paper ++ return getSpreadableNeighbourPos(Direction.Plane.HORIZONTAL.shuffledCopy(level.getRandom()), level, pos, state); // Paper + } + + private static Optional getSpreadableNeighbourPos(List directions, LevelReader level, BlockPos pos, BlockState state) { +diff --git a/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java b/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java +index ee701a4c5042aec359271533680d292a6169d4db..b00d810fc5d129e66c6fb06e8dca9912ce5e09c7 100644 +--- a/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java ++++ b/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java +@@ -41,11 +41,11 @@ public class BonemealableFeaturePlacerBlock extends Block implements Bonemealabl + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + level.registryAccess() + .lookup(Registries.CONFIGURED_FEATURE) + .flatMap(registry -> registry.get(this.feature)) +- .ifPresent(reference -> reference.value().place(level, level.getChunkSource().getGenerator(), random, pos.above())); ++ .ifPresent(reference -> reference.value().place(level, level.getGenerator(), random, pos.above())); + } + + @Override +diff --git a/net/minecraft/world/level/block/BushBlock.java b/net/minecraft/world/level/block/BushBlock.java +index 532d2982b520f6f9f7021ec85ef3bcf9bd31e141..ce59e4b69854d6122370a7736ae99ae014e5600c 100644 +--- a/net/minecraft/world/level/block/BushBlock.java ++++ b/net/minecraft/world/level/block/BushBlock.java +@@ -41,7 +41,7 @@ public class BushBlock extends VegetationBlock implements BonemealableBlock { + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + BonemealableBlock.findSpreadableNeighbourPos(level, pos, state).ifPresent(blockPos -> level.setBlockAndUpdate(blockPos, this.defaultBlockState())); + } + } +diff --git a/net/minecraft/world/level/block/CaveVinesBlock.java b/net/minecraft/world/level/block/CaveVinesBlock.java +index f4a4dc14012c110e58b1c9272d80d4b89394d090..31758dc5d353faf29272ba3feaf72ee360d6949f 100644 +--- a/net/minecraft/world/level/block/CaveVinesBlock.java ++++ b/net/minecraft/world/level/block/CaveVinesBlock.java +@@ -89,7 +89,7 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements CaveVines { + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + level.setBlock(pos, state.setValue(BERRIES, true), Block.UPDATE_CLIENTS); + } + } +diff --git a/net/minecraft/world/level/block/CaveVinesPlantBlock.java b/net/minecraft/world/level/block/CaveVinesPlantBlock.java +index aba65098fb3202e31b07aa2cee52acc5b671fa95..9d249b1ab62c57d0adbe8e1299c710f3d8732883 100644 +--- a/net/minecraft/world/level/block/CaveVinesPlantBlock.java ++++ b/net/minecraft/world/level/block/CaveVinesPlantBlock.java +@@ -65,7 +65,7 @@ public class CaveVinesPlantBlock extends GrowingPlantBodyBlock implements CaveVi + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + level.setBlock(pos, state.setValue(BERRIES, true), Block.UPDATE_CLIENTS); + } + } +diff --git a/net/minecraft/world/level/block/CocoaBlock.java b/net/minecraft/world/level/block/CocoaBlock.java +index d4f82e39bb688e43decfb5c8e72fd6db2bfeb571..21f7ad2cbdae399230fe16cebc503d5dc58a7754 100644 +--- a/net/minecraft/world/level/block/CocoaBlock.java ++++ b/net/minecraft/world/level/block/CocoaBlock.java +@@ -114,7 +114,7 @@ public class CocoaBlock extends HorizontalDirectionalBlock implements Bonemealab + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, state.setValue(AGE, state.getValue(AGE) + 1), Block.UPDATE_CLIENTS); // CraftBukkit + } + +diff --git a/net/minecraft/world/level/block/CommandBlock.java b/net/minecraft/world/level/block/CommandBlock.java +index b5a780a929c2b6db91d7f86d7178419f87815944..fac31906af9090541cdd231c1a42725b069049ec 100644 +--- a/net/minecraft/world/level/block/CommandBlock.java ++++ b/net/minecraft/world/level/block/CommandBlock.java +@@ -63,12 +63,12 @@ public class CommandBlock extends BaseEntityBlock implements GameMasterBlock { + protected void neighborChanged(BlockState state, Level level, BlockPos pos, Block neighborBlock, @Nullable Orientation orientation, boolean movedByPiston) { + if (!level.isClientSide()) { + if (level.getBlockEntity(pos) instanceof CommandBlockEntity commandBlockEntity) { +- this.setPoweredAndUpdate(level, pos, commandBlockEntity, level.hasNeighborSignal(pos)); ++ this.setPoweredAndUpdate((io.papermc.paper.util.capture.PaperCapturingWorldLevel) level, pos, commandBlockEntity, level.hasNeighborSignal(pos)); // Paper - block placement capturing + } + } + } + +- private void setPoweredAndUpdate(Level level, BlockPos pos, CommandBlockEntity blockEntity, boolean powered) { ++ private void setPoweredAndUpdate(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, CommandBlockEntity blockEntity, boolean powered) { // Paper - block placement capturing + boolean isPowered = blockEntity.isPowered(); + // CraftBukkit start + org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos); +@@ -76,7 +76,7 @@ public class CommandBlock extends BaseEntityBlock implements GameMasterBlock { + int current = powered ? 15 : 0; + + org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(bukkitBlock, old, current); +- level.getCraftServer().getPluginManager().callEvent(eventRedstone); ++ eventRedstone.callEvent(); + powered = eventRedstone.getNewCurrent() > 0; + // CraftBukkit end + if (powered != isPowered) { +@@ -155,12 +155,12 @@ public class CommandBlock extends BaseEntityBlock implements GameMasterBlock { + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing + if (level.getBlockEntity(pos) instanceof CommandBlockEntity commandBlockEntity) { + BaseCommandBlock commandBlock = commandBlockEntity.getCommandBlock(); +- if (level instanceof ServerLevel serverLevel) { ++ if (true) { // Paper - block placement capturing + if (!stack.has(DataComponents.BLOCK_ENTITY_DATA)) { +- commandBlock.setTrackOutput(serverLevel.getGameRules().get(GameRules.SEND_COMMAND_FEEDBACK)); ++ commandBlock.setTrackOutput(level.getGameRules().get(GameRules.SEND_COMMAND_FEEDBACK)); // Paper - block placement capturing + commandBlockEntity.setAutomatic(this.automatic); + } + +diff --git a/net/minecraft/world/level/block/CrafterBlock.java b/net/minecraft/world/level/block/CrafterBlock.java +index c5fe15844d405a27cdae18c903dd481c25b437de..abb7d98c6608ec1bbed169327114245d85cb1ef8 100644 +--- a/net/minecraft/world/level/block/CrafterBlock.java ++++ b/net/minecraft/world/level/block/CrafterBlock.java +@@ -122,7 +122,7 @@ public class CrafterBlock extends BaseEntityBlock { + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing + if (state.getValue(TRIGGERED)) { + level.scheduleTick(pos, this, 4); + } +diff --git a/net/minecraft/world/level/block/CropBlock.java b/net/minecraft/world/level/block/CropBlock.java +index 1cf40fafd822d976ef4822335c60d8017659916f..e9401fb9b5b8bbd0f11e0425c7a5175d78bcd5d2 100644 +--- a/net/minecraft/world/level/block/CropBlock.java ++++ b/net/minecraft/world/level/block/CropBlock.java +@@ -13,6 +13,7 @@ import net.minecraft.world.item.Items; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.ItemLike; + import net.minecraft.world.level.Level; ++import net.minecraft.world.level.LevelAccessor; + import net.minecraft.world.level.LevelReader; + import net.minecraft.world.level.block.state.BlockBehaviour; + import net.minecraft.world.level.block.state.BlockState; +@@ -104,13 +105,13 @@ public class CropBlock extends VegetationBlock implements BonemealableBlock { + } + } + +- public void growCrops(Level level, BlockPos pos, BlockState state) { ++ public void growCrops(LevelAccessor level, BlockPos pos, BlockState state) { // Paper + int min = Math.min(this.getMaxAge(), this.getAge(state) + this.getBonemealAgeIncrease(level)); + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, this.getStateForAge(min), Block.UPDATE_CLIENTS); // CraftBukkit + } + +- protected int getBonemealAgeIncrease(Level level) { +- return Mth.nextInt(level.random, 2, 5); ++ protected int getBonemealAgeIncrease(LevelAccessor level) { ++ return Mth.nextInt(level.getRandom(), 2, 5); // Paper + } + + protected static float getGrowthSpeed(Block block, BlockGetter level, BlockPos pos) { +@@ -196,7 +197,7 @@ public class CropBlock extends VegetationBlock implements BonemealableBlock { + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + this.growCrops(level, pos, state); + } + +diff --git a/net/minecraft/world/level/block/DiodeBlock.java b/net/minecraft/world/level/block/DiodeBlock.java +index 02ffb5569e2405d86b3a4a695dd17c9372169ff7..f8bee20b29fddd83bb60526eb9ef4ea7e43092d8 100644 +--- a/net/minecraft/world/level/block/DiodeBlock.java ++++ b/net/minecraft/world/level/block/DiodeBlock.java +@@ -164,10 +164,14 @@ public abstract class DiodeBlock extends HorizontalDirectionalBlock { + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { +- if (this.shouldTurnOn(level, pos, state)) { +- level.scheduleTick(pos, this, 1); +- } ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { ++ // Paper start ++ level.addTask((serverLevel) -> { ++ if (this.shouldTurnOn(serverLevel, pos, state)) { ++ level.scheduleTick(pos, this, 1); ++ } ++ }); ++ // Paper end + } + + @Override +diff --git a/net/minecraft/world/level/block/DoorBlock.java b/net/minecraft/world/level/block/DoorBlock.java +index d4239343b15203caafa9da762cbce206cc1d9b33..d662ac3514ad388b32bdcd8dc256b20291a34379 100644 +--- a/net/minecraft/world/level/block/DoorBlock.java ++++ b/net/minecraft/world/level/block/DoorBlock.java +@@ -151,7 +151,7 @@ public class DoorBlock extends Block { + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing + level.setBlock(pos.above(), state.setValue(HALF, DoubleBlockHalf.UPPER), Block.UPDATE_ALL); + } + +diff --git a/net/minecraft/world/level/block/DoublePlantBlock.java b/net/minecraft/world/level/block/DoublePlantBlock.java +index e67f2b0f8e12c99cc5451865487bbec845998619..884693237482ca7b55db16feecc63c589661c78a 100644 +--- a/net/minecraft/world/level/block/DoublePlantBlock.java ++++ b/net/minecraft/world/level/block/DoublePlantBlock.java +@@ -70,7 +70,7 @@ public class DoublePlantBlock extends VegetationBlock { + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing + BlockPos blockPos = pos.above(); + level.setBlock(blockPos, copyWaterloggedFrom(level, blockPos, this.defaultBlockState().setValue(HALF, DoubleBlockHalf.UPPER)), Block.UPDATE_ALL); + } +diff --git a/net/minecraft/world/level/block/DriedGhastBlock.java b/net/minecraft/world/level/block/DriedGhastBlock.java +index e7dbc14cce1d0ec3f410ae7ebbb4dc47301b5176..449083791b18eb4df10a3117f98025940eda0327 100644 +--- a/net/minecraft/world/level/block/DriedGhastBlock.java ++++ b/net/minecraft/world/level/block/DriedGhastBlock.java +@@ -203,7 +203,7 @@ public class DriedGhastBlock extends HorizontalDirectionalBlock implements Simpl + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing + super.setPlacedBy(level, pos, state, placer, stack); + level.playSound( + null, pos, state.getValue(WATERLOGGED) ? SoundEvents.DRIED_GHAST_PLACE_IN_WATER : SoundEvents.DRIED_GHAST_PLACE, SoundSource.BLOCKS, 1.0F, 1.0F +diff --git a/net/minecraft/world/level/block/FireflyBushBlock.java b/net/minecraft/world/level/block/FireflyBushBlock.java +index 635ce3fb2583f6b14355f6137fb6f79dfd959400..ac418646c849d029969ca2d4dbcdc0c55be0a6d6 100644 +--- a/net/minecraft/world/level/block/FireflyBushBlock.java ++++ b/net/minecraft/world/level/block/FireflyBushBlock.java +@@ -58,7 +58,7 @@ public class FireflyBushBlock extends VegetationBlock implements BonemealableBlo + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + BonemealableBlock.findSpreadableNeighbourPos(level, pos, state).ifPresent(blockPos -> level.setBlockAndUpdate(blockPos, this.defaultBlockState())); + } + } +diff --git a/net/minecraft/world/level/block/FlowerBedBlock.java b/net/minecraft/world/level/block/FlowerBedBlock.java +index 2a24e71d2b598792ebaa867a2f74fd79e97cc6a2..662c070bb28d81fab4c105631d8efc8516e4cfe2 100644 +--- a/net/minecraft/world/level/block/FlowerBedBlock.java ++++ b/net/minecraft/world/level/block/FlowerBedBlock.java +@@ -92,12 +92,12 @@ public class FlowerBedBlock extends VegetationBlock implements BonemealableBlock + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + int amountValue = state.getValue(AMOUNT); + if (amountValue < 4) { + level.setBlock(pos, state.setValue(AMOUNT, amountValue + 1), Block.UPDATE_CLIENTS); + } else { +- popResource(level, pos, new ItemStack(this)); ++ level.addTask((serverLevel) -> popResource(serverLevel, pos, new ItemStack(this))); // Paper + } + } + } +diff --git a/net/minecraft/world/level/block/FungusBlock.java b/net/minecraft/world/level/block/FungusBlock.java +index 9711efb088bd0da9168e9bcd0496bd7caddd2974..0e78d131c2b57ba495d2602716f5a735b9ef7324 100644 +--- a/net/minecraft/world/level/block/FungusBlock.java ++++ b/net/minecraft/world/level/block/FungusBlock.java +@@ -71,7 +71,7 @@ public class FungusBlock extends VegetationBlock implements BonemealableBlock { + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + this.getFeature(level) + // CraftBukkit start + .map((value) -> { +diff --git a/net/minecraft/world/level/block/GlowLichenBlock.java b/net/minecraft/world/level/block/GlowLichenBlock.java +index ba39497a6d7160cde961e339be8028ec131b8019..60fcc089f3a576f71f00982397ca5aacc93d3f7e 100644 +--- a/net/minecraft/world/level/block/GlowLichenBlock.java ++++ b/net/minecraft/world/level/block/GlowLichenBlock.java +@@ -39,7 +39,7 @@ public class GlowLichenBlock extends MultifaceSpreadeableBlock implements Boneme + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + this.spreader.spreadFromRandomFaceTowardRandomDirection(state, level, pos, random); + } + +diff --git a/net/minecraft/world/level/block/GrassBlock.java b/net/minecraft/world/level/block/GrassBlock.java +index 368f60ecce691ea161120743150e87b32efc3ca4..3eb4fa3aec3540a09de0bd0e431dd86bcaf7d0fe 100644 +--- a/net/minecraft/world/level/block/GrassBlock.java ++++ b/net/minecraft/world/level/block/GrassBlock.java +@@ -40,7 +40,7 @@ public class GrassBlock extends SpreadingSnowyDirtBlock implements BonemealableB + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + BlockPos blockPos = pos.above(); + BlockState blockState = Blocks.SHORT_GRASS.defaultBlockState(); + Optional> optional = level.registryAccess() +@@ -62,7 +62,7 @@ public class GrassBlock extends SpreadingSnowyDirtBlock implements BonemealableB + if (blockState1.is(blockState.getBlock()) && random.nextInt(10) == 0) { + BonemealableBlock bonemealableBlock = (BonemealableBlock)blockState.getBlock(); + if (bonemealableBlock.isValidBonemealTarget(level, blockPos1, blockState1)) { +- bonemealableBlock.performBonemeal(level, random, blockPos1, blockState1); ++ bonemealableBlock.performBonemeal(player, level, random, blockPos1, blockState1); + } + } + +diff --git a/net/minecraft/world/level/block/GrowingPlantBodyBlock.java b/net/minecraft/world/level/block/GrowingPlantBodyBlock.java +index 314d198617e34f91f72a2952a2a62ce0a3b9147d..7295c11780564b8bdf4aa35cddb56a1cebaf2e9b 100644 +--- a/net/minecraft/world/level/block/GrowingPlantBodyBlock.java ++++ b/net/minecraft/world/level/block/GrowingPlantBodyBlock.java +@@ -74,11 +74,11 @@ public abstract class GrowingPlantBodyBlock extends GrowingPlantBlock implements + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + Optional headPos = this.getHeadPos(level, pos, state.getBlock()); + if (headPos.isPresent()) { + BlockState blockState = level.getBlockState(headPos.get()); +- ((GrowingPlantHeadBlock)blockState.getBlock()).performBonemeal(level, random, headPos.get(), blockState); ++ ((GrowingPlantHeadBlock)blockState.getBlock()).performBonemeal(player, level, random, headPos.get(), blockState); + } + } + +diff --git a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java +index bac7f990282fd7c676c2f8c40d7fd87badb1e284..c09b69777901df20686fcd1329317b395a7d33bb 100644 +--- a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java ++++ b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java +@@ -135,7 +135,7 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + BlockPos blockPos = pos.relative(this.growthDirection); + int min = Math.min(state.getValue(AGE) + 1, 25); + int blocksToGrowWhenBonemealed = this.getBlocksToGrowWhenBonemealed(random); +diff --git a/net/minecraft/world/level/block/HangingMossBlock.java b/net/minecraft/world/level/block/HangingMossBlock.java +index 9675e0ad00a4a26c38a7388dc3615e0c33b4e802..4fd97dd6ed215080091bae7bdc70e08f8091f487 100644 +--- a/net/minecraft/world/level/block/HangingMossBlock.java ++++ b/net/minecraft/world/level/block/HangingMossBlock.java +@@ -124,7 +124,7 @@ public class HangingMossBlock extends Block implements BonemealableBlock { + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + BlockPos blockPos = this.getTip(level, pos).below(); + if (this.canGrowInto(level.getBlockState(blockPos))) { + level.setBlockAndUpdate(blockPos, state.setValue(TIP, true)); +diff --git a/net/minecraft/world/level/block/JukeboxBlock.java b/net/minecraft/world/level/block/JukeboxBlock.java +index a4b824dab307705559a76b954a3e47d30dd65889..4fe64b4b445ddceedff0909c917e85d6bc4e7e75 100644 +--- a/net/minecraft/world/level/block/JukeboxBlock.java ++++ b/net/minecraft/world/level/block/JukeboxBlock.java +@@ -42,7 +42,7 @@ public class JukeboxBlock extends BaseEntityBlock { + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing + super.setPlacedBy(level, pos, state, placer, stack); + TypedEntityData> typedEntityData = stack.get(DataComponents.BLOCK_ENTITY_DATA); + if (typedEntityData != null && typedEntityData.contains("RecordItem")) { +diff --git a/net/minecraft/world/level/block/MangroveLeavesBlock.java b/net/minecraft/world/level/block/MangroveLeavesBlock.java +index 6d8154821cd17f7529a44eeb16a78caa07529436..b79a527c038965cc1faf9a4b326ce1e9c6c467a9 100644 +--- a/net/minecraft/world/level/block/MangroveLeavesBlock.java ++++ b/net/minecraft/world/level/block/MangroveLeavesBlock.java +@@ -40,7 +40,7 @@ public class MangroveLeavesBlock extends TintedParticleLeavesBlock implements Bo + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + level.setBlock(pos.below(), MangrovePropaguleBlock.createNewHangingPropagule(), Block.UPDATE_CLIENTS); + } + +diff --git a/net/minecraft/world/level/block/MangrovePropaguleBlock.java b/net/minecraft/world/level/block/MangrovePropaguleBlock.java +index 2fba8534a636491314c760bc338226f6506f0a9a..78f1be360817712725a68adba51530f4a5157418 100644 +--- a/net/minecraft/world/level/block/MangrovePropaguleBlock.java ++++ b/net/minecraft/world/level/block/MangrovePropaguleBlock.java +@@ -123,11 +123,11 @@ public class MangrovePropaguleBlock extends SaplingBlock implements SimpleWaterl + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + if (isHanging(state) && !isFullyGrown(state)) { + level.setBlock(pos, state.cycle(AGE), Block.UPDATE_CLIENTS); + } else { +- super.performBonemeal(level, random, pos, state); ++ super.performBonemeal(player, level, random, pos, state); // Paper + } + } + +diff --git a/net/minecraft/world/level/block/MossyCarpetBlock.java b/net/minecraft/world/level/block/MossyCarpetBlock.java +index 3762d833f17d596e04845a3f391423f9c14a878b..89de83c625439389daca9488087b291f89702c98 100644 +--- a/net/minecraft/world/level/block/MossyCarpetBlock.java ++++ b/net/minecraft/world/level/block/MossyCarpetBlock.java +@@ -176,7 +176,7 @@ public class MossyCarpetBlock extends Block implements BonemealableBlock { + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing + if (!level.isClientSide()) { + RandomSource random = level.getRandom(); + BlockState blockState = createTopperWithSideChance(level, pos, random::nextBoolean); +@@ -274,7 +274,7 @@ public class MossyCarpetBlock extends Block implements BonemealableBlock { + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + BlockState blockState = createTopperWithSideChance(level, pos, () -> true); + if (!blockState.isAir()) { + level.setBlock(pos.above(), blockState, Block.UPDATE_ALL); +diff --git a/net/minecraft/world/level/block/MushroomBlock.java b/net/minecraft/world/level/block/MushroomBlock.java +index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..10c95b15c1a9a824e93b1eba08bfcd4169645659 100644 +--- a/net/minecraft/world/level/block/MushroomBlock.java ++++ b/net/minecraft/world/level/block/MushroomBlock.java +@@ -87,13 +87,13 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock + return blockState.is(BlockTags.MUSHROOM_GROW_BLOCK) || level.getRawBrightness(pos, 0) < 13 && this.mayPlaceOn(blockState, level, blockPos); + } + +- public boolean growMushroom(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) { ++ public boolean growMushroom(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, RandomSource random) { // Paper + Optional>> optional = level.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).get(this.feature); + if (optional.isEmpty()) { + return false; + } else { + level.removeBlock(pos, false); +- SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit ++// SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit TODO: + if (optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) { + return true; + } else { +@@ -114,7 +114,7 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { +- this.growMushroom(level, pos, state, random); ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ this.growMushroom(player, level, pos, state, random); // Paper + } + } +diff --git a/net/minecraft/world/level/block/NetherrackBlock.java b/net/minecraft/world/level/block/NetherrackBlock.java +index 24e166e399bc542522fc832064401ebb6ba2568e..f3a549712321c94b56d93c6fbba27558010b9f68 100644 +--- a/net/minecraft/world/level/block/NetherrackBlock.java ++++ b/net/minecraft/world/level/block/NetherrackBlock.java +@@ -43,7 +43,7 @@ public class NetherrackBlock extends Block implements BonemealableBlock { + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + boolean flag = false; + boolean flag1 = false; + +diff --git a/net/minecraft/world/level/block/NyliumBlock.java b/net/minecraft/world/level/block/NyliumBlock.java +index 7357bf27bb0890cefd8f8188a7a326a631cd0ff6..0c58d2f81ce1c133ebcd4806b6d3b8d7fb663832 100644 +--- a/net/minecraft/world/level/block/NyliumBlock.java ++++ b/net/minecraft/world/level/block/NyliumBlock.java +@@ -59,7 +59,7 @@ public class NyliumBlock extends Block implements BonemealableBlock { + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + BlockState blockState = level.getBlockState(pos); + BlockPos blockPos = pos.above(); + ChunkGenerator generator = level.getChunkSource().getGenerator(); +@@ -78,7 +78,7 @@ public class NyliumBlock extends Block implements BonemealableBlock { + private void place( + Registry> featureRegistry, + ResourceKey> featureKey, +- ServerLevel level, ++ io.papermc.paper.util.capture.PaperCapturingWorldLevel level, // Paper + ChunkGenerator chunkGenerator, + RandomSource random, + BlockPos pos +diff --git a/net/minecraft/world/level/block/PitcherCropBlock.java b/net/minecraft/world/level/block/PitcherCropBlock.java +index cbaf7ea236e8689793af65f29af3f1860644d152..8c04f40b959d5ddab8170cf5975704a6ec7007c6 100644 +--- a/net/minecraft/world/level/block/PitcherCropBlock.java ++++ b/net/minecraft/world/level/block/PitcherCropBlock.java +@@ -129,7 +129,7 @@ public class PitcherCropBlock extends DoublePlantBlock implements BonemealableBl + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing + } + + @Override +@@ -146,7 +146,7 @@ public class PitcherCropBlock extends DoublePlantBlock implements BonemealableBl + } + } + +- private void grow(ServerLevel level, BlockState state, BlockPos pos, int ageIncrement) { ++ private void grow(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockState state, BlockPos pos, int ageIncrement) { // Paper + int min = Math.min(state.getValue(AGE) + ageIncrement, 4); + if (this.canGrow(level, pos, state, min)) { + BlockState blockState = state.setValue(AGE, min); +@@ -204,7 +204,7 @@ public class PitcherCropBlock extends DoublePlantBlock implements BonemealableBl + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + PitcherCropBlock.PosAndState lowerHalf = this.getLowerHalf(level, pos, state); + if (lowerHalf != null) { + this.grow(level, lowerHalf.state, lowerHalf.pos, 1); +diff --git a/net/minecraft/world/level/block/RootedDirtBlock.java b/net/minecraft/world/level/block/RootedDirtBlock.java +index db6b32016a1ad4264da9d8812e5ea9356d0601df..2cfc2eef9764dc446ec2b465e3a9480eb51554a6 100644 +--- a/net/minecraft/world/level/block/RootedDirtBlock.java ++++ b/net/minecraft/world/level/block/RootedDirtBlock.java +@@ -32,7 +32,7 @@ public class RootedDirtBlock extends Block implements BonemealableBlock { + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, pos.below(), Blocks.HANGING_ROOTS.defaultBlockState(), Block.UPDATE_ALL); // CraftBukkit + } + +diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java +index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..b14f0271fc1817fa79423d1896255cb6f9c372ff 100644 +--- a/net/minecraft/world/level/block/SaplingBlock.java ++++ b/net/minecraft/world/level/block/SaplingBlock.java +@@ -50,38 +50,11 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { + } + } + +- public void advanceTree(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) { ++ public void advanceTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, RandomSource random) { // Paper + if (state.getValue(STAGE) == 0) { + level.setBlock(pos, state.cycle(STAGE), Block.UPDATE_NONE); + } else { +- // CraftBukkit start +- if (level.captureTreeGeneration) { +- this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); +- } else { +- level.captureTreeGeneration = true; +- this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); +- level.captureTreeGeneration = false; +- if (!level.capturedBlockStates.isEmpty()) { +- org.bukkit.TreeType treeType = SaplingBlock.treeType; +- SaplingBlock.treeType = null; +- org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level); +- java.util.List blocks = new java.util.ArrayList<>(level.capturedBlockStates.values()); +- level.capturedBlockStates.clear(); +- org.bukkit.event.world.StructureGrowEvent event = null; +- if (treeType != null) { +- event = new org.bukkit.event.world.StructureGrowEvent(location, treeType, false, null, blocks); +- org.bukkit.Bukkit.getPluginManager().callEvent(event); +- } +- if (event == null || !event.isCancelled()) { +- for (org.bukkit.block.BlockState blockstate : blocks) { +- org.bukkit.craftbukkit.block.CraftBlockState craftBlockState = (org.bukkit.craftbukkit.block.CraftBlockState) blockstate; +- craftBlockState.place(craftBlockState.getFlags()); +- level.checkCapturedTreeStateForObserverNotify(pos, craftBlockState); // Paper - notify observers even if grow failed +- } +- } +- } +- } +- // CraftBukkit end ++ this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random, new java.util.concurrent.atomic.AtomicReference<>()); + } + } + +@@ -96,7 +69,7 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + this.advanceTree(level, pos, state, random); + } + +diff --git a/net/minecraft/world/level/block/SeaPickleBlock.java b/net/minecraft/world/level/block/SeaPickleBlock.java +index f59fb2923bbcdf9bc73747b6aa9199fef4c2319f..bea293510d219cf41f1df667c983339db6e1bff9 100644 +--- a/net/minecraft/world/level/block/SeaPickleBlock.java ++++ b/net/minecraft/world/level/block/SeaPickleBlock.java +@@ -130,7 +130,7 @@ public class SeaPickleBlock extends VegetationBlock implements BonemealableBlock + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + int i = 5; + int i1 = 1; + int i2 = 2; +diff --git a/net/minecraft/world/level/block/SeagrassBlock.java b/net/minecraft/world/level/block/SeagrassBlock.java +index a41270e041ab408dc6ac1207f7b6ec2eaff161d4..7d23a4a28bfa654e1510b020e70f8d3095e78693 100644 +--- a/net/minecraft/world/level/block/SeagrassBlock.java ++++ b/net/minecraft/world/level/block/SeagrassBlock.java +@@ -87,7 +87,7 @@ public class SeagrassBlock extends VegetationBlock implements BonemealableBlock, + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + BlockState blockState = Blocks.TALL_SEAGRASS.defaultBlockState(); + BlockState blockState1 = blockState.setValue(TallSeagrassBlock.HALF, DoubleBlockHalf.UPPER); + BlockPos blockPos = pos.above(); +diff --git a/net/minecraft/world/level/block/ShortDryGrassBlock.java b/net/minecraft/world/level/block/ShortDryGrassBlock.java +index 1df47e0ea401267027721342aaf26639b2622e13..6435e8c2ab68b9bca861134de05db965ef100d4b 100644 +--- a/net/minecraft/world/level/block/ShortDryGrassBlock.java ++++ b/net/minecraft/world/level/block/ShortDryGrassBlock.java +@@ -47,7 +47,7 @@ public class ShortDryGrassBlock extends DryVegetationBlock implements Bonemealab + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + level.setBlockAndUpdate(pos, Blocks.TALL_DRY_GRASS.defaultBlockState()); + } + } +diff --git a/net/minecraft/world/level/block/SmallDripleafBlock.java b/net/minecraft/world/level/block/SmallDripleafBlock.java +index d5821239d33aad9213b9a87d225e942934623857..63a43f7eecd2dcf38dc12c8f6b7bb28ac9941f99 100644 +--- a/net/minecraft/world/level/block/SmallDripleafBlock.java ++++ b/net/minecraft/world/level/block/SmallDripleafBlock.java +@@ -64,7 +64,7 @@ public class SmallDripleafBlock extends DoublePlantBlock implements Bonemealable + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper + if (!level.isClientSide()) { + BlockPos blockPos = pos.above(); + BlockState blockState = DoublePlantBlock.copyWaterloggedFrom( +@@ -124,14 +124,14 @@ public class SmallDripleafBlock extends DoublePlantBlock implements Bonemealable + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + if (state.getValue(DoublePlantBlock.HALF) == DoubleBlockHalf.LOWER) { + BlockPos blockPos = pos.above(); + level.setBlock(blockPos, level.getFluidState(blockPos).createLegacyBlock(), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE); + BigDripleafBlock.placeWithRandomHeight(level, random, pos, state.getValue(FACING)); + } else { + BlockPos blockPos = pos.below(); +- this.performBonemeal(level, random, blockPos, level.getBlockState(blockPos)); ++ this.performBonemeal(player, level, random, blockPos, level.getBlockState(blockPos)); // Paper + } + } + +diff --git a/net/minecraft/world/level/block/StemBlock.java b/net/minecraft/world/level/block/StemBlock.java +index 82ea0ce3895108d477d10e901e756a27a3a9cedc..1498804e90b7a4a0799f8e60c016caf2afbebda8 100644 +--- a/net/minecraft/world/level/block/StemBlock.java ++++ b/net/minecraft/world/level/block/StemBlock.java +@@ -113,12 +113,12 @@ public class StemBlock extends VegetationBlock implements BonemealableBlock { + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { +- int min = Math.min(7, state.getValue(AGE) + Mth.nextInt(level.random, 2, 5)); ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ int min = Math.min(7, state.getValue(AGE) + Mth.nextInt(level.getRandom(), 2, 5)); // Paper + BlockState blockState = state.setValue(AGE, min); + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, blockState, Block.UPDATE_CLIENTS); // CraftBukkit + if (min == 7) { +- blockState.randomTick(level, pos, level.random); ++ level.addTask((serverLevel) -> blockState.randomTick(serverLevel, pos, level.getRandom())); // Paper + } + } + +diff --git a/net/minecraft/world/level/block/StructureBlock.java b/net/minecraft/world/level/block/StructureBlock.java +index 5b19c117d7935c6b0c3887083f63cc293bc495e3..9fcbe1a3d741852674ed8942e10bc0cac574a81d 100644 +--- a/net/minecraft/world/level/block/StructureBlock.java ++++ b/net/minecraft/world/level/block/StructureBlock.java +@@ -50,7 +50,7 @@ public class StructureBlock extends BaseEntityBlock implements GameMasterBlock { + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing + if (!level.isClientSide()) { + if (placer != null) { + BlockEntity blockEntity = level.getBlockEntity(pos); +diff --git a/net/minecraft/world/level/block/SweetBerryBushBlock.java b/net/minecraft/world/level/block/SweetBerryBushBlock.java +index 3d2bd187cb9f83f9369d11b21c484c21ceba0569..abcc4dafd079b454855158e95e08ee0d2e473197 100644 +--- a/net/minecraft/world/level/block/SweetBerryBushBlock.java ++++ b/net/minecraft/world/level/block/SweetBerryBushBlock.java +@@ -160,7 +160,7 @@ public class SweetBerryBushBlock extends VegetationBlock implements Bonemealable + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + int min = Math.min(3, state.getValue(AGE) + 1); + level.setBlock(pos, state.setValue(AGE, min), Block.UPDATE_CLIENTS); + } +diff --git a/net/minecraft/world/level/block/TallDryGrassBlock.java b/net/minecraft/world/level/block/TallDryGrassBlock.java +index f0514cd9df52b3a459ff92812ae5ad9b0df85763..d634457bdb2d376f9dc7328b29a76cc3b3b90555 100644 +--- a/net/minecraft/world/level/block/TallDryGrassBlock.java ++++ b/net/minecraft/world/level/block/TallDryGrassBlock.java +@@ -47,7 +47,7 @@ public class TallDryGrassBlock extends DryVegetationBlock implements Bonemealabl + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + BonemealableBlock.findSpreadableNeighbourPos(level, pos, Blocks.SHORT_DRY_GRASS.defaultBlockState()) + .ifPresent(blockPos -> level.setBlockAndUpdate(blockPos, Blocks.SHORT_DRY_GRASS.defaultBlockState())); + } +diff --git a/net/minecraft/world/level/block/TallFlowerBlock.java b/net/minecraft/world/level/block/TallFlowerBlock.java +index 4be72e53848b13eb68a2149749a88a83a4cb1e5c..9598bf91cb3c41b0e73a990a7d03fc595a530e2f 100644 +--- a/net/minecraft/world/level/block/TallFlowerBlock.java ++++ b/net/minecraft/world/level/block/TallFlowerBlock.java +@@ -33,7 +33,7 @@ public class TallFlowerBlock extends DoublePlantBlock implements BonemealableBlo + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { +- popResource(level, pos, new ItemStack(this)); ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ level.addTask((serverLevel) -> popResource(serverLevel, pos, new ItemStack(this))); + } + } +diff --git a/net/minecraft/world/level/block/TallGrassBlock.java b/net/minecraft/world/level/block/TallGrassBlock.java +index 6c5259371fb8e2e131b2c69f354521b1e902ac4e..ee1602e5ac3c41b716ff5b1dec3a52c8fd480327 100644 +--- a/net/minecraft/world/level/block/TallGrassBlock.java ++++ b/net/minecraft/world/level/block/TallGrassBlock.java +@@ -41,7 +41,7 @@ public class TallGrassBlock extends VegetationBlock implements BonemealableBlock + } + + @Override +- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper + DoublePlantBlock.placeAt(level, getGrownBlock(state).defaultBlockState(), pos, Block.UPDATE_CLIENTS); + } + +diff --git a/net/minecraft/world/level/block/TorchflowerCropBlock.java b/net/minecraft/world/level/block/TorchflowerCropBlock.java +index 18f8c389c33fcdc84a48f44cefab9c31b0a3e9ca..4358ec97e2ca9a7fdff4b6e84a1216ac87e77ebd 100644 +--- a/net/minecraft/world/level/block/TorchflowerCropBlock.java ++++ b/net/minecraft/world/level/block/TorchflowerCropBlock.java +@@ -8,6 +8,7 @@ import net.minecraft.world.item.Items; + import net.minecraft.world.level.BlockGetter; + import net.minecraft.world.level.ItemLike; + import net.minecraft.world.level.Level; ++import net.minecraft.world.level.LevelAccessor; + import net.minecraft.world.level.block.state.BlockBehaviour; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.block.state.StateDefinition; +@@ -70,7 +71,7 @@ public class TorchflowerCropBlock extends CropBlock { + } + + @Override +- protected int getBonemealAgeIncrease(Level level) { ++ protected int getBonemealAgeIncrease(LevelAccessor level) { // Paper + return 1; + } + } +diff --git a/net/minecraft/world/level/block/TripWireHookBlock.java b/net/minecraft/world/level/block/TripWireHookBlock.java +index 292fe41967db74884e5937cb125f1e022ccfb6bd..5ca452ae97cd1ee8dfd42c4f99971a927c95d320 100644 +--- a/net/minecraft/world/level/block/TripWireHookBlock.java ++++ b/net/minecraft/world/level/block/TripWireHookBlock.java +@@ -101,8 +101,8 @@ public class TripWireHookBlock extends Block { + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { +- calculateState(level, pos, state, false, false, -1, null); ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing ++ level.addTask((serverLevel) -> calculateState(serverLevel, pos, state, false, false, -1, null)); // Paper - block placement capturing + } + + public static void calculateState( +diff --git a/net/minecraft/world/level/block/WitherSkullBlock.java b/net/minecraft/world/level/block/WitherSkullBlock.java +index 24215493601ff724032660178c1261f3c40edd61..e63433a4d72c715d48e4f36011ff113d25f1485d 100644 +--- a/net/minecraft/world/level/block/WitherSkullBlock.java ++++ b/net/minecraft/world/level/block/WitherSkullBlock.java +@@ -38,8 +38,8 @@ public class WitherSkullBlock extends SkullBlock { + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { +- checkSpawn(level, pos); ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing ++ level.addTask((serverLevel) -> checkSpawn(serverLevel, pos)); // Paper - block placement capturing + } + + public static void checkSpawn(Level level, BlockPos pos) { +@@ -49,7 +49,6 @@ public class WitherSkullBlock extends SkullBlock { + } + + public static void checkSpawn(Level level, BlockPos pos, SkullBlockEntity blockEntity) { +- if (level.captureBlockStates) return; // CraftBukkit + if (!level.isClientSide()) { + BlockState blockState = blockEntity.getBlockState(); + boolean flag = blockState.is(Blocks.WITHER_SKELETON_SKULL) || blockState.is(Blocks.WITHER_SKELETON_WALL_SKULL); +diff --git a/net/minecraft/world/level/block/WitherWallSkullBlock.java b/net/minecraft/world/level/block/WitherWallSkullBlock.java +index c57cb7b47a01e313a1be5de769c6766ac2787d95..f34731580bc880dcc5d3e96923a7f2b4125ae9cc 100644 +--- a/net/minecraft/world/level/block/WitherWallSkullBlock.java ++++ b/net/minecraft/world/level/block/WitherWallSkullBlock.java +@@ -22,7 +22,7 @@ public class WitherWallSkullBlock extends WallSkullBlock { + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { +- WitherSkullBlock.checkSpawn(level, pos); ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing ++ level.addTask((serverLevel) -> WitherSkullBlock.checkSpawn(serverLevel, pos)); // Paper - block placement capturing + } + } +diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java +index 5471619a0484ece08640e2b3fd26746c351dc3e0..dd8ebb1fca73e2927bb61a9c330a58ec09288c46 100644 +--- a/net/minecraft/world/level/block/grower/TreeGrower.java ++++ b/net/minecraft/world/level/block/grower/TreeGrower.java +@@ -122,7 +122,27 @@ public final class TreeGrower { + return this.secondaryMegaTree.isPresent() && random.nextFloat() < this.secondaryChance ? this.secondaryMegaTree.get() : this.megaTree.orElse(null); + } + +- public boolean growTree(ServerLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random) { ++ // TODO: SUPPORT WRAPPING ++ // Paper start ++// public boolean growTree(ServerLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random) { ++// io.papermc.paper.util.capture.MinecraftCaptureBridge captureTreeGeneration = new io.papermc.paper.util.capture.MinecraftCaptureBridge(level); ++// java.util.concurrent.atomic.AtomicReference treeType = new java.util.concurrent.atomic.AtomicReference<>(); ++// if (this.growTree(captureTreeGeneration, chunkGenerator, pos, state, random, treeType)) { ++// var states = captureTreeGeneration.calculateLatestBlockStates(level); ++// org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level); ++// java.util.List blocks = new java.util.ArrayList<>(states.values()); ++// org.bukkit.event.world.StructureGrowEvent event = new org.bukkit.event.world.StructureGrowEvent(location, treeType.get(), false, null, blocks); ++// ++// if (event.callEvent()) { ++// captureTreeGeneration.applyTasks(); ++// return true; ++// } ++// } ++// ++// return false; ++// } ++ public boolean growTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random, java.util.concurrent.atomic.AtomicReference treeType) { ++ // Paper end + ResourceKey> configuredMegaFeature = this.getConfiguredMegaFeature(random); + if (configuredMegaFeature != null) { + Holder> holder = level.registryAccess() +@@ -130,7 +150,7 @@ public final class TreeGrower { + .get(configuredMegaFeature) + .orElse(null); + if (holder != null) { +- this.setTreeType(holder); // CraftBukkit ++ treeType.set(this.getTreeType(holder)); // Paper + for (int i = 0; i >= -1; i--) { + for (int i1 = 0; i1 >= -1; i1--) { + if (isTwoByTwoSapling(state, level, pos, i, i1)) { +@@ -163,7 +183,7 @@ public final class TreeGrower { + if (holder1 == null) { + return false; + } else { +- this.setTreeType(holder1); // CraftBukkit ++ treeType.set(this.getTreeType(holder1)); // Paper + ConfiguredFeature configuredFeature2 = holder1.value(); + BlockState blockState1 = level.getFluidState(pos).createLegacyBlock(); + level.setBlock(pos, blockState1, Block.UPDATE_NONE); +@@ -200,53 +220,53 @@ public final class TreeGrower { + } + + // CraftBukkit start +- private void setTreeType(Holder> feature) { ++ public org.bukkit.TreeType getTreeType(Holder> feature) { + if (feature.is(TreeFeatures.OAK) || feature.is(TreeFeatures.OAK_BEES_005)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TREE; ++ return org.bukkit.TreeType.TREE; + } else if (feature.is(TreeFeatures.HUGE_RED_MUSHROOM)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.RED_MUSHROOM; ++ return org.bukkit.TreeType.RED_MUSHROOM; + } else if (feature.is(TreeFeatures.HUGE_BROWN_MUSHROOM)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BROWN_MUSHROOM; ++ return org.bukkit.TreeType.BROWN_MUSHROOM; + } else if (feature.is(TreeFeatures.JUNGLE_TREE)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.COCOA_TREE; ++ return org.bukkit.TreeType.COCOA_TREE; + } else if (feature.is(TreeFeatures.JUNGLE_TREE_NO_VINE)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SMALL_JUNGLE; ++ return org.bukkit.TreeType.SMALL_JUNGLE; + } else if (feature.is(TreeFeatures.PINE)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_REDWOOD; ++ return org.bukkit.TreeType.TALL_REDWOOD; + } else if (feature.is(TreeFeatures.SPRUCE)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.REDWOOD; ++ return org.bukkit.TreeType.REDWOOD; + } else if (feature.is(TreeFeatures.ACACIA)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.ACACIA; ++ return org.bukkit.TreeType.ACACIA; + } else if (feature.is(TreeFeatures.BIRCH) || feature.is(TreeFeatures.BIRCH_BEES_005)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIRCH; ++ return org.bukkit.TreeType.BIRCH; + } else if (feature.is(TreeFeatures.SUPER_BIRCH_BEES_0002)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_BIRCH; ++ return org.bukkit.TreeType.TALL_BIRCH; + } else if (feature.is(TreeFeatures.SWAMP_OAK)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SWAMP; ++ return org.bukkit.TreeType.SWAMP; + } else if (feature.is(TreeFeatures.FANCY_OAK) || feature.is(TreeFeatures.FANCY_OAK_BEES_005)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIG_TREE; ++ return org.bukkit.TreeType.BIG_TREE; + } else if (feature.is(TreeFeatures.JUNGLE_BUSH)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE_BUSH; ++ return org.bukkit.TreeType.JUNGLE_BUSH; + } else if (feature.is(TreeFeatures.DARK_OAK)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.DARK_OAK; ++ return org.bukkit.TreeType.DARK_OAK; + } else if (feature.is(TreeFeatures.MEGA_SPRUCE)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_REDWOOD; ++ return org.bukkit.TreeType.MEGA_REDWOOD; + } else if (feature.is(TreeFeatures.MEGA_PINE)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_PINE; ++ return org.bukkit.TreeType.MEGA_PINE; + } else if (feature.is(TreeFeatures.MEGA_JUNGLE_TREE)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE; ++ return org.bukkit.TreeType.JUNGLE; + } else if (feature.is(TreeFeatures.AZALEA_TREE)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.AZALEA; ++ return org.bukkit.TreeType.AZALEA; + } else if (feature.is(TreeFeatures.MANGROVE)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MANGROVE; ++ return org.bukkit.TreeType.MANGROVE; + } else if (feature.is(TreeFeatures.TALL_MANGROVE)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_MANGROVE; ++ return org.bukkit.TreeType.TALL_MANGROVE; + } else if (feature.is(TreeFeatures.CHERRY) || feature.is(TreeFeatures.CHERRY_BEES_005)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.CHERRY; ++ return org.bukkit.TreeType.CHERRY; + } else if (feature.is(TreeFeatures.PALE_OAK) || feature.is(TreeFeatures.PALE_OAK_BONEMEAL)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK; ++ return org.bukkit.TreeType.PALE_OAK; + } else if (feature.is(TreeFeatures.PALE_OAK_CREAKING)) { +- net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; ++ return org.bukkit.TreeType.PALE_OAK_CREAKING; + } else { + throw new IllegalArgumentException("Unknown tree generator " + feature); + } +diff --git a/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/net/minecraft/world/level/block/piston/PistonBaseBlock.java +index 3bbfa079a2a2da3de361352738b6101894acf82c..c743c9fb8a829f57758a0c68139bfdf06e4a299f 100644 +--- a/net/minecraft/world/level/block/piston/PistonBaseBlock.java ++++ b/net/minecraft/world/level/block/piston/PistonBaseBlock.java +@@ -73,9 +73,9 @@ public class PistonBaseBlock extends DirectionalBlock { + } + + @Override +- public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing + if (!level.isClientSide()) { +- this.checkIfExtend(level, pos, state); ++ level.addTask((serverLevel) -> this.checkIfExtend(serverLevel, pos, state)); // Paper - block placement capturing + } + } + +diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java +index 3acc1374a7ef968d88e9f566ce7b812fb8d580af..4c9a8e3efc4621f0aff1f82965a9fa4aa2d7be00 100644 +--- a/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/net/minecraft/world/level/chunk/LevelChunk.java +@@ -421,7 +421,7 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot + if (!section.getBlockState(i, i1, i2).is(block)) { + return null; + } else { +- if (!this.level.isClientSide() && (flags & Block.UPDATE_SKIP_ON_PLACE) == 0 && (!this.level.captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) { // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled. ++ if (!this.level.isClientSide() && (flags & Block.UPDATE_SKIP_ON_PLACE) == 0) { + state.onPlace(this.level, pos, blockState, flag1); + } + +@@ -472,12 +472,7 @@ public class LevelChunk extends ChunkAccess implements DebugValueSource, ca.spot + } + + public @Nullable BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType) { +- // CraftBukkit start +- BlockEntity blockEntity = this.level.capturedTileEntities.get(pos); +- if (blockEntity == null) { +- blockEntity = this.blockEntities.get(pos); +- } +- // CraftBukkit end ++ BlockEntity blockEntity = this.blockEntities.get(pos); + if (blockEntity == null) { + CompoundTag compoundTag = this.pendingBlockEntities.remove(pos); + if (compoundTag != null) { +diff --git a/net/minecraft/world/level/levelgen/feature/treedecorators/BeehiveDecorator.java b/net/minecraft/world/level/levelgen/feature/treedecorators/BeehiveDecorator.java +index aca28c507cb642ae10c70f8e8393db10c7bf6165..290b6f3f284e3fca03e0106d0f067e55fc73c2ca 100644 +--- a/net/minecraft/world/level/levelgen/feature/treedecorators/BeehiveDecorator.java ++++ b/net/minecraft/world/level/levelgen/feature/treedecorators/BeehiveDecorator.java +@@ -41,7 +41,7 @@ public class BeehiveDecorator extends TreeDecorator { + List list1 = context.logs(); + if (!list1.isEmpty()) { + RandomSource randomSource = context.random(); +- if (!(randomSource.nextFloat() >= this.probability)) { ++ if (!(randomSource.nextFloat() >= 1)) { + int i = !list.isEmpty() + ? Math.max(list.getFirst().getY() - 1, list1.getFirst().getY() + 1) + : Math.min(list1.getFirst().getY() + 1 + randomSource.nextInt(3), list1.getLast().getY()); diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/BlockPlacementPredictor.java b/paper-server/src/main/java/io/papermc/paper/util/capture/BlockPlacementPredictor.java new file mode 100644 index 000000000000..9106ca7c15a4 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/BlockPlacementPredictor.java @@ -0,0 +1,35 @@ +package io.papermc.paper.util.capture; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.Optional; + +public interface BlockPlacementPredictor { + + Optional getLatestBlockAt(BlockPos pos); + + Optional getLatestBlockAtIfLoaded(BlockPos pos); + + Optional getLatestTileAt(BlockPos pos); + + + record BlockEntityPlacement(boolean removed, BlockEntity blockEntity) { + + public static final Optional ABSENT = Optional.of(new BlockEntityPlacement(false, null)); + + public BlockEntity res() { + return this.removed ? null : this.blockEntity; + } + } + + record LoadedBlockState(boolean present, BlockState state) { + + public static final Optional UNLOADED = Optional.of(new LoadedBlockState(false, null)); + + public BlockState res() { + return this.present ? state : null; + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java new file mode 100644 index 000000000000..e922290a2a0e --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java @@ -0,0 +1,174 @@ +package io.papermc.paper.util.capture; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import org.bukkit.Location; +import org.bukkit.craftbukkit.block.CraftBlockStates; +import org.bukkit.craftbukkit.util.CraftLocation; +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; + +import static net.minecraft.world.level.block.Block.UPDATE_NONE; + +/* +The premise of this storage is to essentially "mock" block placement in the game. +This should attempt to mirror block placement the best it can, in order to provide a read solution of possibly what blocks +are going to be placed in the world. + +The reason why we use a custom capture map rather than just storing the blocks placed in the world is to ensure +that blocks are placed in the correct order, and that the vanilla block chain can occur, which includes physics updates. + */ +public final class CaptureRecordMap { + + private final Map> recordsByPos = new HashMap<>(); + private final ArrayDeque orderedRecords = new ArrayDeque<>(); + + interface BlockPlacement { + void applyApiPatch(ServerLevel serverLevel); + + BlockPos pos(); + } + + + public record BlockEntityRecord(boolean remove, @Nullable BlockEntity add, BlockPos pos) implements BlockPlacement { + + // If we have a block entity that we change, apply to the server level. + @Override + public void applyApiPatch(final ServerLevel level) { + if (this.remove) { + level.removeBlockEntity(this.pos); + return; + } + if (this.add != null) { + level.setBlockEntity(this.add); + } + } + } + + public record BlockStateRecord(BlockState blockState, int flags, BlockPos pos) implements BlockPlacement { + + // If we have a block state that we change, insert in without updating anything else about the world. + @Override + public void applyApiPatch(final ServerLevel level) { + level.setBlock(this.pos, this.blockState, UPDATE_NONE | net.minecraft.world.level.block.Block.UPDATE_SKIP_ON_PLACE); + } + } + + public void setLatestBlockStateAt(final BlockPos pos, final BlockState state, final int flags) { + this.add(new BlockStateRecord(state, flags, pos)); + } + + public void setLatestBlockEntityAt(final BlockPos pos, final boolean remove, @Nullable final BlockEntity add) { + this.add(new BlockEntityRecord(remove, add, pos)); + } + + private void add(final BlockPlacement record) { + this.recordsByPos.computeIfAbsent(record.pos(), k -> new ArrayDeque<>()).addLast(record); + this.orderedRecords.addLast(record); + } + + public boolean isEmpty() { + return this.orderedRecords.isEmpty(); + } + + public @Nullable BlockState getLatestBlockStateAt(final BlockPos pos) { + final Deque dq = this.recordsByPos.get(pos); + if (dq == null) return null; + + for (BlockPlacement record : this.of(dq.descendingIterator())) { + if (record instanceof BlockStateRecord bsr) { + return bsr.blockState(); + } + } + + return null; + } + + // Null indicates that its not present, no override + // Optional empty indicates its being removed + public @Nullable Optional<@Nullable BlockEntity> getLatestBlockEntityAt(final BlockPos pos) { + final Deque dq = this.recordsByPos.get(pos); + if (dq == null) return null; + + for (BlockPlacement record : this.of(dq.descendingIterator())) { + if (record instanceof BlockEntityRecord ber) { + return ber.remove() ? Optional.empty() : Optional.of(ber.add()); + } + } + + return null; + } + + public void applyBlockEntities(ServerLevel parent) { + this.recordsByPos.keySet().forEach((pos) -> { + var res = getLatestBlockEntityAt(pos); + if (res != null && res.isPresent()) { + parent.setBlockEntity(res.get()); + } + }); + } + + public void applyApiPatch(final ServerLevel level) { + for (final BlockPlacement record : this.orderedRecords) { + record.applyApiPatch(level); + } + } + + // TODO: Clean this up + public Map calculateLatestBlockStates(final ServerLevel level) { + final Map out = new HashMap<>(); + + for (final Map.Entry> entry : this.recordsByPos.entrySet()) { + final BlockPos pos = entry.getKey(); + final ArrayDeque dq = entry.getValue(); + + BlockState latestState = null; + BlockEntity latestBe = null; + boolean beKnown = false; + + final Iterator it = dq.descendingIterator(); + while (it.hasNext() && (latestState == null || !beKnown)) { + final BlockPlacement r = it.next(); + + if (latestState == null && r instanceof BlockStateRecord bsr) { + latestState = bsr.blockState(); + } + + if (!beKnown && r instanceof BlockEntityRecord ber) { + beKnown = true; + latestBe = ber.remove() ? null : ber.add(); + } + } + + if (latestState == null) { + continue; + } + + if (!beKnown) { + latestBe = level.getBlockEntity(pos); + } + + out.put(CraftLocation.toBukkit(pos), CraftBlockStates.getBlockState(level.getWorld(), CraftLocation.toBlockPosition(CraftLocation.toBukkit(pos)), latestState, latestBe)); + } + + return out; + } + + private Iterable of(Iterator of) { + return new Iterable<>() { + @Override + public @NotNull Iterator iterator() { + return of; + } + }; + } +} \ No newline at end of file diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/LayeredBlockPlacementPredictor.java b/paper-server/src/main/java/io/papermc/paper/util/capture/LayeredBlockPlacementPredictor.java new file mode 100644 index 000000000000..332c0f5ec641 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/LayeredBlockPlacementPredictor.java @@ -0,0 +1,50 @@ +package io.papermc.paper.util.capture; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.state.BlockState; +import org.jspecify.annotations.Nullable; + +import java.util.Optional; + +public record LayeredBlockPlacementPredictor( + BlockPlacementPredictor... predictors +) implements BlockPlacementPredictor { + @Override + public Optional getLatestBlockAt(BlockPos pos) { + for (BlockPlacementPredictor predictor : this.predictors) { + Optional state = predictor.getLatestBlockAt(pos); + if (state.isPresent()) { + return state; + } + } + + + return Optional.empty(); + } + + @Override + public Optional getLatestBlockAtIfLoaded(BlockPos pos) { + for (BlockPlacementPredictor predictor : this.predictors) { + Optional state = predictor.getLatestBlockAtIfLoaded(pos); + if (state.isPresent()) { + return state; + } + } + + + return Optional.empty(); + } + + @Override + public Optional<@Nullable BlockEntityPlacement> getLatestTileAt(BlockPos pos) { + for (BlockPlacementPredictor predictor : this.predictors) { + Optional state = predictor.getLatestTileAt(pos); + if (state.isPresent()) { + return state; + } + } + + + return Optional.empty(); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java b/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java new file mode 100644 index 000000000000..a83d0b2c649f --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java @@ -0,0 +1,35 @@ +package io.papermc.paper.util.capture; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.Optional; + +public record LiveBlockPlacementLayer(ServerLevel level) implements BlockPlacementPredictor{ + @Override + public Optional getLatestBlockAt(BlockPos pos) { + return Optional.of(this.level.getBlockState(pos)); + } + + @Override + public Optional getLatestBlockAtIfLoaded(BlockPos pos) { + BlockState state = this.level.getBlockStateIfLoaded(pos); + if (state == null) { + return LoadedBlockState.UNLOADED; + } + + return Optional.of(new LoadedBlockState(true, state)); + } + + @Override + public Optional getLatestTileAt(BlockPos pos) { + BlockEntity blockEntity = this.level.getBlockEntity(pos); + if (blockEntity == null) { + return BlockEntityPlacement.ABSENT; + } + + return Optional.of(new BlockEntityPlacement(false, blockEntity)); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java new file mode 100644 index 000000000000..a4fd9fc6b839 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java @@ -0,0 +1,477 @@ +package io.papermc.paper.util.capture; + +import io.papermc.paper.configuration.WorldConfiguration; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Holder; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; +import net.minecraft.util.RandomSource; +import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.attribute.EnvironmentAttributeReader; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeManager; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.border.WorldBorder; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.chunk.ChunkSource; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.entity.EntityTypeTest; +import net.minecraft.world.level.gameevent.GameEvent; +import net.minecraft.world.level.gamerules.GameRules; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.lighting.LevelLightEngine; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.storage.LevelData; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.ticks.LevelTickAccess; +import net.minecraft.world.ticks.ScheduledTick; +import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public class MinecraftCaptureBridge implements PaperCapturingWorldLevel { + + private final ServerLevel parent; + + private BlockPlacementPredictor activeReadLayer; + private BlockPlacementPredictor predictiveReadLayer; + + private SimpleBlockPlacementPredictor writeLayer; + + + private SimpleBlockPlacementPredictor legacyStorage; + private boolean isLegacyPredicting = false; + + private final List queuedTasks = new ArrayList<>(); + + private CapturingTickAccess blocks; + private CapturingTickAccess liquids; + + private Consumer sink = queuedTasks::add; + + + public MinecraftCaptureBridge(ServerLevel parent) { + this.parent = parent; + + LiveBlockPlacementLayer liveBlockPlacementLayer = new LiveBlockPlacementLayer(this.parent); + + SimpleBlockPlacementPredictor blockPlacementStorage = new SimpleBlockPlacementPredictor(liveBlockPlacementLayer); + this.legacyStorage = new SimpleBlockPlacementPredictor(liveBlockPlacementLayer); + + + this.writeLayer = blockPlacementStorage; + + this.predictiveReadLayer = new LayeredBlockPlacementPredictor( + new LegacyLayer(this.legacyStorage, () -> this.isLegacyPredicting), + blockPlacementStorage + ); + + this.activeReadLayer = new LayeredBlockPlacementPredictor( + this.predictiveReadLayer, + liveBlockPlacementLayer + ); + + this.blocks = new CapturingTickAccess<>(parent.getBlockTicks()); + this.liquids = new CapturingTickAccess<>(parent.getFluidTicks()); + } + + @Override + public long getSeed() { + return this.parent.getSeed(); + } + + @Override + public ServerLevel getLevel() { + return this.parent; + } + + @Override + public DifficultyInstance getCurrentDifficultyAt(BlockPos pos) { + return this.parent.getCurrentDifficultyAt(pos); + } + + @Override + public long nextSubTickCount() { + return this.parent.nextSubTickCount(); + } + + @Override + public LevelData getLevelData() { + return this.parent.getLevelData(); + } + + @Override + public @Nullable MinecraftServer getServer() { + return this.parent.getServer(); + } + + @Override + public ServerChunkCache getChunkSource() { + return this.parent.getChunkSource(); + } + + @Override + public boolean setBlockAndUpdate(BlockPos blockPos, BlockState blockState) { + return this.setBlock(blockPos, blockState, Block.UPDATE_ALL); + } + + @Override + public WorldConfiguration paperConfig() { + return this.parent.paperConfig(); + } + + @Override + public ChunkGenerator getGenerator() { + return this.parent.getGenerator(); + } + + @Override + public RandomSource getRandom() { + // TODO: Support rolling this back? + return this.parent.getRandom(); + } + + @Override + public void playSound(@Nullable Entity entity, BlockPos pos, SoundEvent sound, SoundSource source, float volume, float pitch) { + this.addTask((serverLevel) -> serverLevel.playSound(entity, pos, sound, source, volume, pitch)); + } + + @Override + public void addParticle(ParticleOptions options, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) { + this.addTask((serverLevel) -> serverLevel.addParticle(options, x, y, z, xSpeed, ySpeed, zSpeed)); + } + + @Override + public void levelEvent(@Nullable Entity entity, int type, BlockPos pos, int data) { + this.addTask((serverLevel) -> serverLevel.levelEvent(entity, type, pos, data)); + } + + @Override + public void gameEvent(Holder gameEvent, Vec3 pos, GameEvent.Context context) { + this.sink.accept(() -> this.parent.gameEvent(gameEvent, pos, context)); + } + + @Override + public float getShade(Direction direction, boolean shade) { + return this.parent.getShade(direction, shade); + } + + @Override + public LevelLightEngine getLightEngine() { + return this.parent.getLightEngine(); + } + + @Override + public WorldBorder getWorldBorder() { + return this.parent.getWorldBorder(); + } + + @Override + public @Nullable BlockEntity getBlockEntity(BlockPos pos) { + return this.activeReadLayer.getLatestTileAt(pos) + .map(BlockPlacementPredictor.BlockEntityPlacement::blockEntity) + .orElse(null); + } + + @Override + public BlockState getBlockState(BlockPos pos) { + return this.activeReadLayer.getLatestBlockAt(pos).orElseThrow(); // Should not ever be null, parent should pass value + } + + @Override + public @Nullable BlockState getBlockStateIfLoaded(BlockPos pos) { + return this.activeReadLayer.getLatestBlockAtIfLoaded(pos).map(BlockPlacementPredictor.LoadedBlockState::state).orElse(null); + } + + @Override + public @Nullable FluidState getFluidIfLoaded(BlockPos pos) { + return this.getBlockStateIfLoaded(pos).getFluidState(); + } + + @Override + public FluidState getFluidState(BlockPos pos) { + return this.getBlockState(pos).getFluidState(); + } + + @Override + public List getEntities(@Nullable Entity entity, AABB area, Predicate predicate) { + return this.parent.getEntities(entity, area, predicate); + } + + @Override + public List getEntities(EntityTypeTest entityTypeTest, AABB bounds, Predicate predicate) { + return this.parent.getEntities(entityTypeTest, bounds, predicate); + } + + @Override + public List players() { + return this.parent.players(); + } + + @Override + public @Nullable ChunkAccess getChunk(int x, int z, ChunkStatus chunkStatus, boolean requireChunk) { + return this.parent.getChunk(x, z, chunkStatus, requireChunk); + } + + @Override + public @Nullable ChunkAccess getChunkIfLoadedImmediately(int x, int z) { + return this.parent.getChunkIfLoadedImmediately(x, z); + } + + @Override + public int getHeight(Heightmap.Types heightmapType, int x, int z) { + return this.parent.getHeight(heightmapType, x, z); // TODO? + } + + @Override + public int getSkyDarken() { + return this.parent.getSkyDarken(); + } + + @Override + public BiomeManager getBiomeManager() { + return this.parent.getBiomeManager(); + } + + @Override + public Holder getUncachedNoiseBiome(int x, int y, int z) { + return this.parent.getUncachedNoiseBiome(x, y, z); + } + + @Override + public boolean isClientSide() { + return this.parent.isClientSide(); + } + + @Override + public int getSeaLevel() { + return this.parent.getSeaLevel(); + } + + @Override + public DimensionType dimensionType() { + return this.parent.dimensionType(); + } + + @Override + public RegistryAccess registryAccess() { + return this.parent.registryAccess(); + } + + @Override + public FeatureFlagSet enabledFeatures() { + return this.parent.enabledFeatures(); + } + + @Override + public EnvironmentAttributeReader environmentAttributes() { + return this.parent.environmentAttributes(); + } + + @Override + public boolean isStateAtPosition(BlockPos pos, Predicate state) { + return state.test(this.getBlockState(pos)); + } + + @Override + public boolean isFluidAtPosition(BlockPos pos, Predicate predicate) { + return predicate.test(this.getFluidState(pos)); + } + + public boolean silentSet(BlockPos pos, BlockState state, int flags) { + return this.writeLayer.setBlockState(pos, state, flags); + } + + @Override + public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft) { + BlockPos copy = pos.immutable(); + this.addTask((serverLevel) -> parent.setBlock(copy, state, flags, recursionLeft)); + + return this.writeLayer.setBlockState(copy, state, flags); + } + + @Override + public boolean removeBlock(BlockPos pos, boolean movedByPiston) { + BlockPos copy = pos.immutable(); + this.addTask((serverLevel) -> serverLevel.removeBlock(copy, movedByPiston)); + + FluidState fluidState = this.getFluidState(copy); + return this.silentSet(copy, fluidState.createLegacyBlock(), Block.UPDATE_ALL | (movedByPiston ? Block.UPDATE_MOVE_BY_PISTON : 0)); + } + + @Override + public boolean destroyBlock(BlockPos pos, boolean dropBlock, @Nullable Entity entity, int recursionLeft) { + BlockPos copy = pos.immutable(); + this.addTask((serverLevel) -> serverLevel.destroyBlock(copy, dropBlock, entity, recursionLeft)); + + BlockState blockState = this.getBlockState(copy); + if (blockState.isAir()) { + return false; + } else { + FluidState fluidState = this.getFluidState(copy); + + return this.silentSet(copy, fluidState.createLegacyBlock(), Block.UPDATE_ALL); + } + } + + @Override + public void sendBlockUpdated(BlockPos pos, BlockState state, BlockState blockState1, int updateClients) { + BlockPos copy = pos.immutable(); + this.addTask((serverLevel) -> serverLevel.sendBlockUpdated(copy, state, blockState1, updateClients)); + } + + @Override + public void setBlockEntity(BlockEntity blockEntity) { + this.writeLayer.getRecordMap().setLatestBlockEntityAt(blockEntity.getBlockPos().immutable(), false, blockEntity); + } + + @Override + public boolean setBlockSilent(BlockPos pos, BlockState state, int flags, int recursionLeft) { + BlockPos copy = pos.immutable(); + return this.silentSet(copy, state, flags); + } + + @Override + public LevelTickAccess getBlockTicks() { + return this.blocks; + } + + @Override + public LevelTickAccess getFluidTicks() { + return this.liquids; + } + + @Override + public GameRules getGameRules() { + return this.parent.getGameRules(); + } + + @Override + public void addTask(Consumer levelConsumer) { + this.sink.accept(() -> levelConsumer.accept(parent)); + } + + public net.minecraft.world.level.block.state.BlockState getLatestBlockState(final BlockPos pos) { + return this.predictiveReadLayer.getLatestBlockAt(pos).orElse(null); + } + + public @Nullable Optional<@Nullable BlockEntity> getLatestBlockEntity(final BlockPos pos) { + Optional placement = this.predictiveReadLayer.getLatestTileAt(pos); + if (placement.isEmpty()) { + return null; + } + + return placement.map(BlockPlacementPredictor.BlockEntityPlacement::blockEntity); + } + + public Map calculateLatestBlockStates(ServerLevel level) { + return this.writeLayer.getRecordMap().calculateLatestBlockStates(level); + } + + public void applyTasks() { + this.sink = Runnable::run; + for (Runnable runnable : this.queuedTasks) { + runnable.run(); + } + this.blocks.apply(); + this.liquids.apply(); + + // If we have changes that the plugin applied ontop of the already existing changes, we know that we can apply them. + // So, do that! + if (!this.legacyStorage.isEmpty()) { + this.legacyStorage.getRecordMap().applyApiPatch(this.parent); + } + + + // Apply block entities, those may have been written in our estimation. So just apply them. + // I dont really like this, as I in general dont really want to apply things from the prediction layer. + // But, these may be mutated by anything. + if (!this.writeLayer.getRecordMap().isEmpty()) { + this.writeLayer.getRecordMap().applyBlockEntities(this.parent); + } + } + + public void activateLegacyCapture() { + this.writeLayer = this.legacyStorage; + this.isLegacyPredicting = true; + } + + public static class CapturingTickAccess implements LevelTickAccess<@NotNull T> { + + private final LevelTickAccess<@NotNull T> wrapped; + private final Set scheduled = new HashSet<>(); + private final List> ticks = new ArrayList<>(); + + public CapturingTickAccess(LevelTickAccess<@NotNull T> wrapped) { + this.wrapped = wrapped; + } + + @Override + public boolean willTickThisTick(@NotNull BlockPos pos, T type) { + return this.wrapped.willTickThisTick(pos, type); + } + + @Override + public void schedule(ScheduledTick<@NotNull T> tick) { + this.scheduled.add(tick.pos()); + this.ticks.add(tick); + } + + @Override + public boolean hasScheduledTick(@NotNull BlockPos pos, T type) { + return this.wrapped.hasScheduledTick(pos, type) || this.scheduled.contains(pos); + } + + @Override + public int count() { + return this.wrapped.count() + this.scheduled.size(); + } + + public void apply() { + this.ticks.forEach(this.wrapped::schedule); + } + } + + public record LegacyLayer(BlockPlacementPredictor predictor, BooleanSupplier cond) implements BlockPlacementPredictor { + + @Override + public Optional getLatestBlockAt(BlockPos pos) { + return cond.getAsBoolean() ? this.predictor.getLatestBlockAt(pos) : Optional.empty(); + } + + @Override + public Optional getLatestBlockAtIfLoaded(BlockPos pos) { + return cond.getAsBoolean() ? this.predictor.getLatestBlockAtIfLoaded(pos) : Optional.empty(); + } + + @Override + public Optional getLatestTileAt(BlockPos pos) { + return cond.getAsBoolean() ? this.predictor.getLatestTileAt(pos) : Optional.empty(); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java b/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java new file mode 100644 index 000000000000..92271e482873 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java @@ -0,0 +1,32 @@ +package io.papermc.paper.util.capture; + +import io.papermc.paper.configuration.WorldConfiguration; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.gamerules.GameRules; + +public interface PaperCapturingWorldLevel extends WorldGenLevel { + + GameRules getGameRules(); + + void addTask(java.util.function.Consumer levelConsumer); + + void sendBlockUpdated(BlockPos pos, BlockState state, BlockState blockState1, int updateClients); + + void setBlockEntity(BlockEntity blockEntity); + + boolean setBlockSilent(BlockPos pos, BlockState state, int flags, int recursionLeft); + + ServerChunkCache getChunkSource(); + + boolean setBlockAndUpdate(BlockPos blockPos, BlockState blockState); + + WorldConfiguration paperConfig(); + + ChunkGenerator getGenerator(); +} + diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java b/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java new file mode 100644 index 000000000000..48949bddc3a2 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java @@ -0,0 +1,56 @@ +package io.papermc.paper.util.capture; + +import io.papermc.paper.configuration.WorldConfiguration; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.gamerules.GameRules; + +import java.util.function.Consumer; + +public interface ServerLevelPaperCapturingWorldLevel extends PaperCapturingWorldLevel { + + ServerLevel handle(); + + @Override + default GameRules getGameRules() { + return this.handle().getGameRules(); + } + + @Override + default void addTask(Consumer levelConsumer) { + levelConsumer.accept(this.handle()); + } + + @Override + default void sendBlockUpdated(BlockPos pos, BlockState state, BlockState blockState1, int updateClients) { + this.handle().sendBlockUpdated(pos, state, blockState1, updateClients); + } + + @Override + default void setBlockEntity(BlockEntity blockEntity) { + this.handle().setBlockEntity(blockEntity); + } + + @Override + default boolean setBlockSilent(BlockPos pos, BlockState state, int flags, int recursionLeft) { + return this.handle().setBlock(pos, state, flags, recursionLeft); + } + + @Override + default boolean setBlockAndUpdate(BlockPos blockPos, BlockState blockState) { + return this.handle().setBlockAndUpdate(blockPos, blockState); + } + + @Override + default WorldConfiguration paperConfig() { + return this.handle().paperConfig(); + } + + @Override + default ChunkGenerator getGenerator() { + return this.handle().getGenerator(); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java new file mode 100644 index 000000000000..2c94e7fdd312 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java @@ -0,0 +1,69 @@ +package io.papermc.paper.util.capture; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.entity.BlockEntity; +import org.bukkit.Location; +import org.bukkit.block.BlockState; +import org.jspecify.annotations.Nullable; + +import java.util.Map; +import java.util.Optional; + +public class SimpleBlockCapture implements AutoCloseable { + + private final MinecraftCaptureBridge capturingWorldLevel; + private final ServerLevel level; + + private boolean openLegacyCapturing = false; + + public SimpleBlockCapture(WorldCapturer blockCapturer, ServerLevel world) { + this.capturingWorldLevel = new MinecraftCaptureBridge(world); + this.level = world; + } + + public PaperCapturingWorldLevel capturingWorldLevel() { + return this.capturingWorldLevel; + } + + public boolean isCapturing() { + return true; + } + + public Map getCapturedBlockStates() { + return this.capturingWorldLevel.calculateLatestBlockStates(level); + } + + public net.minecraft.world.level.block.state.BlockState getCaptureBlockState(final BlockPos pos) { + return this.capturingWorldLevel.getLatestBlockState(pos); + } + + public @Nullable Optional<@Nullable BlockEntity> getCaptureBlockEntity(final BlockPos pos) { + return this.capturingWorldLevel.getLatestBlockEntity(pos); + } + + + public void openLegacySupport() { + this.openLegacyCapturing = true; + this.capturingWorldLevel.activateLegacyCapture(); + + } + + public boolean isViewingCaptureState() { + return openLegacyCapturing; + } + + public void finalizePlacement() { + this.level.capturer.releaseCapture(); + this.capturingWorldLevel.applyTasks(); + } + + @Override + public void close() { + this.level.capturer.releaseCapture(); + } + + public net.minecraft.world.level.block.state.BlockState getCaptureBlockStateIfLoaded(BlockPos pos) { + return this.capturingWorldLevel.getBlockStateIfLoaded(pos); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java new file mode 100644 index 000000000000..36225ab83257 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java @@ -0,0 +1,121 @@ +package io.papermc.paper.util.capture; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.Optional; + +// Block state +class SimpleBlockPlacementPredictor implements BlockPlacementPredictor { + + private final CaptureRecordMap guesstimationMap = new CaptureRecordMap(); + + private final BlockPlacementPredictor base; + + SimpleBlockPlacementPredictor(BlockPlacementPredictor base) { + this.base = base; + } + + public boolean setBlockState(BlockPos pos, BlockState state, int flags) { + BlockState blockState = this.base.getLatestBlockAt(pos).orElse(Blocks.AIR.defaultBlockState()); + // Dont do any processing if the same + if (blockState == state) { + return false; + } else { + Block block = state.getBlock(); + + this.setLatestBlockAt(pos, state, flags); + + // TODO: local heightmaps? +// this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING).update(i, y, i2, state); +// this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES).update(i, y, i2, state); +// this.heightmaps.get(Heightmap.Types.OCEAN_FLOOR).update(i, y, i2, state); +// this.heightmaps.get(Heightmap.Types.WORLD_SURFACE).update(i, y, i2, state); + + // LIGHT ENGINE CALCULATIONS + + boolean differentState = !blockState.is(block); + if (differentState && blockState.hasBlockEntity() && !state.shouldChangedStateKeepBlockEntity(blockState)) { + this.removeBlockEntity(pos); + } + + +// if ((differentState || block instanceof BaseRailBlock) && ((flags & Block.UPDATE_NEIGHBORS) != 0 || updateMoveByPiston)) { +// BlockState finalBlockState = blockState; +// this.capturingWorldLevel.addTask((serverLevel) -> { +// finalBlockState.affectNeighborsAfterRemoval(serverLevel, pos, updateMoveByPiston); +// }); +// } + + if (state.hasBlockEntity()) { + BlockEntity blockEntity = this.getLatestTileAt(pos).map(BlockEntityPlacement::blockEntity).orElse(null); + if (blockEntity != null && !blockEntity.isValidBlockState(state)) { + blockEntity = null; + } + + if (blockEntity == null) { + blockEntity = ((EntityBlock)block).newBlockEntity(pos, state); + if (blockEntity != null) { + this.addAndRegisterBlockEntity(blockEntity); + } + } else { + blockEntity.setBlockState(state); + } + } + + } + + return true; + } + + + private void addAndRegisterBlockEntity(BlockEntity blockEntity) { + this.guesstimationMap.setLatestBlockEntityAt(blockEntity.getBlockPos(), false, blockEntity); + } + + private void removeBlockEntity(BlockPos pos) { + this.guesstimationMap.setLatestBlockEntityAt(pos, true, null); + } + + public boolean isEmpty() { + return this.guesstimationMap.isEmpty(); + } + + @Override + public Optional getLatestTileAt(BlockPos pos) { + var value = this.guesstimationMap.getLatestBlockEntityAt(pos); + if (value == null) { + return Optional.empty(); + } else { + return value + .map(block -> new BlockEntityPlacement(false, block)) + .or(() -> BlockEntityPlacement.ABSENT); + } + } + + @Override + public Optional getLatestBlockAt(BlockPos pos) { + return Optional.ofNullable(this.guesstimationMap.getLatestBlockStateAt(pos)); + } + + @Override + public Optional getLatestBlockAtIfLoaded(BlockPos pos) { + return Optional.ofNullable(this.guesstimationMap.getLatestBlockStateAt(pos)) + .map((state) -> new LoadedBlockState(true, state)); + } + + + + public void setLatestBlockAt(BlockPos pos, BlockState data, int flags) { + this.guesstimationMap.setLatestBlockStateAt(pos, data, flags); + } + + public CaptureRecordMap getRecordMap() { + return guesstimationMap; + } + +} \ No newline at end of file diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java b/paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java new file mode 100644 index 000000000000..acef54731d3d --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java @@ -0,0 +1,36 @@ +package io.papermc.paper.util.capture; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; + +import java.util.function.Consumer; + +// TODO: Cleanup this state, because its held on the server world. I had proper state handling but threw it away at some point +public class WorldCapturer { + + public final ServerLevel world; + + private SimpleBlockCapture capture; + + public WorldCapturer(Level world) { + this.world = (ServerLevel) world; + } + + public SimpleBlockCapture createCaptureSession() { + this.capture = new SimpleBlockCapture(this, world); + + return this.capture; + } + + public void releaseCapture() { + this.capture = null; + } + + public SimpleBlockCapture getCapture() { + return capture; + } + + public boolean isCapturing() { + return this.capture != null; + } +} \ No newline at end of file diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index ebc65e3338c6..aaaa1126fa36 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -738,26 +738,27 @@ public boolean generateTree(Location loc, TreeType type) { @Override public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { - this.world.captureTreeGeneration = true; - this.world.captureBlockStates = true; - boolean grownTree = this.generateTree(loc, type); - this.world.captureBlockStates = false; - this.world.captureTreeGeneration = false; - if (grownTree) { // Copy block data to delegate - for (BlockState blockstate : this.world.capturedBlockStates.values()) { - BlockPos position = ((CraftBlockState) blockstate).getPosition(); - net.minecraft.world.level.block.state.BlockState oldBlock = this.world.getBlockState(position); - int flags = ((CraftBlockState) blockstate).getFlags(); - delegate.setBlockData(blockstate.getX(), blockstate.getY(), blockstate.getZ(), blockstate.getBlockData()); - net.minecraft.world.level.block.state.BlockState newBlock = this.world.getBlockState(position); - this.world.notifyAndUpdatePhysics(position, null, oldBlock, newBlock, newBlock, flags, net.minecraft.world.level.block.Block.UPDATE_LIMIT); - } - this.world.capturedBlockStates.clear(); - return true; - } else { - this.world.capturedBlockStates.clear(); - return false; - } +// this.world.captureTreeGeneration = true; +// this.world.captureBlockStates = true; +// boolean grownTree = this.generateTree(loc, type); +// this.world.captureBlockStates = false; +// this.world.captureTreeGeneration = false; +// if (grownTree) { // Copy block data to delegate +// for (BlockState blockstate : this.world.capturedBlockStates.values()) { +// BlockPos position = ((CraftBlockState) blockstate).getPosition(); +// net.minecraft.world.level.block.state.BlockState oldBlock = this.world.getBlockState(position); +// int flags = ((CraftBlockState) blockstate).getFlags(); +// delegate.setBlockData(blockstate.getX(), blockstate.getY(), blockstate.getZ(), blockstate.getBlockData()); +// net.minecraft.world.level.block.state.BlockState newBlock = this.world.getBlockState(position); +// this.world.notifyAndUpdatePhysics(position, null, oldBlock, newBlock, newBlock, flags, net.minecraft.world.level.block.Block.UPDATE_LIMIT); +// } +// this.world.capturedBlockStates.clear(); +// return true; +// } else { +// this.world.capturedBlockStates.clear(); +// return false; +// } + return false; } @Override diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java index 31e665388bc6..8082dc1ba85f 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java @@ -552,38 +552,39 @@ public boolean applyBoneMeal(BlockFace face) { BlockFertilizeEvent event = null; ServerLevel world = this.getCraftWorld().getHandle(); UseOnContext context = new UseOnContext(world, null, InteractionHand.MAIN_HAND, Items.BONE_MEAL.getDefaultInstance(), new BlockHitResult(Vec3.ZERO, direction, this.getPosition(), false)); - - // SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent - world.captureTreeGeneration = true; - InteractionResult result = BoneMealItem.applyBonemeal(context); - world.captureTreeGeneration = false; - - if (!world.capturedBlockStates.isEmpty()) { - TreeType treeType = SaplingBlock.treeType; - SaplingBlock.treeType = null; - List states = new ArrayList<>(world.capturedBlockStates.values()); - world.capturedBlockStates.clear(); - StructureGrowEvent structureEvent = null; - - if (treeType != null) { - structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, states); - Bukkit.getPluginManager().callEvent(structureEvent); - } - - event = new BlockFertilizeEvent(CraftBlock.at(world, this.getPosition()), null, states); - event.setCancelled(structureEvent != null && structureEvent.isCancelled()); - Bukkit.getPluginManager().callEvent(event); - - if (!event.isCancelled()) { - for (BlockState state : states) { - CraftBlockState craftBlockState = (CraftBlockState) state; - craftBlockState.place(craftBlockState.getFlags()); - world.checkCapturedTreeStateForObserverNotify(this.position, craftBlockState); // Paper - notify observers even if grow failed - } - } - } - - return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled()); +// +// // SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent +// world.captureTreeGeneration = true; +// InteractionResult result = BoneMealItem.applyBonemeal(context); +// world.captureTreeGeneration = false; +// +// if (!world.capturedBlockStates.isEmpty()) { +// TreeType treeType = SaplingBlock.treeType; +// SaplingBlock.treeType = null; +// List states = new ArrayList<>(world.capturedBlockStates.values()); +// world.capturedBlockStates.clear(); +// StructureGrowEvent structureEvent = null; +// +// if (treeType != null) { +// structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, states); +// Bukkit.getPluginManager().callEvent(structureEvent); +// } +// +// event = new BlockFertilizeEvent(CraftBlock.at(world, this.getPosition()), null, states); +// event.setCancelled(structureEvent != null && structureEvent.isCancelled()); +// Bukkit.getPluginManager().callEvent(event); +// +// if (!event.isCancelled()) { +// for (BlockState state : states) { +// CraftBlockState craftBlockState = (CraftBlockState) state; +// craftBlockState.place(craftBlockState.getFlags()); +// world.checkCapturedTreeStateForObserverNotify(this.position, craftBlockState); // Paper - notify observers even if grow failed +// } +// } +// } + + // return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled()); + return false; } @Override diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index cd83ca2ace1d..ea3abb465259 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -13,6 +13,8 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; import io.papermc.paper.adventure.PaperAdventure; @@ -23,6 +25,7 @@ import io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent; import io.papermc.paper.event.entity.ItemTransportingEntityValidateTargetEvent; import io.papermc.paper.event.player.PlayerBedFailEnterEvent; +import io.papermc.paper.util.capture.PaperCapturingWorldLevel; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.network.Connection; @@ -33,6 +36,7 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.players.PlayerList; import net.minecraft.tags.DamageTypeTags; +import net.minecraft.util.RandomSource; import net.minecraft.util.Unit; import net.minecraft.world.Container; import net.minecraft.world.InteractionHand; @@ -68,9 +72,11 @@ import net.minecraft.world.level.Explosion; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.WorldGenLevel; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.SignBlockEntity; import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; +import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.gamerules.GameRule; import net.minecraft.world.level.redstone.Redstone; import net.minecraft.world.level.storage.loot.LootContext; @@ -85,6 +91,7 @@ import org.bukkit.Material; import org.bukkit.PortalType; import org.bukkit.Statistic.Type; +import org.bukkit.TreeType; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; @@ -153,6 +160,7 @@ import org.bukkit.event.block.BlockDropItemEvent; import org.bukkit.event.block.BlockExplodeEvent; import org.bukkit.event.block.BlockFadeEvent; +import org.bukkit.event.block.BlockFertilizeEvent; import org.bukkit.event.block.BlockFormEvent; import org.bukkit.event.block.BlockGrowEvent; import org.bukkit.event.block.BlockIgniteEvent; @@ -268,6 +276,7 @@ import org.bukkit.event.world.EntitiesLoadEvent; import org.bukkit.event.world.EntitiesUnloadEvent; import org.bukkit.event.world.LootGenerateEvent; +import org.bukkit.event.world.StructureGrowEvent; import org.bukkit.inventory.CraftingRecipe; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.InventoryView; @@ -1291,7 +1300,7 @@ public static PlayerExpChangeEvent callPlayerExpChangeEvent(net.minecraft.world. return event; } - public static boolean handleBlockGrowEvent(Level world, BlockPos pos, net.minecraft.world.level.block.state.BlockState state, @net.minecraft.world.level.block.Block.UpdateFlags int flags) { + public static boolean handleBlockGrowEvent(LevelAccessor world, BlockPos pos, net.minecraft.world.level.block.state.BlockState state, @net.minecraft.world.level.block.Block.UpdateFlags int flags) { CraftBlockState snapshot = CraftBlockStates.getBlockState(world, pos); snapshot.setData(state); @@ -2381,6 +2390,46 @@ public static boolean sendChestLockedNotifications(Vec3 pos) { } return true; } + return false; + } + + public static boolean structureEvent(ServerLevel level, Player player, BlockPos pos, Function worldgenCapture, TreeType type) { + io.papermc.paper.util.capture.MinecraftCaptureBridge captureTreeGeneration = new io.papermc.paper.util.capture.MinecraftCaptureBridge(level); + if (worldgenCapture.apply(captureTreeGeneration)) { + var states = captureTreeGeneration.calculateLatestBlockStates(level); + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level); + + java.util.List blocks = new java.util.ArrayList<>(states.values()); + StructureGrowEvent structureEvent = new StructureGrowEvent(location, type, false, player, blocks); + + if (structureEvent.callEvent()) { + captureTreeGeneration.applyTasks(); + return true; + } + } + + + return false; + } + + public static boolean fertilizeBlock(ServerLevel level, Player player, BlockPos pos, Consumer worldgenCapture) { + io.papermc.paper.util.capture.MinecraftCaptureBridge captureTreeGeneration = new io.papermc.paper.util.capture.MinecraftCaptureBridge(level); + worldgenCapture.accept(captureTreeGeneration); + if (true) { + var states = captureTreeGeneration.calculateLatestBlockStates(level); + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level); + + java.util.List blocks = new java.util.ArrayList<>(states.values()); + BlockFertilizeEvent structureEvent = new BlockFertilizeEvent(location.getBlock(), player, blocks); + + if (structureEvent.callEvent()) { + captureTreeGeneration.applyTasks(); + return true; + } + } + + + return false; } } diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java index fd891f5b1fad..1a497438e14f 100644 --- a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java +++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java @@ -1,7 +1,18 @@ package io.papermc.testplugin; +import org.bukkit.Material; +import org.bukkit.block.BlockState; +import org.bukkit.block.Chest; +import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockFertilizeEvent; +import org.bukkit.event.block.BlockMultiPlaceEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.function.Consumer; public final class TestPlugin extends JavaPlugin implements Listener { @@ -11,4 +22,47 @@ public void onEnable() { // io.papermc.testplugin.brigtests.Registration.registerViaOnEnable(this); } + + @EventHandler + public void blockPlace(BlockFertilizeEvent event) { + //event.setCancelled(true); + + event.getPlayer().sendBlockChanges(event.getBlocks()); + + new BukkitRunnable(){ + + @Override + public void run() { + event.getBlocks().forEach((state) -> event.getPlayer().sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData())); + } + }.runTaskLater(this, 20 * 2); + + } + + @EventHandler + public void blockPlace(BlockPlaceEvent event) { + event.getPlayer().sendActionBar("Replaced: " + event.getBlockReplacedState().getType()); + if (event.getPlayer().getInventory().contains(Material.DIAMOND)) { + event.getBlock().setType(Material.CHEST); + + Chest state = (Chest) event.getBlock().getState(false); + state.getBlockInventory().setItem(0, new ItemStack(Material.STONE)); + } else if (event.getPlayer().getInventory().contains(Material.GOLD_INGOT)) { + event.setCancelled(true); + } + event.getPlayer().sendMessage(event.getBlock().getType().toString()); + } + + @EventHandler + public void blockPlace(BlockMultiPlaceEvent event) { + event.getPlayer().sendActionBar("Replaced: " + String.join(",", event.getReplacedBlockStates().stream().map(BlockState::getType).map(Material::toString).toList())); + + event.getPlayer().sendMessage(event.getBlock().getType().toString()); + if (event.getPlayer().getInventory().contains(Material.DIAMOND)) { + event.setCancelled(true); + } + } + + + } From f4074a073ee46ecfac3c4740002874058c3de043 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Thu, 5 Feb 2026 17:05:08 -0500 Subject: [PATCH 02/17] Fix build --- paper-server/patches/features/0032-Block-Capture-System.patch | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paper-server/patches/features/0032-Block-Capture-System.patch b/paper-server/patches/features/0032-Block-Capture-System.patch index 4a719af678e5..afb1e1e17a66 100644 --- a/paper-server/patches/features/0032-Block-Capture-System.patch +++ b/paper-server/patches/features/0032-Block-Capture-System.patch @@ -702,7 +702,7 @@ index ed06cffe8a5eba2ca4a34ade81f8185e21d7b651..25a094bc33546bc71bbe4ad348bd954a return interactionResult; } diff --git a/net/minecraft/world/item/SignItem.java b/net/minecraft/world/item/SignItem.java -index 3a41cf80e347ae3b30858879fb91f719625c8bb6..8e08155f4c3168994aa31452a7f4f2370ebf3b71 100644 +index 3a41cf80e347ae3b30858879fb91f719625c8bb6..334f30499b8e043bf74ebe7001ded66c42a9aa03 100644 --- a/net/minecraft/world/item/SignItem.java +++ b/net/minecraft/world/item/SignItem.java @@ -11,7 +11,7 @@ import net.minecraft.world.level.block.state.BlockState; @@ -730,7 +730,7 @@ index 3a41cf80e347ae3b30858879fb91f719625c8bb6..8e08155f4c3168994aa31452a7f4f237 - // signBlock.openTextEdit(player, signBlockEntity, true); - SignItem.openSign = pos; - // CraftBukkit end -+ level.addTask((serverLevel) -> signBlock.openTextEdit(player, signBlockEntity, true)); // Paper ++ level.addTask((serverLevel) -> signBlock.openTextEdit(player, signBlockEntity, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE)); // Paper } return flag; From c77b8418bf8caeba71cc32805adc821cbd7bdf95 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Fri, 6 Feb 2026 10:04:38 -0500 Subject: [PATCH 03/17] Better bonemeal support --- .../features/0032-Block-Capture-System.patch | 425 +++++++++--------- .../paper/util/capture/BoneMealContext.java | 23 + .../paper/util/capture/CaptureRecordMap.java | 146 +++--- .../util/capture/LiveBlockPlacementLayer.java | 2 +- .../util/capture/MinecraftCaptureBridge.java | 55 ++- .../capture/PaperCapturingWorldLevel.java | 2 + .../ServerLevelPaperCapturingWorldLevel.java | 5 + .../util/capture/SimpleBlockCapture.java | 20 +- .../SimpleBlockPlacementPredictor.java | 3 +- .../paper/util/capture/WorldCapturer.java | 14 +- .../org/bukkit/craftbukkit/CraftWorld.java | 41 +- .../bukkit/craftbukkit/block/CraftBlock.java | 36 +- .../craftbukkit/event/CraftEventFactory.java | 41 +- .../io/papermc/testplugin/TestPlugin.java | 35 +- 14 files changed, 422 insertions(+), 426 deletions(-) create mode 100644 paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java diff --git a/paper-server/patches/features/0032-Block-Capture-System.patch b/paper-server/patches/features/0032-Block-Capture-System.patch index afb1e1e17a66..bd9e260cd328 100644 --- a/paper-server/patches/features/0032-Block-Capture-System.patch +++ b/paper-server/patches/features/0032-Block-Capture-System.patch @@ -22,19 +22,21 @@ big todos: diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java -index bfefb5031544caa59230f0073e8880c2b39ebf4d..f1f35c09854bda9e7895e09f440e39c3e47d337f 100644 +index bfefb5031544caa59230f0073e8880c2b39ebf4d..0cb28b27e39670b307148377d28398645c4451c4 100644 --- a/net/minecraft/core/dispenser/DispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -394,56 +394,56 @@ public interface DispenseItemBehavior { - return item; - } - }); -- DispenserBlock.registerBehavior(Items.BONE_MEAL, new OptionalDispenseItemBehavior() { -- @Override -- protected ItemStack execute(BlockSource blockSource, ItemStack item) { -- this.setSuccess(true); -- Level level = blockSource.level(); -- BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING)); +@@ -9,6 +9,7 @@ import net.minecraft.core.Direction; + import net.minecraft.core.component.DataComponents; + import net.minecraft.core.particles.ParticleTypes; + import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; + import net.minecraft.sounds.SoundEvents; + import net.minecraft.sounds.SoundSource; + import net.minecraft.tags.BlockTags; +@@ -400,46 +401,22 @@ public interface DispenseItemBehavior { + this.setSuccess(true); + Level level = blockSource.level(); + BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING)); - // Paper start - Call BlockDispenseEvent - ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(blockSource, blockPos, item, this); - if (result != null) { @@ -44,10 +46,22 @@ index bfefb5031544caa59230f0073e8880c2b39ebf4d..f1f35c09854bda9e7895e09f440e39c3 - // Paper end - Call BlockDispenseEvent - level.captureTreeGeneration = true; // CraftBukkit - if (!BoneMealItem.growCrop(item, level, blockPos) && !BoneMealItem.growWaterPlant(item, level, blockPos, null)) { -- this.setSuccess(false); -- } else if (!level.isClientSide()) { -- level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, blockPos, 15); -- } ++ // Paper start ++ io.papermc.paper.util.capture.BoneMealContext boneMealContext = new io.papermc.paper.util.capture.BoneMealContext(); ++ boneMealContext.ogServerLevel = level.getMinecraftWorld(); ++ ++ if (!BoneMealItem.growCrop(item, level, blockPos, boneMealContext) && !org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( ++ (ServerLevel) level, ++ boneMealContext.getBukkitPlayer(), ++ blockPos, ++ (res) -> BoneMealItem.growWaterPlant(item, res, blockPos, null, boneMealContext), ++ boneMealContext.precancelStructureEvent ++ )) { ++ // Paper end + this.setSuccess(false); + } else if (!level.isClientSide()) { + level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, blockPos, 15); + } - // CraftBukkit start - level.captureTreeGeneration = false; - if (!level.capturedBlockStates.isEmpty()) { @@ -75,63 +89,9 @@ index bfefb5031544caa59230f0073e8880c2b39ebf4d..f1f35c09854bda9e7895e09f440e39c3 - } - } - // CraftBukkit end -- -- return item; -- } -- }); -+// DispenserBlock.registerBehavior(Items.BONE_MEAL, new OptionalDispenseItemBehavior() { -+// @Override -+// protected ItemStack execute(BlockSource blockSource, ItemStack item) { -+// this.setSuccess(true); -+// Level level = blockSource.level(); -+// BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING)); -+// // Paper start - Call BlockDispenseEvent -+// ItemStack result = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDispenseEvent(blockSource, blockPos, item, this); -+// if (result != null) { -+// this.setSuccess(false); -+// return result; -+// } -+// // Paper end - Call BlockDispenseEvent -+// level.captureTreeGeneration = true; // CraftBukkit -+// if (!BoneMealItem.growCrop(item, level, blockPos) && !BoneMealItem.growWaterPlant(item, level, blockPos, null)) { -+// this.setSuccess(false); -+// } else if (!level.isClientSide()) { -+// level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, blockPos, 15); -+// } -+// // CraftBukkit start -+// level.captureTreeGeneration = false; -+// if (!level.capturedBlockStates.isEmpty()) { -+// org.bukkit.TreeType treeType = net.minecraft.world.level.block.SaplingBlock.treeType; -+// net.minecraft.world.level.block.SaplingBlock.treeType = null; -+// org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(blockPos, level); -+// List states = new java.util.ArrayList<>(level.capturedBlockStates.values()); -+// level.capturedBlockStates.clear(); -+// org.bukkit.event.world.StructureGrowEvent structureEvent = null; -+// if (treeType != null) { -+// structureEvent = new org.bukkit.event.world.StructureGrowEvent(location, treeType, false, null, states); -+// org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent); -+// } -+// -+// org.bukkit.event.block.BlockFertilizeEvent fertilizeEvent = new org.bukkit.event.block.BlockFertilizeEvent(location.getBlock(), null, states); -+// fertilizeEvent.setCancelled(structureEvent != null && structureEvent.isCancelled()); -+// org.bukkit.Bukkit.getPluginManager().callEvent(fertilizeEvent); -+// -+// if (!fertilizeEvent.isCancelled()) { -+// for (org.bukkit.block.BlockState state : states) { -+// org.bukkit.craftbukkit.block.CraftBlockState craftBlockState = (org.bukkit.craftbukkit.block.CraftBlockState) state; -+// craftBlockState.place(craftBlockState.getFlags()); -+// blockSource.level().checkCapturedTreeStateForObserverNotify(blockPos, craftBlockState); // Paper - notify observers even if grow failed -+// } -+// } -+// } -+// // CraftBukkit end -+// -+// return item; -+// } -+// }); - DispenserBlock.registerBehavior(Blocks.TNT, new OptionalDispenseItemBehavior() { - @Override - protected ItemStack execute(BlockSource blockSource, ItemStack item) { + + return item; + } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index dc65503a2d785d64d37b76b0303f51cf66d9769a..9ea4dc4d45f4060955e24b0c80143b852539027d 100644 --- a/net/minecraft/server/level/ServerLevel.java @@ -202,19 +162,6 @@ index d815149ba20d815ec11fd7d4c203aa407e7aa8b2..78d5ab312a806b94403ddf38babea69a level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, blockPos, 15); this.cropPos = this.pickNextTarget(level, owner); this.setCurrentCropAsTarget(owner); -diff --git a/net/minecraft/world/entity/animal/bee/Bee.java b/net/minecraft/world/entity/animal/bee/Bee.java -index 0cbcf23b6edba2305dfbbc95abb06a90a6edd42b..ca943ed049d3a454333fc4633ff75c47e7b12c6a 100644 ---- a/net/minecraft/world/entity/animal/bee/Bee.java -+++ b/net/minecraft/world/entity/animal/bee/Bee.java -@@ -1005,7 +1005,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - } else if (blockState.is(Blocks.CAVE_VINES) || blockState.is(Blocks.CAVE_VINES_PLANT)) { - BonemealableBlock bonemealableBlock = (BonemealableBlock)blockState.getBlock(); - if (bonemealableBlock.isValidBonemealTarget(Bee.this.level(), blockPos, blockState)) { -- bonemealableBlock.performBonemeal((ServerLevel)Bee.this.level(), Bee.this.random, blockPos, blockState); -+ bonemealableBlock.performBonemeal(null, (ServerLevel)Bee.this.level(), Bee.this.random, blockPos, blockState); // Paper - blockState1 = Bee.this.level().getBlockState(blockPos); - } - } diff --git a/net/minecraft/world/item/BedItem.java b/net/minecraft/world/item/BedItem.java index 2818c7ad02a861270283384ceb7ecd4d44f6d624..f2cd7765da1ee5138aba9505c59354447ad061f8 100644 --- a/net/minecraft/world/item/BedItem.java @@ -230,7 +177,7 @@ index 2818c7ad02a861270283384ceb7ecd4d44f6d624..f2cd7765da1ee5138aba9505c5935444 } } diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java -index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..12774a0efe6e3a3619dea1a5d21cd714767f69f1 100644 +index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..ff349ef9b953bb78200909e96321204fe3acb710 100644 --- a/net/minecraft/world/item/BlockItem.java +++ b/net/minecraft/world/item/BlockItem.java @@ -1,13 +1,18 @@ @@ -271,7 +218,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..12774a0efe6e3a3619dea1a5d21cd714 - } else { + } + // Paper start -+ try ( SimpleBlockCapture capture = context.getLevel().capturer.createCaptureSession();) { ++ try ( SimpleBlockCapture capture = ((ServerLevel) context.getLevel()).fork()) { + boolean placeRes = this.placeBlock(blockPlaceContext, placementState, capture.capturingWorldLevel()); + if (!placeRes) { + return InteractionResult.FAIL; @@ -409,7 +356,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..12774a0efe6e3a3619dea1a5d21cd714 return false; } else { diff --git a/net/minecraft/world/item/BoneMealItem.java b/net/minecraft/world/item/BoneMealItem.java -index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..55b6e1aab280676a37f0c70faa457535f6353038 100644 +index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..f28e2f96485776099c80a3692f2ea688f9ecc89c 100644 --- a/net/minecraft/world/item/BoneMealItem.java +++ b/net/minecraft/world/item/BoneMealItem.java @@ -6,11 +6,13 @@ import net.minecraft.core.Holder; @@ -426,16 +373,21 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..55b6e1aab280676a37f0c70faa457535 import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; -@@ -44,7 +46,7 @@ public class BoneMealItem extends Item { +@@ -44,7 +46,12 @@ public class BoneMealItem extends Item { BlockPos clickedPos = context.getClickedPos(); BlockPos blockPos = clickedPos.relative(context.getClickedFace()); ItemStack itemInHand = context.getItemInHand(); - if (growCrop(itemInHand, level, clickedPos)) { -+ if (growCrop(itemInHand, level, clickedPos, context.getPlayer())) { // Paper ++ // Paper start ++ io.papermc.paper.util.capture.BoneMealContext boneMealContext = new io.papermc.paper.util.capture.BoneMealContext(); ++ boneMealContext.player = (ServerPlayer) context.getPlayer(); ++ boneMealContext.ogServerLevel = level.getMinecraftWorld(); ++ // Paper end ++ if (growCrop(itemInHand, level, clickedPos, boneMealContext)) { // Paper if (!level.isClientSide()) { if (context.getPlayer() != null) itemInHand.causeUseVibration(context.getPlayer(), GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518 level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, clickedPos, 15); -@@ -54,7 +56,15 @@ public class BoneMealItem extends Item { +@@ -54,7 +61,16 @@ public class BoneMealItem extends Item { } else { BlockState blockState = level.getBlockState(clickedPos); boolean isFaceSturdy = blockState.isFaceSturdy(level, clickedPos, context.getClickedFace()); @@ -443,21 +395,22 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..55b6e1aab280676a37f0c70faa457535 + + boolean result = org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( + (ServerLevel) level, -+ (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), ++ boneMealContext.getBukkitPlayer(), + blockPos, -+ (res) -> growWaterPlant(itemInHand, res, blockPos, context.getClickedFace(), context.getPlayer()) ++ (res) -> growWaterPlant(itemInHand, res, blockPos, context.getClickedFace(), boneMealContext), ++ boneMealContext.precancelStructureEvent + ); + + if (isFaceSturdy && result) { if (!level.isClientSide()) { if (context.getPlayer() != null) itemInHand.causeUseVibration(context.getPlayer(), GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518 level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, blockPos, 15); -@@ -67,12 +77,17 @@ public class BoneMealItem extends Item { +@@ -67,12 +83,18 @@ public class BoneMealItem extends Item { } } - public static boolean growCrop(ItemStack stack, Level level, BlockPos pos) { -+ public static boolean growCrop(ItemStack stack, Level level, BlockPos pos, @Nullable Player player) { // Paper ++ public static boolean growCrop(ItemStack stack, Level level, BlockPos pos, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper BlockState blockState = level.getBlockState(pos); if (blockState.getBlock() instanceof BonemealableBlock bonemealableBlock && bonemealableBlock.isValidBonemealTarget(level, pos, blockState)) { if (level instanceof ServerLevel) { @@ -465,23 +418,24 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..55b6e1aab280676a37f0c70faa457535 - bonemealableBlock.performBonemeal((ServerLevel)level, level.random, pos, blockState); + org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( + (ServerLevel) level, -+ (org.bukkit.entity.Player) player.getBukkitEntity(), ++ mealContext.getBukkitPlayer(), + pos, -+ (res) -> bonemealableBlock.performBonemeal((ServerPlayer) player, res, level.random, pos, blockState) ++ (res) -> bonemealableBlock.performBonemeal(res, level.random, pos, blockState, mealContext), ++ mealContext.precancelStructureEvent + ); } stack.shrink(1); -@@ -84,7 +99,7 @@ public class BoneMealItem extends Item { +@@ -84,7 +106,7 @@ public class BoneMealItem extends Item { } } - public static boolean growWaterPlant(ItemStack stack, Level level, BlockPos pos, @Nullable Direction clickedSide) { -+ public static boolean growWaterPlant(ItemStack stack, LevelAccessor level, BlockPos pos, @Nullable Direction clickedSide, @Nullable Player player) { ++ public static boolean growWaterPlant(ItemStack stack, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, @Nullable Direction clickedSide, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper if (level.getBlockState(pos).is(Blocks.WATER) && level.getFluidState(pos).getAmount() == 8) { if (!(level instanceof ServerLevel)) { return true; -@@ -107,7 +122,7 @@ public class BoneMealItem extends Item { +@@ -107,7 +129,7 @@ public class BoneMealItem extends Item { if (biome.is(BiomeTags.PRODUCES_CORALS_FROM_BONEMEAL)) { if (i == 0 && clickedSide != null && clickedSide.getAxis().isHorizontal()) { blockState = BuiltInRegistries.BLOCK @@ -490,7 +444,7 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..55b6e1aab280676a37f0c70faa457535 .map(holder -> holder.value().defaultBlockState()) .orElse(blockState); if (blockState.hasProperty(BaseCoralWallFanBlock.FACING)) { -@@ -115,7 +130,7 @@ public class BoneMealItem extends Item { +@@ -115,7 +137,7 @@ public class BoneMealItem extends Item { } } else if (random.nextInt(4) == 0) { blockState = BuiltInRegistries.BLOCK @@ -499,12 +453,12 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..55b6e1aab280676a37f0c70faa457535 .map(holder -> holder.value().defaultBlockState()) .orElse(blockState); } -@@ -134,7 +149,7 @@ public class BoneMealItem extends Item { +@@ -134,7 +156,7 @@ public class BoneMealItem extends Item { } else if (blockState1.is(Blocks.SEAGRASS) && ((BonemealableBlock)Blocks.SEAGRASS).isValidBonemealTarget(level, blockPos, blockState1) && random.nextInt(10) == 0) { - ((BonemealableBlock)Blocks.SEAGRASS).performBonemeal((ServerLevel)level, random, blockPos, blockState1); -+ ((BonemealableBlock)Blocks.SEAGRASS).performBonemeal((ServerPlayer) player, (ServerLevel)level, random, blockPos, blockState1); ++ ((BonemealableBlock)Blocks.SEAGRASS).performBonemeal(level, random, blockPos, blockState1, mealContext); // Paper } } } @@ -989,7 +943,7 @@ index d8f3ed6f5e5cbc045399e38664cd062737481f7b..83336865ad6ab30c3494361c5fa60886 + } diff --git a/net/minecraft/world/level/block/AzaleaBlock.java b/net/minecraft/world/level/block/AzaleaBlock.java -index 435a455ad2ec3dfb142d570a51a720bc6c49dac3..5841a0a3decc7fabd206f5d017ddc68db464556c 100644 +index 435a455ad2ec3dfb142d570a51a720bc6c49dac3..10363f11fb1b113ba6e6d48ca4d432dfc01f9bff 100644 --- a/net/minecraft/world/level/block/AzaleaBlock.java +++ b/net/minecraft/world/level/block/AzaleaBlock.java @@ -49,8 +49,8 @@ public class AzaleaBlock extends VegetationBlock implements BonemealableBlock { @@ -998,13 +952,13 @@ index 435a455ad2ec3dfb142d570a51a720bc6c49dac3..5841a0a3decc7fabd206f5d017ddc68d @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { - TreeGrower.AZALEA.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper -+ TreeGrower.AZALEA.growTree(level, level.getChunkSource().getGenerator(), pos, state, random, new java.util.concurrent.atomic.AtomicReference<>()); // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ TreeGrower.AZALEA.growTree(level, level.getChunkSource().getGenerator(), pos, state, random, mealContext); // Paper } @Override diff --git a/net/minecraft/world/level/block/BambooSaplingBlock.java b/net/minecraft/world/level/block/BambooSaplingBlock.java -index 88c204ad9d4ead792eb618dbb8611cca863e798c..36a60db022271496e851106d19aa303f16af2cb9 100644 +index 88c204ad9d4ead792eb618dbb8611cca863e798c..baf1f1b9e445c5caf2784f73db49ae83cdc7156b 100644 --- a/net/minecraft/world/level/block/BambooSaplingBlock.java +++ b/net/minecraft/world/level/block/BambooSaplingBlock.java @@ -84,11 +84,11 @@ public class BambooSaplingBlock extends Block implements BonemealableBlock { @@ -1012,7 +966,7 @@ index 88c204ad9d4ead792eb618dbb8611cca863e798c..36a60db022271496e851106d19aa303f @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper this.growBamboo(level, pos); } @@ -1022,7 +976,7 @@ index 88c204ad9d4ead792eb618dbb8611cca863e798c..36a60db022271496e851106d19aa303f } } diff --git a/net/minecraft/world/level/block/BambooStalkBlock.java b/net/minecraft/world/level/block/BambooStalkBlock.java -index 81e2a279d4c29f5fe52387875489239515a8c82b..a9c033d5bf4a78162bbcc7baf46626f259f65296 100644 +index 81e2a279d4c29f5fe52387875489239515a8c82b..af797be959d91e30b444459f2ccc99c29b680fc7 100644 --- a/net/minecraft/world/level/block/BambooStalkBlock.java +++ b/net/minecraft/world/level/block/BambooStalkBlock.java @@ -166,7 +166,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { @@ -1030,7 +984,7 @@ index 81e2a279d4c29f5fe52387875489239515a8c82b..a9c033d5bf4a78162bbcc7baf46626f2 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper int heightAboveUpToMax = this.getHeightAboveUpToMax(level, pos); int heightBelowUpToMax = this.getHeightBelowUpToMax(level, pos); int i = heightAboveUpToMax + heightBelowUpToMax + 1; @@ -1087,7 +1041,7 @@ index dbc912d514120a33f22959d6dc36ccf6ebc6be80..6fb29d9c58a4b304591463f9937f616f } diff --git a/net/minecraft/world/level/block/BigDripleafBlock.java b/net/minecraft/world/level/block/BigDripleafBlock.java -index c2ef71dd9ee070f80d32b8829b80240ff22f0f7f..66a41f4198551d22714d74330cad8938c228e9f5 100644 +index c2ef71dd9ee070f80d32b8829b80240ff22f0f7f..70d4c9359c9e3c7c28ad8ae6f2f4a6a180a70299 100644 --- a/net/minecraft/world/level/block/BigDripleafBlock.java +++ b/net/minecraft/world/level/block/BigDripleafBlock.java @@ -177,7 +177,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone @@ -1095,12 +1049,12 @@ index c2ef71dd9ee070f80d32b8829b80240ff22f0f7f..66a41f4198551d22714d74330cad8938 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper BlockPos blockPos = pos.above(); BlockState blockState = level.getBlockState(blockPos); if (canPlaceAt(level, blockPos, blockState)) { diff --git a/net/minecraft/world/level/block/BigDripleafStemBlock.java b/net/minecraft/world/level/block/BigDripleafStemBlock.java -index 0ce3e60a94dff03874cad51a936f247d5c3d65ce..425c367b60413c6f664be7b43933b06c8358cb4f 100644 +index 0ce3e60a94dff03874cad51a936f247d5c3d65ce..40c56db22734c0caf51d53666163b8625368f341 100644 --- a/net/minecraft/world/level/block/BigDripleafStemBlock.java +++ b/net/minecraft/world/level/block/BigDripleafStemBlock.java @@ -119,7 +119,7 @@ public class BigDripleafStemBlock extends HorizontalDirectionalBlock implements @@ -1108,7 +1062,7 @@ index 0ce3e60a94dff03874cad51a936f247d5c3d65ce..425c367b60413c6f664be7b43933b06c @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper Optional topConnectedBlock = BlockUtil.getTopConnectedBlock(level, pos, state.getBlock(), Direction.UP, Blocks.BIG_DRIPLEAF); if (!topConnectedBlock.isEmpty()) { BlockPos blockPos = topConnectedBlock.get(); @@ -1126,15 +1080,19 @@ index 94b4143449c99ee35db44ab8e2a766d924aa6410..9b9cc245b1fc42d9747de68049189935 public boolean isPossibleToRespawnInThis(BlockState state) { diff --git a/net/minecraft/world/level/block/BonemealableBlock.java b/net/minecraft/world/level/block/BonemealableBlock.java -index 676cc6cb2ea0ac25c4e4bd6a32856f07784c7b49..a858eb2cc530cd55a9a98e001fa0b11e0cc039ad 100644 +index 676cc6cb2ea0ac25c4e4bd6a32856f07784c7b49..959c4dacca5c8cadb2cb0e63f88e355af95617e5 100644 --- a/net/minecraft/world/level/block/BonemealableBlock.java +++ b/net/minecraft/world/level/block/BonemealableBlock.java -@@ -15,14 +15,14 @@ public interface BonemealableBlock { +@@ -15,14 +15,18 @@ public interface BonemealableBlock { boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state); - void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state); -+ void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state); // Paper ++ default void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { ++ performBonemeal(level, random, pos, state, null); ++ } ++ ++ void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext); // Paper static boolean hasSpreadableNeighbourPos(LevelReader level, BlockPos pos, BlockState state) { return getSpreadableNeighbourPos(Direction.Plane.HORIZONTAL.stream().toList(), level, pos, state).isPresent(); @@ -1148,7 +1106,7 @@ index 676cc6cb2ea0ac25c4e4bd6a32856f07784c7b49..a858eb2cc530cd55a9a98e001fa0b11e private static Optional getSpreadableNeighbourPos(List directions, LevelReader level, BlockPos pos, BlockState state) { diff --git a/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java b/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java -index ee701a4c5042aec359271533680d292a6169d4db..b00d810fc5d129e66c6fb06e8dca9912ce5e09c7 100644 +index ee701a4c5042aec359271533680d292a6169d4db..9f0a3e2225b17cbacfd9de2e40c64bcec568555c 100644 --- a/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java +++ b/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java @@ -41,11 +41,11 @@ public class BonemealableFeaturePlacerBlock extends Block implements Bonemealabl @@ -1156,7 +1114,7 @@ index ee701a4c5042aec359271533680d292a6169d4db..b00d810fc5d129e66c6fb06e8dca9912 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper level.registryAccess() .lookup(Registries.CONFIGURED_FEATURE) .flatMap(registry -> registry.get(this.feature)) @@ -1166,7 +1124,7 @@ index ee701a4c5042aec359271533680d292a6169d4db..b00d810fc5d129e66c6fb06e8dca9912 @Override diff --git a/net/minecraft/world/level/block/BushBlock.java b/net/minecraft/world/level/block/BushBlock.java -index 532d2982b520f6f9f7021ec85ef3bcf9bd31e141..ce59e4b69854d6122370a7736ae99ae014e5600c 100644 +index 532d2982b520f6f9f7021ec85ef3bcf9bd31e141..52663da1b1413a104c40b44b341fd5e4f1b2a4eb 100644 --- a/net/minecraft/world/level/block/BushBlock.java +++ b/net/minecraft/world/level/block/BushBlock.java @@ -41,7 +41,7 @@ public class BushBlock extends VegetationBlock implements BonemealableBlock { @@ -1174,12 +1132,12 @@ index 532d2982b520f6f9f7021ec85ef3bcf9bd31e141..ce59e4b69854d6122370a7736ae99ae0 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper BonemealableBlock.findSpreadableNeighbourPos(level, pos, state).ifPresent(blockPos -> level.setBlockAndUpdate(blockPos, this.defaultBlockState())); } } diff --git a/net/minecraft/world/level/block/CaveVinesBlock.java b/net/minecraft/world/level/block/CaveVinesBlock.java -index f4a4dc14012c110e58b1c9272d80d4b89394d090..31758dc5d353faf29272ba3feaf72ee360d6949f 100644 +index f4a4dc14012c110e58b1c9272d80d4b89394d090..053090bcec4a53b707238e634db4683002f10c93 100644 --- a/net/minecraft/world/level/block/CaveVinesBlock.java +++ b/net/minecraft/world/level/block/CaveVinesBlock.java @@ -89,7 +89,7 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements CaveVines { @@ -1187,12 +1145,12 @@ index f4a4dc14012c110e58b1c9272d80d4b89394d090..31758dc5d353faf29272ba3feaf72ee3 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper level.setBlock(pos, state.setValue(BERRIES, true), Block.UPDATE_CLIENTS); } } diff --git a/net/minecraft/world/level/block/CaveVinesPlantBlock.java b/net/minecraft/world/level/block/CaveVinesPlantBlock.java -index aba65098fb3202e31b07aa2cee52acc5b671fa95..9d249b1ab62c57d0adbe8e1299c710f3d8732883 100644 +index aba65098fb3202e31b07aa2cee52acc5b671fa95..f2d6f31e007f49d50052fb24efa7fc2eaa92a799 100644 --- a/net/minecraft/world/level/block/CaveVinesPlantBlock.java +++ b/net/minecraft/world/level/block/CaveVinesPlantBlock.java @@ -65,7 +65,7 @@ public class CaveVinesPlantBlock extends GrowingPlantBodyBlock implements CaveVi @@ -1200,12 +1158,12 @@ index aba65098fb3202e31b07aa2cee52acc5b671fa95..9d249b1ab62c57d0adbe8e1299c710f3 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper level.setBlock(pos, state.setValue(BERRIES, true), Block.UPDATE_CLIENTS); } } diff --git a/net/minecraft/world/level/block/CocoaBlock.java b/net/minecraft/world/level/block/CocoaBlock.java -index d4f82e39bb688e43decfb5c8e72fd6db2bfeb571..21f7ad2cbdae399230fe16cebc503d5dc58a7754 100644 +index d4f82e39bb688e43decfb5c8e72fd6db2bfeb571..0b7e4732bc65c8c75ec876eebb238eafed1bb597 100644 --- a/net/minecraft/world/level/block/CocoaBlock.java +++ b/net/minecraft/world/level/block/CocoaBlock.java @@ -114,7 +114,7 @@ public class CocoaBlock extends HorizontalDirectionalBlock implements Bonemealab @@ -1213,7 +1171,7 @@ index d4f82e39bb688e43decfb5c8e72fd6db2bfeb571..21f7ad2cbdae399230fe16cebc503d5d @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, state.setValue(AGE, state.getValue(AGE) + 1), Block.UPDATE_CLIENTS); // CraftBukkit } @@ -1275,7 +1233,7 @@ index c5fe15844d405a27cdae18c903dd481c25b437de..abb7d98c6608ec1bbed169327114245d level.scheduleTick(pos, this, 4); } diff --git a/net/minecraft/world/level/block/CropBlock.java b/net/minecraft/world/level/block/CropBlock.java -index 1cf40fafd822d976ef4822335c60d8017659916f..e9401fb9b5b8bbd0f11e0425c7a5175d78bcd5d2 100644 +index 1cf40fafd822d976ef4822335c60d8017659916f..e9b49027c02160304b917bd5f1b03b2929e0e87a 100644 --- a/net/minecraft/world/level/block/CropBlock.java +++ b/net/minecraft/world/level/block/CropBlock.java @@ -13,6 +13,7 @@ import net.minecraft.world.item.Items; @@ -1308,7 +1266,7 @@ index 1cf40fafd822d976ef4822335c60d8017659916f..e9401fb9b5b8bbd0f11e0425c7a5175d @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper this.growCrops(level, pos, state); } @@ -1375,7 +1333,7 @@ index e7dbc14cce1d0ec3f410ae7ebbb4dc47301b5176..449083791b18eb4df10a3117f9802594 level.playSound( null, pos, state.getValue(WATERLOGGED) ? SoundEvents.DRIED_GHAST_PLACE_IN_WATER : SoundEvents.DRIED_GHAST_PLACE, SoundSource.BLOCKS, 1.0F, 1.0F diff --git a/net/minecraft/world/level/block/FireflyBushBlock.java b/net/minecraft/world/level/block/FireflyBushBlock.java -index 635ce3fb2583f6b14355f6137fb6f79dfd959400..ac418646c849d029969ca2d4dbcdc0c55be0a6d6 100644 +index 635ce3fb2583f6b14355f6137fb6f79dfd959400..c1927d4655d91d556ec9fb734c197a3a33a8ff56 100644 --- a/net/minecraft/world/level/block/FireflyBushBlock.java +++ b/net/minecraft/world/level/block/FireflyBushBlock.java @@ -58,7 +58,7 @@ public class FireflyBushBlock extends VegetationBlock implements BonemealableBlo @@ -1383,12 +1341,12 @@ index 635ce3fb2583f6b14355f6137fb6f79dfd959400..ac418646c849d029969ca2d4dbcdc0c5 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper BonemealableBlock.findSpreadableNeighbourPos(level, pos, state).ifPresent(blockPos -> level.setBlockAndUpdate(blockPos, this.defaultBlockState())); } } diff --git a/net/minecraft/world/level/block/FlowerBedBlock.java b/net/minecraft/world/level/block/FlowerBedBlock.java -index 2a24e71d2b598792ebaa867a2f74fd79e97cc6a2..662c070bb28d81fab4c105631d8efc8516e4cfe2 100644 +index 2a24e71d2b598792ebaa867a2f74fd79e97cc6a2..5f39cc10064c222f9009bdee5ce59bfe3bbae704 100644 --- a/net/minecraft/world/level/block/FlowerBedBlock.java +++ b/net/minecraft/world/level/block/FlowerBedBlock.java @@ -92,12 +92,12 @@ public class FlowerBedBlock extends VegetationBlock implements BonemealableBlock @@ -1396,7 +1354,7 @@ index 2a24e71d2b598792ebaa867a2f74fd79e97cc6a2..662c070bb28d81fab4c105631d8efc85 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper int amountValue = state.getValue(AMOUNT); if (amountValue < 4) { level.setBlock(pos, state.setValue(AMOUNT, amountValue + 1), Block.UPDATE_CLIENTS); @@ -1407,7 +1365,7 @@ index 2a24e71d2b598792ebaa867a2f74fd79e97cc6a2..662c070bb28d81fab4c105631d8efc85 } } diff --git a/net/minecraft/world/level/block/FungusBlock.java b/net/minecraft/world/level/block/FungusBlock.java -index 9711efb088bd0da9168e9bcd0496bd7caddd2974..0e78d131c2b57ba495d2602716f5a735b9ef7324 100644 +index 9711efb088bd0da9168e9bcd0496bd7caddd2974..6e7e4aa0e8dc9dd3dd92f29d5603255ba176b252 100644 --- a/net/minecraft/world/level/block/FungusBlock.java +++ b/net/minecraft/world/level/block/FungusBlock.java @@ -71,7 +71,7 @@ public class FungusBlock extends VegetationBlock implements BonemealableBlock { @@ -1415,12 +1373,12 @@ index 9711efb088bd0da9168e9bcd0496bd7caddd2974..0e78d131c2b57ba495d2602716f5a735 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper this.getFeature(level) // CraftBukkit start .map((value) -> { diff --git a/net/minecraft/world/level/block/GlowLichenBlock.java b/net/minecraft/world/level/block/GlowLichenBlock.java -index ba39497a6d7160cde961e339be8028ec131b8019..60fcc089f3a576f71f00982397ca5aacc93d3f7e 100644 +index ba39497a6d7160cde961e339be8028ec131b8019..f38447dbc1880878d29e08a18a2db32319291992 100644 --- a/net/minecraft/world/level/block/GlowLichenBlock.java +++ b/net/minecraft/world/level/block/GlowLichenBlock.java @@ -39,7 +39,7 @@ public class GlowLichenBlock extends MultifaceSpreadeableBlock implements Boneme @@ -1428,12 +1386,12 @@ index ba39497a6d7160cde961e339be8028ec131b8019..60fcc089f3a576f71f00982397ca5aac @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper this.spreader.spreadFromRandomFaceTowardRandomDirection(state, level, pos, random); } diff --git a/net/minecraft/world/level/block/GrassBlock.java b/net/minecraft/world/level/block/GrassBlock.java -index 368f60ecce691ea161120743150e87b32efc3ca4..3eb4fa3aec3540a09de0bd0e431dd86bcaf7d0fe 100644 +index 368f60ecce691ea161120743150e87b32efc3ca4..8a74e7b53ec7d3c3ad7b71856ef01abef9b98dd7 100644 --- a/net/minecraft/world/level/block/GrassBlock.java +++ b/net/minecraft/world/level/block/GrassBlock.java @@ -40,7 +40,7 @@ public class GrassBlock extends SpreadingSnowyDirtBlock implements BonemealableB @@ -1441,7 +1399,7 @@ index 368f60ecce691ea161120743150e87b32efc3ca4..3eb4fa3aec3540a09de0bd0e431dd86b @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper BlockPos blockPos = pos.above(); BlockState blockState = Blocks.SHORT_GRASS.defaultBlockState(); Optional> optional = level.registryAccess() @@ -1450,30 +1408,25 @@ index 368f60ecce691ea161120743150e87b32efc3ca4..3eb4fa3aec3540a09de0bd0e431dd86b BonemealableBlock bonemealableBlock = (BonemealableBlock)blockState.getBlock(); if (bonemealableBlock.isValidBonemealTarget(level, blockPos1, blockState1)) { - bonemealableBlock.performBonemeal(level, random, blockPos1, blockState1); -+ bonemealableBlock.performBonemeal(player, level, random, blockPos1, blockState1); ++ bonemealableBlock.performBonemeal(level, random, blockPos1, blockState1, mealContext); // Paper } } diff --git a/net/minecraft/world/level/block/GrowingPlantBodyBlock.java b/net/minecraft/world/level/block/GrowingPlantBodyBlock.java -index 314d198617e34f91f72a2952a2a62ce0a3b9147d..7295c11780564b8bdf4aa35cddb56a1cebaf2e9b 100644 +index 314d198617e34f91f72a2952a2a62ce0a3b9147d..a043d0956de69914736346241071cd59d8514d66 100644 --- a/net/minecraft/world/level/block/GrowingPlantBodyBlock.java +++ b/net/minecraft/world/level/block/GrowingPlantBodyBlock.java -@@ -74,11 +74,11 @@ public abstract class GrowingPlantBodyBlock extends GrowingPlantBlock implements +@@ -74,7 +74,7 @@ public abstract class GrowingPlantBodyBlock extends GrowingPlantBlock implements } @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper Optional headPos = this.getHeadPos(level, pos, state.getBlock()); if (headPos.isPresent()) { BlockState blockState = level.getBlockState(headPos.get()); -- ((GrowingPlantHeadBlock)blockState.getBlock()).performBonemeal(level, random, headPos.get(), blockState); -+ ((GrowingPlantHeadBlock)blockState.getBlock()).performBonemeal(player, level, random, headPos.get(), blockState); - } - } - diff --git a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java -index bac7f990282fd7c676c2f8c40d7fd87badb1e284..c09b69777901df20686fcd1329317b395a7d33bb 100644 +index bac7f990282fd7c676c2f8c40d7fd87badb1e284..1a1dcfe8abff794663f3aaeefff693686cc54bce 100644 --- a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java +++ b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java @@ -135,7 +135,7 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements @@ -1481,12 +1434,12 @@ index bac7f990282fd7c676c2f8c40d7fd87badb1e284..c09b69777901df20686fcd1329317b39 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper BlockPos blockPos = pos.relative(this.growthDirection); int min = Math.min(state.getValue(AGE) + 1, 25); int blocksToGrowWhenBonemealed = this.getBlocksToGrowWhenBonemealed(random); diff --git a/net/minecraft/world/level/block/HangingMossBlock.java b/net/minecraft/world/level/block/HangingMossBlock.java -index 9675e0ad00a4a26c38a7388dc3615e0c33b4e802..4fd97dd6ed215080091bae7bdc70e08f8091f487 100644 +index 9675e0ad00a4a26c38a7388dc3615e0c33b4e802..a319d8e0a2737723c059a4400a3e62d81c4f0717 100644 --- a/net/minecraft/world/level/block/HangingMossBlock.java +++ b/net/minecraft/world/level/block/HangingMossBlock.java @@ -124,7 +124,7 @@ public class HangingMossBlock extends Block implements BonemealableBlock { @@ -1494,7 +1447,7 @@ index 9675e0ad00a4a26c38a7388dc3615e0c33b4e802..4fd97dd6ed215080091bae7bdc70e08f @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper BlockPos blockPos = this.getTip(level, pos).below(); if (this.canGrowInto(level.getBlockState(blockPos))) { level.setBlockAndUpdate(blockPos, state.setValue(TIP, true)); @@ -1512,7 +1465,7 @@ index a4b824dab307705559a76b954a3e47d30dd65889..4fe64b4b445ddceedff0909c917e85d6 TypedEntityData> typedEntityData = stack.get(DataComponents.BLOCK_ENTITY_DATA); if (typedEntityData != null && typedEntityData.contains("RecordItem")) { diff --git a/net/minecraft/world/level/block/MangroveLeavesBlock.java b/net/minecraft/world/level/block/MangroveLeavesBlock.java -index 6d8154821cd17f7529a44eeb16a78caa07529436..b79a527c038965cc1faf9a4b326ce1e9c6c467a9 100644 +index 6d8154821cd17f7529a44eeb16a78caa07529436..5ba0ec6900bd8c0fa9b38f83d8af6ba07d9bada6 100644 --- a/net/minecraft/world/level/block/MangroveLeavesBlock.java +++ b/net/minecraft/world/level/block/MangroveLeavesBlock.java @@ -40,7 +40,7 @@ public class MangroveLeavesBlock extends TintedParticleLeavesBlock implements Bo @@ -1520,12 +1473,12 @@ index 6d8154821cd17f7529a44eeb16a78caa07529436..b79a527c038965cc1faf9a4b326ce1e9 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper level.setBlock(pos.below(), MangrovePropaguleBlock.createNewHangingPropagule(), Block.UPDATE_CLIENTS); } diff --git a/net/minecraft/world/level/block/MangrovePropaguleBlock.java b/net/minecraft/world/level/block/MangrovePropaguleBlock.java -index 2fba8534a636491314c760bc338226f6506f0a9a..78f1be360817712725a68adba51530f4a5157418 100644 +index 2fba8534a636491314c760bc338226f6506f0a9a..71320ae181ae72f0803e70190d73627972074e71 100644 --- a/net/minecraft/world/level/block/MangrovePropaguleBlock.java +++ b/net/minecraft/world/level/block/MangrovePropaguleBlock.java @@ -123,11 +123,11 @@ public class MangrovePropaguleBlock extends SaplingBlock implements SimpleWaterl @@ -1533,17 +1486,17 @@ index 2fba8534a636491314c760bc338226f6506f0a9a..78f1be360817712725a68adba51530f4 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper if (isHanging(state) && !isFullyGrown(state)) { level.setBlock(pos, state.cycle(AGE), Block.UPDATE_CLIENTS); } else { - super.performBonemeal(level, random, pos, state); -+ super.performBonemeal(player, level, random, pos, state); // Paper ++ super.performBonemeal(level, random, pos, state, mealContext); // Paper } } diff --git a/net/minecraft/world/level/block/MossyCarpetBlock.java b/net/minecraft/world/level/block/MossyCarpetBlock.java -index 3762d833f17d596e04845a3f391423f9c14a878b..89de83c625439389daca9488087b291f89702c98 100644 +index 3762d833f17d596e04845a3f391423f9c14a878b..f5d03f2a3a3866857ddaece77ac4e1273d71c83b 100644 --- a/net/minecraft/world/level/block/MossyCarpetBlock.java +++ b/net/minecraft/world/level/block/MossyCarpetBlock.java @@ -176,7 +176,7 @@ public class MossyCarpetBlock extends Block implements BonemealableBlock { @@ -1560,42 +1513,48 @@ index 3762d833f17d596e04845a3f391423f9c14a878b..89de83c625439389daca9488087b291f @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper BlockState blockState = createTopperWithSideChance(level, pos, () -> true); if (!blockState.isAir()) { level.setBlock(pos.above(), blockState, Block.UPDATE_ALL); diff --git a/net/minecraft/world/level/block/MushroomBlock.java b/net/minecraft/world/level/block/MushroomBlock.java -index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..10c95b15c1a9a824e93b1eba08bfcd4169645659 100644 +index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..519c12aeec62c7437e0f3e92718965c798280612 100644 --- a/net/minecraft/world/level/block/MushroomBlock.java +++ b/net/minecraft/world/level/block/MushroomBlock.java -@@ -87,13 +87,13 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock +@@ -87,16 +87,18 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock return blockState.is(BlockTags.MUSHROOM_GROW_BLOCK) || level.getRawBrightness(pos, 0) < 13 && this.mayPlaceOn(blockState, level, blockPos); } - public boolean growMushroom(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) { -+ public boolean growMushroom(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, RandomSource random) { // Paper ++ public boolean growMushroom(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper Optional>> optional = level.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).get(this.feature); if (optional.isEmpty()) { return false; } else { level.removeBlock(pos, false); - SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit -+// SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit TODO: - if (optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) { +- if (optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) { ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.structureEvent(mealContext.ogServerLevel, level, mealContext.getBukkitPlayer(), pos, (gen) -> { ++ return optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos); ++ }, (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM)) { return true; } else { -@@ -114,7 +114,7 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock ++ mealContext.precancelStructureEvent = false; // Paper + level.setBlock(pos, state, Block.UPDATE_ALL); + return false; + } +@@ -114,7 +116,7 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock } @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { - this.growMushroom(level, pos, state, random); -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper -+ this.growMushroom(player, level, pos, state, random); // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ this.growMushroom(level, pos, state, random, mealContext); // Paper } } diff --git a/net/minecraft/world/level/block/NetherrackBlock.java b/net/minecraft/world/level/block/NetherrackBlock.java -index 24e166e399bc542522fc832064401ebb6ba2568e..f3a549712321c94b56d93c6fbba27558010b9f68 100644 +index 24e166e399bc542522fc832064401ebb6ba2568e..c217cc3fe5fb64c18a7a6df1b72ce7b232b2e65d 100644 --- a/net/minecraft/world/level/block/NetherrackBlock.java +++ b/net/minecraft/world/level/block/NetherrackBlock.java @@ -43,7 +43,7 @@ public class NetherrackBlock extends Block implements BonemealableBlock { @@ -1603,12 +1562,12 @@ index 24e166e399bc542522fc832064401ebb6ba2568e..f3a549712321c94b56d93c6fbba27558 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper boolean flag = false; boolean flag1 = false; diff --git a/net/minecraft/world/level/block/NyliumBlock.java b/net/minecraft/world/level/block/NyliumBlock.java -index 7357bf27bb0890cefd8f8188a7a326a631cd0ff6..0c58d2f81ce1c133ebcd4806b6d3b8d7fb663832 100644 +index 7357bf27bb0890cefd8f8188a7a326a631cd0ff6..45dd1e6c7baaf3b75340520478ec7b7fd091f604 100644 --- a/net/minecraft/world/level/block/NyliumBlock.java +++ b/net/minecraft/world/level/block/NyliumBlock.java @@ -59,7 +59,7 @@ public class NyliumBlock extends Block implements BonemealableBlock { @@ -1616,7 +1575,7 @@ index 7357bf27bb0890cefd8f8188a7a326a631cd0ff6..0c58d2f81ce1c133ebcd4806b6d3b8d7 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper BlockState blockState = level.getBlockState(pos); BlockPos blockPos = pos.above(); ChunkGenerator generator = level.getChunkSource().getGenerator(); @@ -1630,7 +1589,7 @@ index 7357bf27bb0890cefd8f8188a7a326a631cd0ff6..0c58d2f81ce1c133ebcd4806b6d3b8d7 RandomSource random, BlockPos pos diff --git a/net/minecraft/world/level/block/PitcherCropBlock.java b/net/minecraft/world/level/block/PitcherCropBlock.java -index cbaf7ea236e8689793af65f29af3f1860644d152..8c04f40b959d5ddab8170cf5975704a6ec7007c6 100644 +index cbaf7ea236e8689793af65f29af3f1860644d152..0f4e092dd8ddc273234fbe87f002ab11bb868283 100644 --- a/net/minecraft/world/level/block/PitcherCropBlock.java +++ b/net/minecraft/world/level/block/PitcherCropBlock.java @@ -129,7 +129,7 @@ public class PitcherCropBlock extends DoublePlantBlock implements BonemealableBl @@ -1656,12 +1615,12 @@ index cbaf7ea236e8689793af65f29af3f1860644d152..8c04f40b959d5ddab8170cf5975704a6 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper PitcherCropBlock.PosAndState lowerHalf = this.getLowerHalf(level, pos, state); if (lowerHalf != null) { this.grow(level, lowerHalf.state, lowerHalf.pos, 1); diff --git a/net/minecraft/world/level/block/RootedDirtBlock.java b/net/minecraft/world/level/block/RootedDirtBlock.java -index db6b32016a1ad4264da9d8812e5ea9356d0601df..2cfc2eef9764dc446ec2b465e3a9480eb51554a6 100644 +index db6b32016a1ad4264da9d8812e5ea9356d0601df..19000f09efadb6a2c616badfb837487a71168f14 100644 --- a/net/minecraft/world/level/block/RootedDirtBlock.java +++ b/net/minecraft/world/level/block/RootedDirtBlock.java @@ -32,7 +32,7 @@ public class RootedDirtBlock extends Block implements BonemealableBlock { @@ -1669,20 +1628,27 @@ index db6b32016a1ad4264da9d8812e5ea9356d0601df..2cfc2eef9764dc446ec2b465e3a9480e @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, pos.below(), Blocks.HANGING_ROOTS.defaultBlockState(), Block.UPDATE_ALL); // CraftBukkit } diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java -index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..b14f0271fc1817fa79423d1896255cb6f9c372ff 100644 +index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..779b181fb8e1a65844607a85270ec1d9b732f761 100644 --- a/net/minecraft/world/level/block/SaplingBlock.java +++ b/net/minecraft/world/level/block/SaplingBlock.java -@@ -50,38 +50,11 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { +@@ -50,38 +50,18 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { } } - public void advanceTree(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) { -+ public void advanceTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, RandomSource random) { // Paper ++ // Paper start ++ public void advanceTree(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) { // Paper ++ io.papermc.paper.util.capture.BoneMealContext mealContext = new io.papermc.paper.util.capture.BoneMealContext(); ++ mealContext.ogServerLevel = level; ++ this.advanceTree(level, pos, state, random, mealContext); ++ } ++ public void advanceTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.BoneMealContext mealContext) { ++ // Paper end if (state.getValue(STAGE) == 0) { level.setBlock(pos, state.cycle(STAGE), Block.UPDATE_NONE); } else { @@ -1714,21 +1680,23 @@ index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..b14f0271fc1817fa79423d1896255cb6 - } - } - // CraftBukkit end -+ this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random, new java.util.concurrent.atomic.AtomicReference<>()); ++ this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random, mealContext); } } -@@ -96,7 +69,7 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { +@@ -96,8 +76,8 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { } @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper - this.advanceTree(level, pos, state, random); +- this.advanceTree(level, pos, state, random); ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ this.advanceTree(level, pos, state, random, mealContext); // Paper } + @Override diff --git a/net/minecraft/world/level/block/SeaPickleBlock.java b/net/minecraft/world/level/block/SeaPickleBlock.java -index f59fb2923bbcdf9bc73747b6aa9199fef4c2319f..bea293510d219cf41f1df667c983339db6e1bff9 100644 +index f59fb2923bbcdf9bc73747b6aa9199fef4c2319f..a69f21db840abd71041d6d1c32dced0c48646fa6 100644 --- a/net/minecraft/world/level/block/SeaPickleBlock.java +++ b/net/minecraft/world/level/block/SeaPickleBlock.java @@ -130,7 +130,7 @@ public class SeaPickleBlock extends VegetationBlock implements BonemealableBlock @@ -1736,12 +1704,12 @@ index f59fb2923bbcdf9bc73747b6aa9199fef4c2319f..bea293510d219cf41f1df667c983339d @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper int i = 5; int i1 = 1; int i2 = 2; diff --git a/net/minecraft/world/level/block/SeagrassBlock.java b/net/minecraft/world/level/block/SeagrassBlock.java -index a41270e041ab408dc6ac1207f7b6ec2eaff161d4..7d23a4a28bfa654e1510b020e70f8d3095e78693 100644 +index a41270e041ab408dc6ac1207f7b6ec2eaff161d4..ddef3caad5ee406274734ee422c1f924627222c3 100644 --- a/net/minecraft/world/level/block/SeagrassBlock.java +++ b/net/minecraft/world/level/block/SeagrassBlock.java @@ -87,7 +87,7 @@ public class SeagrassBlock extends VegetationBlock implements BonemealableBlock, @@ -1749,12 +1717,12 @@ index a41270e041ab408dc6ac1207f7b6ec2eaff161d4..7d23a4a28bfa654e1510b020e70f8d30 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper BlockState blockState = Blocks.TALL_SEAGRASS.defaultBlockState(); BlockState blockState1 = blockState.setValue(TallSeagrassBlock.HALF, DoubleBlockHalf.UPPER); BlockPos blockPos = pos.above(); diff --git a/net/minecraft/world/level/block/ShortDryGrassBlock.java b/net/minecraft/world/level/block/ShortDryGrassBlock.java -index 1df47e0ea401267027721342aaf26639b2622e13..6435e8c2ab68b9bca861134de05db965ef100d4b 100644 +index 1df47e0ea401267027721342aaf26639b2622e13..a0188c77806f4fe21cffcb43aa83cde6c14442e3 100644 --- a/net/minecraft/world/level/block/ShortDryGrassBlock.java +++ b/net/minecraft/world/level/block/ShortDryGrassBlock.java @@ -47,7 +47,7 @@ public class ShortDryGrassBlock extends DryVegetationBlock implements Bonemealab @@ -1762,12 +1730,12 @@ index 1df47e0ea401267027721342aaf26639b2622e13..6435e8c2ab68b9bca861134de05db965 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper level.setBlockAndUpdate(pos, Blocks.TALL_DRY_GRASS.defaultBlockState()); } } diff --git a/net/minecraft/world/level/block/SmallDripleafBlock.java b/net/minecraft/world/level/block/SmallDripleafBlock.java -index d5821239d33aad9213b9a87d225e942934623857..63a43f7eecd2dcf38dc12c8f6b7bb28ac9941f99 100644 +index d5821239d33aad9213b9a87d225e942934623857..18316ed939130115890c5ce4cd9da502a98cf057 100644 --- a/net/minecraft/world/level/block/SmallDripleafBlock.java +++ b/net/minecraft/world/level/block/SmallDripleafBlock.java @@ -64,7 +64,7 @@ public class SmallDripleafBlock extends DoublePlantBlock implements Bonemealable @@ -1784,7 +1752,7 @@ index d5821239d33aad9213b9a87d225e942934623857..63a43f7eecd2dcf38dc12c8f6b7bb28a @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper if (state.getValue(DoublePlantBlock.HALF) == DoubleBlockHalf.LOWER) { BlockPos blockPos = pos.above(); level.setBlock(blockPos, level.getFluidState(blockPos).createLegacyBlock(), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE); @@ -1792,12 +1760,12 @@ index d5821239d33aad9213b9a87d225e942934623857..63a43f7eecd2dcf38dc12c8f6b7bb28a } else { BlockPos blockPos = pos.below(); - this.performBonemeal(level, random, blockPos, level.getBlockState(blockPos)); -+ this.performBonemeal(player, level, random, blockPos, level.getBlockState(blockPos)); // Paper ++ this.performBonemeal(level, random, blockPos, level.getBlockState(blockPos), mealContext); // Paper } } diff --git a/net/minecraft/world/level/block/StemBlock.java b/net/minecraft/world/level/block/StemBlock.java -index 82ea0ce3895108d477d10e901e756a27a3a9cedc..1498804e90b7a4a0799f8e60c016caf2afbebda8 100644 +index 82ea0ce3895108d477d10e901e756a27a3a9cedc..14694eaea6d2f27401c3e656f7e9e9133f215494 100644 --- a/net/minecraft/world/level/block/StemBlock.java +++ b/net/minecraft/world/level/block/StemBlock.java @@ -113,12 +113,12 @@ public class StemBlock extends VegetationBlock implements BonemealableBlock { @@ -1806,7 +1774,7 @@ index 82ea0ce3895108d477d10e901e756a27a3a9cedc..1498804e90b7a4a0799f8e60c016caf2 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { - int min = Math.min(7, state.getValue(AGE) + Mth.nextInt(level.random, 2, 5)); -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper + int min = Math.min(7, state.getValue(AGE) + Mth.nextInt(level.getRandom(), 2, 5)); // Paper BlockState blockState = state.setValue(AGE, min); org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, blockState, Block.UPDATE_CLIENTS); // CraftBukkit @@ -1830,7 +1798,7 @@ index 5b19c117d7935c6b0c3887083f63cc293bc495e3..9fcbe1a3d741852674ed8942e10bc0ca if (placer != null) { BlockEntity blockEntity = level.getBlockEntity(pos); diff --git a/net/minecraft/world/level/block/SweetBerryBushBlock.java b/net/minecraft/world/level/block/SweetBerryBushBlock.java -index 3d2bd187cb9f83f9369d11b21c484c21ceba0569..abcc4dafd079b454855158e95e08ee0d2e473197 100644 +index 3d2bd187cb9f83f9369d11b21c484c21ceba0569..b6aadb943a4b5c04a39f1700a508c22df0464770 100644 --- a/net/minecraft/world/level/block/SweetBerryBushBlock.java +++ b/net/minecraft/world/level/block/SweetBerryBushBlock.java @@ -160,7 +160,7 @@ public class SweetBerryBushBlock extends VegetationBlock implements Bonemealable @@ -1838,12 +1806,12 @@ index 3d2bd187cb9f83f9369d11b21c484c21ceba0569..abcc4dafd079b454855158e95e08ee0d @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper int min = Math.min(3, state.getValue(AGE) + 1); level.setBlock(pos, state.setValue(AGE, min), Block.UPDATE_CLIENTS); } diff --git a/net/minecraft/world/level/block/TallDryGrassBlock.java b/net/minecraft/world/level/block/TallDryGrassBlock.java -index f0514cd9df52b3a459ff92812ae5ad9b0df85763..d634457bdb2d376f9dc7328b29a76cc3b3b90555 100644 +index f0514cd9df52b3a459ff92812ae5ad9b0df85763..53a8c2130037a1de75f8cc9b63f035f7a62b4bea 100644 --- a/net/minecraft/world/level/block/TallDryGrassBlock.java +++ b/net/minecraft/world/level/block/TallDryGrassBlock.java @@ -47,7 +47,7 @@ public class TallDryGrassBlock extends DryVegetationBlock implements Bonemealabl @@ -1851,12 +1819,12 @@ index f0514cd9df52b3a459ff92812ae5ad9b0df85763..d634457bdb2d376f9dc7328b29a76cc3 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper BonemealableBlock.findSpreadableNeighbourPos(level, pos, Blocks.SHORT_DRY_GRASS.defaultBlockState()) .ifPresent(blockPos -> level.setBlockAndUpdate(blockPos, Blocks.SHORT_DRY_GRASS.defaultBlockState())); } diff --git a/net/minecraft/world/level/block/TallFlowerBlock.java b/net/minecraft/world/level/block/TallFlowerBlock.java -index 4be72e53848b13eb68a2149749a88a83a4cb1e5c..9598bf91cb3c41b0e73a990a7d03fc595a530e2f 100644 +index 4be72e53848b13eb68a2149749a88a83a4cb1e5c..365092462b7963f273297f3f9ae56291b1a6b056 100644 --- a/net/minecraft/world/level/block/TallFlowerBlock.java +++ b/net/minecraft/world/level/block/TallFlowerBlock.java @@ -33,7 +33,7 @@ public class TallFlowerBlock extends DoublePlantBlock implements BonemealableBlo @@ -1865,12 +1833,12 @@ index 4be72e53848b13eb68a2149749a88a83a4cb1e5c..9598bf91cb3c41b0e73a990a7d03fc59 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { - popResource(level, pos, new ItemStack(this)); -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper + level.addTask((serverLevel) -> popResource(serverLevel, pos, new ItemStack(this))); } } diff --git a/net/minecraft/world/level/block/TallGrassBlock.java b/net/minecraft/world/level/block/TallGrassBlock.java -index 6c5259371fb8e2e131b2c69f354521b1e902ac4e..ee1602e5ac3c41b716ff5b1dec3a52c8fd480327 100644 +index 6c5259371fb8e2e131b2c69f354521b1e902ac4e..195bf95f843f459fab58382e5ceba2077c465dd7 100644 --- a/net/minecraft/world/level/block/TallGrassBlock.java +++ b/net/minecraft/world/level/block/TallGrassBlock.java @@ -41,7 +41,7 @@ public class TallGrassBlock extends VegetationBlock implements BonemealableBlock @@ -1878,7 +1846,7 @@ index 6c5259371fb8e2e131b2c69f354521b1e902ac4e..ee1602e5ac3c41b716ff5b1dec3a52c8 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(net.minecraft.server.level.ServerPlayer player, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper DoublePlantBlock.placeAt(level, getGrownBlock(state).defaultBlockState(), pos, Block.UPDATE_CLIENTS); } @@ -1956,48 +1924,55 @@ index c57cb7b47a01e313a1be5de769c6766ac2787d95..f34731580bc880dcc5d3e96923a7f2b4 } } diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java -index 5471619a0484ece08640e2b3fd26746c351dc3e0..dd8ebb1fca73e2927bb61a9c330a58ec09288c46 100644 +index 5471619a0484ece08640e2b3fd26746c351dc3e0..04663ea08fd6b849622e4908edee1562550bf63c 100644 --- a/net/minecraft/world/level/block/grower/TreeGrower.java +++ b/net/minecraft/world/level/block/grower/TreeGrower.java -@@ -122,7 +122,27 @@ public final class TreeGrower { +@@ -122,7 +122,34 @@ public final class TreeGrower { return this.secondaryMegaTree.isPresent() && random.nextFloat() < this.secondaryChance ? this.secondaryMegaTree.get() : this.megaTree.orElse(null); } - public boolean growTree(ServerLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random) { + // TODO: SUPPORT WRAPPING + // Paper start -+// public boolean growTree(ServerLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random) { -+// io.papermc.paper.util.capture.MinecraftCaptureBridge captureTreeGeneration = new io.papermc.paper.util.capture.MinecraftCaptureBridge(level); -+// java.util.concurrent.atomic.AtomicReference treeType = new java.util.concurrent.atomic.AtomicReference<>(); -+// if (this.growTree(captureTreeGeneration, chunkGenerator, pos, state, random, treeType)) { -+// var states = captureTreeGeneration.calculateLatestBlockStates(level); -+// org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level); -+// java.util.List blocks = new java.util.ArrayList<>(states.values()); -+// org.bukkit.event.world.StructureGrowEvent event = new org.bukkit.event.world.StructureGrowEvent(location, treeType.get(), false, null, blocks); -+// -+// if (event.callEvent()) { -+// captureTreeGeneration.applyTasks(); -+// return true; -+// } -+// } -+// -+// return false; -+// } -+ public boolean growTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random, java.util.concurrent.atomic.AtomicReference treeType) { ++ public boolean growTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random) { ++ return this.growTree0(level, chunkGenerator, pos, state, random, null); ++ } ++ ++ public boolean growTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.BoneMealContext mealContext) { ++ try (io.papermc.paper.util.capture.SimpleBlockCapture capture = level.fork()) { ++ io.papermc.paper.util.capture.MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); ++ if (this.growTree0(captureTreeGeneration, chunkGenerator, pos, state, random, mealContext.treeHook)) { ++ var states = captureTreeGeneration.calculateLatestBlockStates(captureTreeGeneration, mealContext.ogServerLevel); ++ org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, mealContext.ogServerLevel); ++ java.util.List blocks = new java.util.ArrayList<>(states.values()); ++ org.bukkit.event.world.StructureGrowEvent event = new org.bukkit.event.world.StructureGrowEvent(location, mealContext.treeHook.get(), false, mealContext.getBukkitPlayer(), blocks); ++ ++ if (event.callEvent()) { ++ captureTreeGeneration.applyTasks(); ++ return true; ++ } ++ } ++ ++ } ++ ++ ++ return false; ++ } ++ private boolean growTree0(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random, java.util.concurrent.atomic.AtomicReference treeType) { + // Paper end ResourceKey> configuredMegaFeature = this.getConfiguredMegaFeature(random); if (configuredMegaFeature != null) { Holder> holder = level.registryAccess() -@@ -130,7 +150,7 @@ public final class TreeGrower { +@@ -130,7 +157,7 @@ public final class TreeGrower { .get(configuredMegaFeature) .orElse(null); if (holder != null) { - this.setTreeType(holder); // CraftBukkit -+ treeType.set(this.getTreeType(holder)); // Paper ++ if (treeType != null) treeType.set(this.getTreeType(holder)); // Paper for (int i = 0; i >= -1; i--) { for (int i1 = 0; i1 >= -1; i1--) { if (isTwoByTwoSapling(state, level, pos, i, i1)) { -@@ -163,7 +183,7 @@ public final class TreeGrower { +@@ -163,7 +190,7 @@ public final class TreeGrower { if (holder1 == null) { return false; } else { @@ -2006,7 +1981,7 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..dd8ebb1fca73e2927bb61a9c330a58ec ConfiguredFeature configuredFeature2 = holder1.value(); BlockState blockState1 = level.getFluidState(pos).createLegacyBlock(); level.setBlock(pos, blockState1, Block.UPDATE_NONE); -@@ -200,53 +220,53 @@ public final class TreeGrower { +@@ -200,53 +227,53 @@ public final class TreeGrower { } // CraftBukkit start diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java b/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java new file mode 100644 index 000000000000..a83fae0262ed --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java @@ -0,0 +1,23 @@ +package io.papermc.paper.util.capture; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.entity.Player; +import org.jspecify.annotations.Nullable; + +import java.util.concurrent.atomic.AtomicReference; + +public class BoneMealContext { + + public ServerLevel ogServerLevel; + public ServerPlayer player; + public boolean precancelStructureEvent = true; + public java.util.concurrent.atomic.AtomicReference treeHook = new AtomicReference<>(); // just make this a field + + + @Nullable + public Player getBukkitPlayer() { + return this.player == null ? null : this.player.getBukkitEntity(); + } + +} diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java index e922290a2a0e..f22192cd7151 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java @@ -11,9 +11,11 @@ import org.jspecify.annotations.Nullable; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -29,83 +31,42 @@ */ public final class CaptureRecordMap { - private final Map> recordsByPos = new HashMap<>(); - private final ArrayDeque orderedRecords = new ArrayDeque<>(); - - interface BlockPlacement { - void applyApiPatch(ServerLevel serverLevel); - - BlockPos pos(); - } - - - public record BlockEntityRecord(boolean remove, @Nullable BlockEntity add, BlockPos pos) implements BlockPlacement { - - // If we have a block entity that we change, apply to the server level. - @Override - public void applyApiPatch(final ServerLevel level) { - if (this.remove) { - level.removeBlockEntity(this.pos); - return; - } - if (this.add != null) { - level.setBlockEntity(this.add); - } - } - } - - public record BlockStateRecord(BlockState blockState, int flags, BlockPos pos) implements BlockPlacement { - - // If we have a block state that we change, insert in without updating anything else about the world. - @Override - public void applyApiPatch(final ServerLevel level) { - level.setBlock(this.pos, this.blockState, UPDATE_NONE | net.minecraft.world.level.block.Block.UPDATE_SKIP_ON_PLACE); - } - } + private final Map recordsByPos = new HashMap<>(); public void setLatestBlockStateAt(final BlockPos pos, final BlockState state, final int flags) { - this.add(new BlockStateRecord(state, flags, pos)); + this.add(new CaptureRecord(state, pos)); } public void setLatestBlockEntityAt(final BlockPos pos, final boolean remove, @Nullable final BlockEntity add) { - this.add(new BlockEntityRecord(remove, add, pos)); + this.add(new CaptureRecord(remove, add, pos)); } - private void add(final BlockPlacement record) { - this.recordsByPos.computeIfAbsent(record.pos(), k -> new ArrayDeque<>()).addLast(record); - this.orderedRecords.addLast(record); + private void add(final CaptureRecord record) { + this.recordsByPos.put(record.pos, record); } public boolean isEmpty() { - return this.orderedRecords.isEmpty(); + return this.recordsByPos.isEmpty(); } public @Nullable BlockState getLatestBlockStateAt(final BlockPos pos) { - final Deque dq = this.recordsByPos.get(pos); - if (dq == null) return null; - - for (BlockPlacement record : this.of(dq.descendingIterator())) { - if (record instanceof BlockStateRecord bsr) { - return bsr.blockState(); - } + CaptureRecord record = this.recordsByPos.get(pos); + if (record == null) { + return null; } - return null; + return record.state; } // Null indicates that its not present, no override // Optional empty indicates its being removed public @Nullable Optional<@Nullable BlockEntity> getLatestBlockEntityAt(final BlockPos pos) { - final Deque dq = this.recordsByPos.get(pos); - if (dq == null) return null; - - for (BlockPlacement record : this.of(dq.descendingIterator())) { - if (record instanceof BlockEntityRecord ber) { - return ber.remove() ? Optional.empty() : Optional.of(ber.add()); - } + CaptureRecord record = this.recordsByPos.get(pos); + if (record == null) { + return null; } - return null; + return Optional.ofNullable(record.blockEntity); } public void applyBlockEntities(ServerLevel parent) { @@ -118,57 +79,60 @@ public void applyBlockEntities(ServerLevel parent) { } public void applyApiPatch(final ServerLevel level) { - for (final BlockPlacement record : this.orderedRecords) { - record.applyApiPatch(level); - } + this.recordsByPos.keySet().forEach((pos) -> { + this.recordsByPos.get(pos).applyApiPatch(level); + }); } // TODO: Clean this up - public Map calculateLatestBlockStates(final ServerLevel level) { + public Map calculateLatestBlockStates(PaperCapturingWorldLevel predictor, ServerLevel level) { final Map out = new HashMap<>(); - for (final Map.Entry> entry : this.recordsByPos.entrySet()) { - final BlockPos pos = entry.getKey(); - final ArrayDeque dq = entry.getValue(); + this.recordsByPos.keySet().forEach((pos) -> { + + CaptureRecord captureRecord = this.recordsByPos.get(pos); + out.put(CraftLocation.toBukkit(pos), CraftBlockStates.getBlockState(level.getWorld(), CraftLocation.toBlockPosition(CraftLocation.toBukkit(pos)), captureRecord.state, captureRecord.blockEntity)); + }); - BlockState latestState = null; - BlockEntity latestBe = null; - boolean beKnown = false; + return out; + } - final Iterator it = dq.descendingIterator(); - while (it.hasNext() && (latestState == null || !beKnown)) { - final BlockPlacement r = it.next(); - if (latestState == null && r instanceof BlockStateRecord bsr) { - latestState = bsr.blockState(); - } - if (!beKnown && r instanceof BlockEntityRecord ber) { - beKnown = true; - latestBe = ber.remove() ? null : ber.add(); - } - } + public class CaptureRecord { - if (latestState == null) { - continue; - } + private final BlockPos pos; - if (!beKnown) { - latestBe = level.getBlockEntity(pos); - } + private BlockState state; + private BlockEntity blockEntity; + private boolean removeBe; - out.put(CraftLocation.toBukkit(pos), CraftBlockStates.getBlockState(level.getWorld(), CraftLocation.toBlockPosition(CraftLocation.toBukkit(pos)), latestState, latestBe)); + public CaptureRecord(BlockPos pos, BlockState state, BlockEntity blockEntity) { + this.pos = pos; + this.state = state; + this.blockEntity = blockEntity; } - return out; - } + public CaptureRecord(BlockState state, BlockPos pos) { + this.pos = pos; + this.state = state; + } + + public CaptureRecord(boolean remove, @Nullable BlockEntity add, BlockPos pos) { + this.removeBe = remove; + this.blockEntity = add; + this.pos = pos; + } - private Iterable of(Iterator of) { - return new Iterable<>() { - @Override - public @NotNull Iterator iterator() { - return of; + public void applyApiPatch(ServerLevel level) { + if (this.removeBe) { + level.removeBlockEntity(this.pos); + } + + level.setBlock(this.pos, this.state, UPDATE_NONE | net.minecraft.world.level.block.Block.UPDATE_SKIP_ON_PLACE); + if (this.blockEntity != null) { + level.setBlockEntity(this.blockEntity); } - }; + } } } \ No newline at end of file diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java b/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java index a83d0b2c649f..3a59b1163dc2 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java @@ -7,7 +7,7 @@ import java.util.Optional; -public record LiveBlockPlacementLayer(ServerLevel level) implements BlockPlacementPredictor{ +public record LiveBlockPlacementLayer(ServerLevel level) implements BlockPlacementPredictor { @Override public Optional getLatestBlockAt(BlockPos pos) { return Optional.of(this.level.getBlockState(pos)); diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java index a4fd9fc6b839..e20c1b762bc0 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java @@ -25,7 +25,6 @@ import net.minecraft.world.level.border.WorldBorder; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; -import net.minecraft.world.level.chunk.ChunkSource; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.entity.EntityTypeTest; @@ -57,28 +56,24 @@ public class MinecraftCaptureBridge implements PaperCapturingWorldLevel { private final ServerLevel parent; - - private BlockPlacementPredictor activeReadLayer; - private BlockPlacementPredictor predictiveReadLayer; - + private final List queuedTasks = new ArrayList<>(); + private final BlockPlacementPredictor activeReadLayer; + private final BlockPlacementPredictor predictiveReadLayer; private SimpleBlockPlacementPredictor writeLayer; - - - private SimpleBlockPlacementPredictor legacyStorage; + private final SimpleBlockPlacementPredictor legacyStorage; private boolean isLegacyPredicting = false; - - private final List queuedTasks = new ArrayList<>(); - - private CapturingTickAccess blocks; - private CapturingTickAccess liquids; + private final CapturingTickAccess blocks; + private final CapturingTickAccess liquids; private Consumer sink = queuedTasks::add; public MinecraftCaptureBridge(ServerLevel parent) { - this.parent = parent; + this(parent, new LiveBlockPlacementLayer(parent)); + } - LiveBlockPlacementLayer liveBlockPlacementLayer = new LiveBlockPlacementLayer(this.parent); + public MinecraftCaptureBridge(ServerLevel parent, BlockPlacementPredictor liveBlockPlacementLayer) { + this.parent = parent; SimpleBlockPlacementPredictor blockPlacementStorage = new SimpleBlockPlacementPredictor(liveBlockPlacementLayer); this.legacyStorage = new SimpleBlockPlacementPredictor(liveBlockPlacementLayer); @@ -150,6 +145,27 @@ public ChunkGenerator getGenerator() { return this.parent.getGenerator(); } + @Override + public SimpleBlockCapture fork() { + return this.parent.capturer.createCaptureSession(new BlockPlacementPredictor() { + @Override + public Optional getLatestBlockAt(BlockPos pos) { + return MinecraftCaptureBridge.this.activeReadLayer.getLatestBlockAt(pos); + } + + @Override + public Optional getLatestBlockAtIfLoaded(BlockPos pos) { + return MinecraftCaptureBridge.this.activeReadLayer.getLatestBlockAtIfLoaded(pos); + } + + @Override + public Optional getLatestTileAt(BlockPos pos) { + return MinecraftCaptureBridge.this.activeReadLayer.getLatestTileAt(pos); + + } + }); + } + @Override public RandomSource getRandom() { // TODO: Support rolling this back? @@ -327,7 +343,7 @@ public boolean removeBlock(BlockPos pos, boolean movedByPiston) { @Override public boolean destroyBlock(BlockPos pos, boolean dropBlock, @Nullable Entity entity, int recursionLeft) { BlockPos copy = pos.immutable(); - this.addTask((serverLevel) -> serverLevel.destroyBlock(copy, dropBlock, entity, recursionLeft)); + this.addTask((serverLevel) -> serverLevel.destroyBlock(copy, dropBlock, entity, recursionLeft)); BlockState blockState = this.getBlockState(copy); if (blockState.isAir()) { @@ -389,8 +405,8 @@ public net.minecraft.world.level.block.state.BlockState getLatestBlockState(fina return placement.map(BlockPlacementPredictor.BlockEntityPlacement::blockEntity); } - public Map calculateLatestBlockStates(ServerLevel level) { - return this.writeLayer.getRecordMap().calculateLatestBlockStates(level); + public Map calculateLatestBlockStates(PaperCapturingWorldLevel predictor, ServerLevel level) { + return this.writeLayer.getRecordMap().calculateLatestBlockStates(predictor, level); } public void applyTasks() { @@ -457,7 +473,8 @@ public void apply() { } } - public record LegacyLayer(BlockPlacementPredictor predictor, BooleanSupplier cond) implements BlockPlacementPredictor { + public record LegacyLayer(BlockPlacementPredictor predictor, + BooleanSupplier cond) implements BlockPlacementPredictor { @Override public Optional getLatestBlockAt(BlockPos pos) { diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java b/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java index 92271e482873..2be61bb6f421 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java @@ -28,5 +28,7 @@ public interface PaperCapturingWorldLevel extends WorldGenLevel { WorldConfiguration paperConfig(); ChunkGenerator getGenerator(); + + SimpleBlockCapture fork(); } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java b/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java index 48949bddc3a2..b9a31db97639 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java @@ -53,4 +53,9 @@ default WorldConfiguration paperConfig() { default ChunkGenerator getGenerator() { return this.handle().getGenerator(); } + + @Override + default SimpleBlockCapture fork() { + return this.handle().capturer.createCaptureSession(); + } } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java index 2c94e7fdd312..bba63d1e24ac 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java @@ -10,19 +10,24 @@ import java.util.Map; import java.util.Optional; -public class SimpleBlockCapture implements AutoCloseable { +public class SimpleBlockCapture implements AutoCloseable { private final MinecraftCaptureBridge capturingWorldLevel; private final ServerLevel level; private boolean openLegacyCapturing = false; - public SimpleBlockCapture(WorldCapturer blockCapturer, ServerLevel world) { - this.capturingWorldLevel = new MinecraftCaptureBridge(world); + + private final SimpleBlockCapture oldCapture; + + + public SimpleBlockCapture(BlockPlacementPredictor base, ServerLevel world, SimpleBlockCapture oldCapture) { + this.capturingWorldLevel = new MinecraftCaptureBridge(world, base); this.level = world; + this.oldCapture = oldCapture; } - public PaperCapturingWorldLevel capturingWorldLevel() { + public MinecraftCaptureBridge capturingWorldLevel() { return this.capturingWorldLevel; } @@ -31,7 +36,7 @@ public boolean isCapturing() { } public Map getCapturedBlockStates() { - return this.capturingWorldLevel.calculateLatestBlockStates(level); + return this.capturingWorldLevel.calculateLatestBlockStates(this.capturingWorldLevel, level); } public net.minecraft.world.level.block.state.BlockState getCaptureBlockState(final BlockPos pos) { @@ -46,7 +51,6 @@ public net.minecraft.world.level.block.state.BlockState getCaptureBlockState(fin public void openLegacySupport() { this.openLegacyCapturing = true; this.capturingWorldLevel.activateLegacyCapture(); - } public boolean isViewingCaptureState() { @@ -54,13 +58,13 @@ public boolean isViewingCaptureState() { } public void finalizePlacement() { - this.level.capturer.releaseCapture(); + this.level.capturer.releaseCapture(this.oldCapture); this.capturingWorldLevel.applyTasks(); } @Override public void close() { - this.level.capturer.releaseCapture(); + this.level.capturer.releaseCapture(this.oldCapture); } public net.minecraft.world.level.block.state.BlockState getCaptureBlockStateIfLoaded(BlockPos pos) { diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java index 36225ab83257..4032cdc9e5f4 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java @@ -58,7 +58,7 @@ public boolean setBlockState(BlockPos pos, BlockState state, int flags) { } if (blockEntity == null) { - blockEntity = ((EntityBlock)block).newBlockEntity(pos, state); + blockEntity = ((EntityBlock) block).newBlockEntity(pos, state); if (blockEntity != null) { this.addAndRegisterBlockEntity(blockEntity); } @@ -109,7 +109,6 @@ public Optional getLatestBlockAtIfLoaded(BlockPos pos) { } - public void setLatestBlockAt(BlockPos pos, BlockState data, int flags) { this.guesstimationMap.setLatestBlockStateAt(pos, data, flags); } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java b/paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java index acef54731d3d..bfaa12220b83 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java @@ -3,8 +3,6 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; -import java.util.function.Consumer; - // TODO: Cleanup this state, because its held on the server world. I had proper state handling but threw it away at some point public class WorldCapturer { @@ -16,14 +14,18 @@ public WorldCapturer(Level world) { this.world = (ServerLevel) world; } - public SimpleBlockCapture createCaptureSession() { - this.capture = new SimpleBlockCapture(this, world); + public SimpleBlockCapture createCaptureSession(BlockPlacementPredictor blockPlacementPredictor) { + this.capture = new SimpleBlockCapture(blockPlacementPredictor, world, this.capture); return this.capture; } - public void releaseCapture() { - this.capture = null; + public SimpleBlockCapture createCaptureSession() { + return this.createCaptureSession(new LiveBlockPlacementLayer(this.world)); + } + + public void releaseCapture(SimpleBlockCapture oldCapture) { + this.capture = oldCapture; } public SimpleBlockCapture getCapture() { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index aaaa1126fa36..690d9ec1e375 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -13,6 +13,8 @@ import io.papermc.paper.raytracing.RayTraceTarget; import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.util.capture.MinecraftCaptureBridge; +import io.papermc.paper.util.capture.SimpleBlockCapture; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import java.nio.file.Path; import java.util.ArrayList; @@ -124,6 +126,7 @@ import org.bukkit.craftbukkit.util.CraftRayTraceResult; import org.bukkit.craftbukkit.util.CraftSpawnCategory; import org.bukkit.craftbukkit.util.CraftStructureSearchResult; +import org.bukkit.craftbukkit.util.RandomSourceWrapper; import org.bukkit.entity.AbstractArrow; import org.bukkit.entity.Arrow; import org.bukkit.entity.Entity; @@ -139,6 +142,7 @@ import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; import org.bukkit.event.weather.LightningStrikeEvent; import org.bukkit.event.world.SpawnChangeEvent; +import org.bukkit.event.world.StructureGrowEvent; import org.bukkit.event.world.TimeSkipEvent; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BlockPopulator; @@ -738,27 +742,22 @@ public boolean generateTree(Location loc, TreeType type) { @Override public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { -// this.world.captureTreeGeneration = true; -// this.world.captureBlockStates = true; -// boolean grownTree = this.generateTree(loc, type); -// this.world.captureBlockStates = false; -// this.world.captureTreeGeneration = false; -// if (grownTree) { // Copy block data to delegate -// for (BlockState blockstate : this.world.capturedBlockStates.values()) { -// BlockPos position = ((CraftBlockState) blockstate).getPosition(); -// net.minecraft.world.level.block.state.BlockState oldBlock = this.world.getBlockState(position); -// int flags = ((CraftBlockState) blockstate).getFlags(); -// delegate.setBlockData(blockstate.getX(), blockstate.getY(), blockstate.getZ(), blockstate.getBlockData()); -// net.minecraft.world.level.block.state.BlockState newBlock = this.world.getBlockState(position); -// this.world.notifyAndUpdatePhysics(position, null, oldBlock, newBlock, newBlock, flags, net.minecraft.world.level.block.Block.UPDATE_LIMIT); -// } -// this.world.capturedBlockStates.clear(); -// return true; -// } else { -// this.world.capturedBlockStates.clear(); -// return false; -// } - return false; + try (SimpleBlockCapture capture = this.world.fork()) { + MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); + + BlockPos pos = CraftLocation.toBlockPosition(loc); + boolean res = this.generateTree(captureTreeGeneration, this.getHandle().getMinecraftWorld().getChunkSource().getGenerator(), pos, new RandomSourceWrapper(CraftWorld.rand), type); + if (res) { + var states = captureTreeGeneration.calculateLatestBlockStates(captureTreeGeneration, this.world); + + java.util.List blocks = new java.util.ArrayList<>(states.values()); + for (BlockState state : blocks) { + delegate.setBlockData(state.getX(), state.getY(), state.getZ(), state.getBlockData()); + } + } + + return res; + } } @Override diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java index 8082dc1ba85f..cd24e43b5d2f 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java @@ -549,42 +549,10 @@ public boolean breakNaturally(ItemStack item, boolean triggerEffect, boolean dro @Override public boolean applyBoneMeal(BlockFace face) { Direction direction = CraftBlock.blockFaceToNotch(face); - BlockFertilizeEvent event = null; ServerLevel world = this.getCraftWorld().getHandle(); UseOnContext context = new UseOnContext(world, null, InteractionHand.MAIN_HAND, Items.BONE_MEAL.getDefaultInstance(), new BlockHitResult(Vec3.ZERO, direction, this.getPosition(), false)); -// -// // SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent -// world.captureTreeGeneration = true; -// InteractionResult result = BoneMealItem.applyBonemeal(context); -// world.captureTreeGeneration = false; -// -// if (!world.capturedBlockStates.isEmpty()) { -// TreeType treeType = SaplingBlock.treeType; -// SaplingBlock.treeType = null; -// List states = new ArrayList<>(world.capturedBlockStates.values()); -// world.capturedBlockStates.clear(); -// StructureGrowEvent structureEvent = null; -// -// if (treeType != null) { -// structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, states); -// Bukkit.getPluginManager().callEvent(structureEvent); -// } -// -// event = new BlockFertilizeEvent(CraftBlock.at(world, this.getPosition()), null, states); -// event.setCancelled(structureEvent != null && structureEvent.isCancelled()); -// Bukkit.getPluginManager().callEvent(event); -// -// if (!event.isCancelled()) { -// for (BlockState state : states) { -// CraftBlockState craftBlockState = (CraftBlockState) state; -// craftBlockState.place(craftBlockState.getFlags()); -// world.checkCapturedTreeStateForObserverNotify(this.position, craftBlockState); // Paper - notify observers even if grow failed -// } -// } -// } - - // return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled()); - return false; + + return BoneMealItem.applyBonemeal(context) == InteractionResult.SUCCESS; } @Override diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index ea3abb465259..3a709c21e541 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -25,7 +25,9 @@ import io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent; import io.papermc.paper.event.entity.ItemTransportingEntityValidateTargetEvent; import io.papermc.paper.event.player.PlayerBedFailEnterEvent; +import io.papermc.paper.util.capture.MinecraftCaptureBridge; import io.papermc.paper.util.capture.PaperCapturingWorldLevel; +import io.papermc.paper.util.capture.SimpleBlockCapture; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.network.Connection; @@ -2393,43 +2395,46 @@ public static boolean sendChestLockedNotifications(Vec3 pos) { return false; } - public static boolean structureEvent(ServerLevel level, Player player, BlockPos pos, Function worldgenCapture, TreeType type) { - io.papermc.paper.util.capture.MinecraftCaptureBridge captureTreeGeneration = new io.papermc.paper.util.capture.MinecraftCaptureBridge(level); - if (worldgenCapture.apply(captureTreeGeneration)) { - var states = captureTreeGeneration.calculateLatestBlockStates(level); - org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level); + public static boolean structureEvent(ServerLevel serverLevel, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, Player player, BlockPos pos, Function worldgenCapture, TreeType type) { + try (SimpleBlockCapture capture = level.fork()) { + MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); + if (worldgenCapture.apply(captureTreeGeneration)) { + var states = captureTreeGeneration.calculateLatestBlockStates(level, serverLevel); + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, serverLevel); - java.util.List blocks = new java.util.ArrayList<>(states.values()); - StructureGrowEvent structureEvent = new StructureGrowEvent(location, type, false, player, blocks); + java.util.List blocks = new java.util.ArrayList<>(states.values()); + StructureGrowEvent structureEvent = new StructureGrowEvent(location, type, false, player, blocks); - if (structureEvent.callEvent()) { - captureTreeGeneration.applyTasks(); - return true; + if (structureEvent.callEvent()) { + capture.finalizePlacement(); + return true; + } } } + return false; } - public static boolean fertilizeBlock(ServerLevel level, Player player, BlockPos pos, Consumer worldgenCapture) { - io.papermc.paper.util.capture.MinecraftCaptureBridge captureTreeGeneration = new io.papermc.paper.util.capture.MinecraftCaptureBridge(level); - worldgenCapture.accept(captureTreeGeneration); - if (true) { - var states = captureTreeGeneration.calculateLatestBlockStates(level); + public static boolean fertilizeBlock(ServerLevel level, Player player, BlockPos pos, Consumer worldgenCapture, boolean cancelled) { + try (SimpleBlockCapture capture = level.fork()) { + MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); + + worldgenCapture.accept(captureTreeGeneration); + var states = captureTreeGeneration.calculateLatestBlockStates(captureTreeGeneration, level); org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level); java.util.List blocks = new java.util.ArrayList<>(states.values()); BlockFertilizeEvent structureEvent = new BlockFertilizeEvent(location.getBlock(), player, blocks); + structureEvent.setCancelled(cancelled); if (structureEvent.callEvent()) { - captureTreeGeneration.applyTasks(); + capture.finalizePlacement(); return true; } } - - return false; } } diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java index 1a497438e14f..b08c38cf1ff7 100644 --- a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java +++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java @@ -1,16 +1,25 @@ package io.papermc.testplugin; +import org.bukkit.BlockChangeDelegate; +import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.TreeType; +import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; import org.bukkit.block.Chest; +import org.bukkit.block.data.BlockData; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockFertilizeEvent; import org.bukkit.event.block.BlockMultiPlaceEvent; import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.player.PlayerSwapHandItemsEvent; +import org.bukkit.event.world.StructureGrowEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitRunnable; +import org.jetbrains.annotations.NotNull; import java.util.function.Consumer; @@ -25,7 +34,24 @@ public void onEnable() { @EventHandler public void blockPlace(BlockFertilizeEvent event) { - //event.setCancelled(true); + event.setCancelled(true); + + Bukkit.getPlayer("Owen1212055").sendBlockChanges(event.getBlocks()); + + new BukkitRunnable(){ + + @Override + public void run() { + event.getBlocks().forEach((state) -> Bukkit.getPlayer("Owen1212055").sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData())); + } + }.runTaskLater(this, 20 * 2); + + } + + + @EventHandler + public void blockPlace(StructureGrowEvent event) { + event.setCancelled(true); event.getPlayer().sendBlockChanges(event.getBlocks()); @@ -36,7 +62,14 @@ public void run() { event.getBlocks().forEach((state) -> event.getPlayer().sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData())); } }.runTaskLater(this, 20 * 2); + } + + + @EventHandler + public void blockPlace(PlayerSwapHandItemsEvent event) { + event.getPlayer().getTargetBlockExact(5).applyBoneMeal(BlockFace.UP); + event.getPlayer().getWorld().generateTree(event.getPlayer().getLocation(), TreeType.values()[(int) (TreeType.values().length * Math.random())]); } @EventHandler From d55864cc098b19e936dfe2f330ad72136302c809 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Fri, 6 Feb 2026 10:08:51 -0500 Subject: [PATCH 04/17] Fix block entity placement --- .../paper/util/capture/CaptureRecordMap.java | 11 ++++- .../io/papermc/testplugin/TestPlugin.java | 44 +++++++++---------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java index f22192cd7151..e51ac0d0bb40 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java @@ -38,7 +38,11 @@ public void setLatestBlockStateAt(final BlockPos pos, final BlockState state, fi } public void setLatestBlockEntityAt(final BlockPos pos, final boolean remove, @Nullable final BlockEntity add) { - this.add(new CaptureRecord(remove, add, pos)); + + CaptureRecord oldRecord = this.recordsByPos.get(pos); + if (oldRecord != null) { + oldRecord.setBlockEntity(remove, add); + } } private void add(final CaptureRecord record) { @@ -134,5 +138,10 @@ public void applyApiPatch(ServerLevel level) { level.setBlockEntity(this.blockEntity); } } + + public void setBlockEntity(boolean remove, @Nullable BlockEntity add) { + this.removeBe = remove; + this.blockEntity = add; + } } } \ No newline at end of file diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java index b08c38cf1ff7..7173ed0cda99 100644 --- a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java +++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java @@ -34,34 +34,34 @@ public void onEnable() { @EventHandler public void blockPlace(BlockFertilizeEvent event) { - event.setCancelled(true); - - Bukkit.getPlayer("Owen1212055").sendBlockChanges(event.getBlocks()); - - new BukkitRunnable(){ - - @Override - public void run() { - event.getBlocks().forEach((state) -> Bukkit.getPlayer("Owen1212055").sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData())); - } - }.runTaskLater(this, 20 * 2); +// event.setCancelled(true); +// +// Bukkit.getPlayer("Owen1212055").sendBlockChanges(event.getBlocks()); +// +// new BukkitRunnable(){ +// +// @Override +// public void run() { +// event.getBlocks().forEach((state) -> Bukkit.getPlayer("Owen1212055").sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData())); +// } +// }.runTaskLater(this, 20 * 2); } @EventHandler public void blockPlace(StructureGrowEvent event) { - event.setCancelled(true); - - event.getPlayer().sendBlockChanges(event.getBlocks()); - - new BukkitRunnable(){ - - @Override - public void run() { - event.getBlocks().forEach((state) -> event.getPlayer().sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData())); - } - }.runTaskLater(this, 20 * 2); +// event.setCancelled(true); +// +// event.getPlayer().sendBlockChanges(event.getBlocks()); +// +// new BukkitRunnable(){ +// +// @Override +// public void run() { +// event.getBlocks().forEach((state) -> event.getPlayer().sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData())); +// } +// }.runTaskLater(this, 20 * 2); } From 6968eb065e5b167cf04ab9b3d68a7e298bf39441 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Fri, 6 Feb 2026 10:31:01 -0500 Subject: [PATCH 05/17] Small name cleanup --- .../features/0032-Block-Capture-System.patch | 20 +++++++++---------- .../paper/util/capture/CaptureRecordMap.java | 5 ++--- .../util/capture/MinecraftCaptureBridge.java | 13 ++++++------ .../util/capture/SimpleBlockCapture.java | 15 +++++++------- .../org/bukkit/craftbukkit/CraftWorld.java | 2 +- .../craftbukkit/event/CraftEventFactory.java | 4 ++-- 6 files changed, 30 insertions(+), 29 deletions(-) diff --git a/paper-server/patches/features/0032-Block-Capture-System.patch b/paper-server/patches/features/0032-Block-Capture-System.patch index bd9e260cd328..9862c229190a 100644 --- a/paper-server/patches/features/0032-Block-Capture-System.patch +++ b/paper-server/patches/features/0032-Block-Capture-System.patch @@ -177,7 +177,7 @@ index 2818c7ad02a861270283384ceb7ecd4d44f6d624..f2cd7765da1ee5138aba9505c5935444 } } diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java -index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..ff349ef9b953bb78200909e96321204fe3acb710 100644 +index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..d58d490600f463b311235bbe965906340c16f9a2 100644 --- a/net/minecraft/world/item/BlockItem.java +++ b/net/minecraft/world/item/BlockItem.java @@ -1,13 +1,18 @@ @@ -286,7 +286,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..ff349ef9b953bb78200909e96321204f + blocks.add(state.getBlock().getState()); + }); + -+ capture.openLegacySupport(); ++ capture.overlayCaptureOnLevel(); + + if (blocks.size() > 1) { + placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, clickedPos); @@ -689,7 +689,7 @@ index 3a41cf80e347ae3b30858879fb91f719625c8bb6..334f30499b8e043bf74ebe7001ded66c return flag; diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 579bbba4e823d4d0318e58759ca732b7c8e4d865..0b7aacb48cf0b0f974fd24d90f0910a3a72fcde9 100644 +index 579bbba4e823d4d0318e58759ca732b7c8e4d865..f9ebaf0eaa5954f40cd0aeb5d41b150f3b2c2bfb 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -143,10 +143,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @@ -714,7 +714,7 @@ index 579bbba4e823d4d0318e58759ca732b7c8e4d865..0b7aacb48cf0b0f974fd24d90f0910a3 - if (previous != null) { - return previous.getHandle(); + // Paper start - Perf: Optimize capturedTileEntities lookup -+ if (this.capturer.isCapturing() && this.capturer.getCapture().isViewingCaptureState()) { ++ if (this.capturer.isCapturing() && this.capturer.getCapture().isOverlayingCaptureOnLevel()) { + BlockState guess = this.capturer.getCapture().getCaptureBlockStateIfLoaded(pos); + if (guess != null) { + return guess; @@ -744,7 +744,7 @@ index 579bbba4e823d4d0318e58759ca732b7c8e4d865..0b7aacb48cf0b0f974fd24d90f0910a3 - blockstate.setFlags(flags); - return true; + // Paper start - Perf: Optimize capturedTileEntities lookup -+ if (this.capturer.isCapturing() && this.capturer.getCapture().isViewingCaptureState()) { ++ if (this.capturer.isCapturing() && this.capturer.getCapture().isOverlayingCaptureOnLevel()) { + return this.capturer.getCapture().capturingWorldLevel().setBlockSilent(pos, state, flags, recursionLeft); } - // CraftBukkit end @@ -889,7 +889,7 @@ index 579bbba4e823d4d0318e58759ca732b7c8e4d865..0b7aacb48cf0b0f974fd24d90f0910a3 - if (previous != null) { - return previous.getHandle(); + // Paper start - Perf: Optimize capturedTileEntities lookup -+ if (this.capturer.isCapturing() && this.capturer.getCapture().isViewingCaptureState()) { ++ if (this.capturer.isCapturing() && this.capturer.getCapture().isOverlayingCaptureOnLevel()) { + BlockState guess = this.capturer.getCapture().getCaptureBlockState(pos); + if (guess != null) { + return guess; @@ -906,7 +906,7 @@ index 579bbba4e823d4d0318e58759ca732b7c8e4d865..0b7aacb48cf0b0f974fd24d90f0910a3 - net.minecraft.world.level.block.entity.BlockEntity blockEntity; - if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(pos)) != null) { - return blockEntity; -+ if (this.capturer.isCapturing() && this.capturer.getCapture().isViewingCaptureState()) { ++ if (this.capturer.isCapturing() && this.capturer.getCapture().isOverlayingCaptureOnLevel()) { + var guess = this.capturer.getCapture().getCaptureBlockEntity(pos); + if (guess != null) { + return guess.orElse(null); @@ -922,7 +922,7 @@ index 579bbba4e823d4d0318e58759ca732b7c8e4d865..0b7aacb48cf0b0f974fd24d90f0910a3 - if (this.captureBlockStates) { - this.capturedTileEntities.put(blockPos.immutable(), blockEntity); + // Paper start - Perf: Optimize capturedTileEntities lookup -+ if (this.capturer.isCapturing() && this.capturer.getCapture().isViewingCaptureState()) { ++ if (this.capturer.isCapturing() && this.capturer.getCapture().isOverlayingCaptureOnLevel()) { + this.capturer.getCapture().capturingWorldLevel().setBlockEntity(blockEntity); return; } @@ -1924,7 +1924,7 @@ index c57cb7b47a01e313a1be5de769c6766ac2787d95..f34731580bc880dcc5d3e96923a7f2b4 } } diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java -index 5471619a0484ece08640e2b3fd26746c351dc3e0..04663ea08fd6b849622e4908edee1562550bf63c 100644 +index 5471619a0484ece08640e2b3fd26746c351dc3e0..991b565a64083068e8f5a97034131898dce9ab59 100644 --- a/net/minecraft/world/level/block/grower/TreeGrower.java +++ b/net/minecraft/world/level/block/grower/TreeGrower.java @@ -122,7 +122,34 @@ public final class TreeGrower { @@ -1942,7 +1942,7 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..04663ea08fd6b849622e4908edee1562 + try (io.papermc.paper.util.capture.SimpleBlockCapture capture = level.fork()) { + io.papermc.paper.util.capture.MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); + if (this.growTree0(captureTreeGeneration, chunkGenerator, pos, state, random, mealContext.treeHook)) { -+ var states = captureTreeGeneration.calculateLatestBlockStates(captureTreeGeneration, mealContext.ogServerLevel); ++ var states = captureTreeGeneration.calculateLatestBlockStates(mealContext.ogServerLevel); + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, mealContext.ogServerLevel); + java.util.List blocks = new java.util.ArrayList<>(states.values()); + org.bukkit.event.world.StructureGrowEvent event = new org.bukkit.event.world.StructureGrowEvent(location, mealContext.treeHook.get(), false, mealContext.getBukkitPlayer(), blocks); diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java index e51ac0d0bb40..5f4a343af147 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java @@ -89,7 +89,7 @@ public void applyApiPatch(final ServerLevel level) { } // TODO: Clean this up - public Map calculateLatestBlockStates(PaperCapturingWorldLevel predictor, ServerLevel level) { + public Map calculateLatestBlockStates(ServerLevel level) { final Map out = new HashMap<>(); this.recordsByPos.keySet().forEach((pos) -> { @@ -102,8 +102,7 @@ public Map calculateLatestBlockStates(Pap } - - public class CaptureRecord { + public static class CaptureRecord { private final BlockPos pos; diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java index e20c1b762bc0..390f80384f8c 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java @@ -61,7 +61,7 @@ public class MinecraftCaptureBridge implements PaperCapturingWorldLevel { private final BlockPlacementPredictor predictiveReadLayer; private SimpleBlockPlacementPredictor writeLayer; private final SimpleBlockPlacementPredictor legacyStorage; - private boolean isLegacyPredicting = false; + private boolean forwardingLevelCalls = false; private final CapturingTickAccess blocks; private final CapturingTickAccess liquids; @@ -82,7 +82,8 @@ public MinecraftCaptureBridge(ServerLevel parent, BlockPlacementPredictor liveBl this.writeLayer = blockPlacementStorage; this.predictiveReadLayer = new LayeredBlockPlacementPredictor( - new LegacyLayer(this.legacyStorage, () -> this.isLegacyPredicting), + // If we are currently overlaying server level, we want to show those first. + new LegacyLayer(this.legacyStorage, () -> this.forwardingLevelCalls), blockPlacementStorage ); @@ -405,8 +406,8 @@ public net.minecraft.world.level.block.state.BlockState getLatestBlockState(fina return placement.map(BlockPlacementPredictor.BlockEntityPlacement::blockEntity); } - public Map calculateLatestBlockStates(PaperCapturingWorldLevel predictor, ServerLevel level) { - return this.writeLayer.getRecordMap().calculateLatestBlockStates(predictor, level); + public Map calculateLatestBlockStates(ServerLevel level) { + return this.writeLayer.getRecordMap().calculateLatestBlockStates(level); } public void applyTasks() { @@ -432,9 +433,9 @@ public void applyTasks() { } } - public void activateLegacyCapture() { + public void allowWriteOnLevel() { this.writeLayer = this.legacyStorage; - this.isLegacyPredicting = true; + this.forwardingLevelCalls = true; } public static class CapturingTickAccess implements LevelTickAccess<@NotNull T> { diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java index bba63d1e24ac..02f4d4bdc2d0 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java @@ -15,7 +15,7 @@ public class SimpleBlockCapture implements AutoCloseable { private final MinecraftCaptureBridge capturingWorldLevel; private final ServerLevel level; - private boolean openLegacyCapturing = false; + private boolean isOverlayingCaptureOnLevel = false; private final SimpleBlockCapture oldCapture; @@ -36,7 +36,7 @@ public boolean isCapturing() { } public Map getCapturedBlockStates() { - return this.capturingWorldLevel.calculateLatestBlockStates(this.capturingWorldLevel, level); + return this.capturingWorldLevel.calculateLatestBlockStates(this.level); } public net.minecraft.world.level.block.state.BlockState getCaptureBlockState(final BlockPos pos) { @@ -48,13 +48,14 @@ public net.minecraft.world.level.block.state.BlockState getCaptureBlockState(fin } - public void openLegacySupport() { - this.openLegacyCapturing = true; - this.capturingWorldLevel.activateLegacyCapture(); + // This is done so that the captured blocks appear ontop of the world. + public void overlayCaptureOnLevel() { + this.isOverlayingCaptureOnLevel = true; + this.capturingWorldLevel.allowWriteOnLevel(); } - public boolean isViewingCaptureState() { - return openLegacyCapturing; + public boolean isOverlayingCaptureOnLevel() { + return isOverlayingCaptureOnLevel; } public void finalizePlacement() { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 690d9ec1e375..a85e742267f1 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -748,7 +748,7 @@ public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate del BlockPos pos = CraftLocation.toBlockPosition(loc); boolean res = this.generateTree(captureTreeGeneration, this.getHandle().getMinecraftWorld().getChunkSource().getGenerator(), pos, new RandomSourceWrapper(CraftWorld.rand), type); if (res) { - var states = captureTreeGeneration.calculateLatestBlockStates(captureTreeGeneration, this.world); + var states = captureTreeGeneration.calculateLatestBlockStates(this.world); java.util.List blocks = new java.util.ArrayList<>(states.values()); for (BlockState state : blocks) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 3a709c21e541..8a592589a5fc 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -2399,7 +2399,7 @@ public static boolean structureEvent(ServerLevel serverLevel, io.papermc.paper.u try (SimpleBlockCapture capture = level.fork()) { MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); if (worldgenCapture.apply(captureTreeGeneration)) { - var states = captureTreeGeneration.calculateLatestBlockStates(level, serverLevel); + var states = captureTreeGeneration.calculateLatestBlockStates(serverLevel); org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, serverLevel); java.util.List blocks = new java.util.ArrayList<>(states.values()); @@ -2422,7 +2422,7 @@ public static boolean fertilizeBlock(ServerLevel level, Player player, BlockPos MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); worldgenCapture.accept(captureTreeGeneration); - var states = captureTreeGeneration.calculateLatestBlockStates(captureTreeGeneration, level); + var states = captureTreeGeneration.calculateLatestBlockStates(level); org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level); java.util.List blocks = new java.util.ArrayList<>(states.values()); From 8e32dcbfe77c7760171a08d095aed2a6ee8dccef Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Fri, 6 Feb 2026 12:32:57 -0500 Subject: [PATCH 06/17] Fix some layering issues, simplify a bit --- .../features/0032-Block-Capture-System.patch | 14 +-- .../paper/util/capture/CaptureRecordMap.java | 1 - .../util/capture/LiveBlockPlacementLayer.java | 20 +++-- .../util/capture/MinecraftCaptureBridge.java | 85 +++++++------------ .../capture/PaperCapturingWorldLevel.java | 2 +- .../ServerLevelPaperCapturingWorldLevel.java | 2 +- .../util/capture/SimpleBlockCapture.java | 4 +- .../paper/util/capture/WorldCapturer.java | 2 +- .../org/bukkit/craftbukkit/CraftWorld.java | 2 +- .../craftbukkit/event/CraftEventFactory.java | 5 +- .../io/papermc/testplugin/TestPlugin.java | 7 +- 11 files changed, 64 insertions(+), 80 deletions(-) diff --git a/paper-server/patches/features/0032-Block-Capture-System.patch b/paper-server/patches/features/0032-Block-Capture-System.patch index 9862c229190a..926009224b0c 100644 --- a/paper-server/patches/features/0032-Block-Capture-System.patch +++ b/paper-server/patches/features/0032-Block-Capture-System.patch @@ -177,7 +177,7 @@ index 2818c7ad02a861270283384ceb7ecd4d44f6d624..f2cd7765da1ee5138aba9505c5935444 } } diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java -index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..d58d490600f463b311235bbe965906340c16f9a2 100644 +index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a2e923e9ac2a10d928bfbeb8fdf62ecbab675ddc 100644 --- a/net/minecraft/world/item/BlockItem.java +++ b/net/minecraft/world/item/BlockItem.java @@ -1,13 +1,18 @@ @@ -218,7 +218,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..d58d490600f463b311235bbe96590634 - } else { + } + // Paper start -+ try ( SimpleBlockCapture capture = ((ServerLevel) context.getLevel()).fork()) { ++ try ( SimpleBlockCapture capture = ((ServerLevel) context.getLevel()).forkCaptureSession()) { + boolean placeRes = this.placeBlock(blockPlaceContext, placementState, capture.capturingWorldLevel()); + if (!placeRes) { + return InteractionResult.FAIL; @@ -689,7 +689,7 @@ index 3a41cf80e347ae3b30858879fb91f719625c8bb6..334f30499b8e043bf74ebe7001ded66c return flag; diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 579bbba4e823d4d0318e58759ca732b7c8e4d865..f9ebaf0eaa5954f40cd0aeb5d41b150f3b2c2bfb 100644 +index 579bbba4e823d4d0318e58759ca732b7c8e4d865..d9a116d9c15dab1396a51d86cc661d925500a657 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -143,10 +143,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @@ -890,7 +890,7 @@ index 579bbba4e823d4d0318e58759ca732b7c8e4d865..f9ebaf0eaa5954f40cd0aeb5d41b150f - return previous.getHandle(); + // Paper start - Perf: Optimize capturedTileEntities lookup + if (this.capturer.isCapturing() && this.capturer.getCapture().isOverlayingCaptureOnLevel()) { -+ BlockState guess = this.capturer.getCapture().getCaptureBlockState(pos); ++ BlockState guess = this.capturer.getCapture().getOverlayBlockState(pos); + if (guess != null) { + return guess; } @@ -907,7 +907,7 @@ index 579bbba4e823d4d0318e58759ca732b7c8e4d865..f9ebaf0eaa5954f40cd0aeb5d41b150f - if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(pos)) != null) { - return blockEntity; + if (this.capturer.isCapturing() && this.capturer.getCapture().isOverlayingCaptureOnLevel()) { -+ var guess = this.capturer.getCapture().getCaptureBlockEntity(pos); ++ var guess = this.capturer.getCapture().getOverlayBlockEntity(pos); + if (guess != null) { + return guess.orElse(null); + } @@ -1924,7 +1924,7 @@ index c57cb7b47a01e313a1be5de769c6766ac2787d95..f34731580bc880dcc5d3e96923a7f2b4 } } diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java -index 5471619a0484ece08640e2b3fd26746c351dc3e0..991b565a64083068e8f5a97034131898dce9ab59 100644 +index 5471619a0484ece08640e2b3fd26746c351dc3e0..e9e5b7a57cd40114222d7e4d21ed1992a0c4707b 100644 --- a/net/minecraft/world/level/block/grower/TreeGrower.java +++ b/net/minecraft/world/level/block/grower/TreeGrower.java @@ -122,7 +122,34 @@ public final class TreeGrower { @@ -1939,7 +1939,7 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..991b565a64083068e8f5a97034131898 + } + + public boolean growTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.BoneMealContext mealContext) { -+ try (io.papermc.paper.util.capture.SimpleBlockCapture capture = level.fork()) { ++ try (io.papermc.paper.util.capture.SimpleBlockCapture capture = level.forkCaptureSession()) { + io.papermc.paper.util.capture.MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); + if (this.growTree0(captureTreeGeneration, chunkGenerator, pos, state, random, mealContext.treeHook)) { + var states = captureTreeGeneration.calculateLatestBlockStates(mealContext.ogServerLevel); diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java index 5f4a343af147..0d82d0edd217 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java @@ -38,7 +38,6 @@ public void setLatestBlockStateAt(final BlockPos pos, final BlockState state, fi } public void setLatestBlockEntityAt(final BlockPos pos, final boolean remove, @Nullable final BlockEntity add) { - CaptureRecord oldRecord = this.recordsByPos.get(pos); if (oldRecord != null) { oldRecord.setBlockEntity(remove, add); diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java b/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java index 3a59b1163dc2..00e7b46fa2d4 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java @@ -1,21 +1,21 @@ package io.papermc.paper.util.capture; import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import java.util.Optional; +import java.util.function.Supplier; -public record LiveBlockPlacementLayer(ServerLevel level) implements BlockPlacementPredictor { +public record LiveBlockPlacementLayer(WorldCapturer level, net.minecraft.server.level.ServerLevel serverLevel) implements BlockPlacementPredictor { @Override public Optional getLatestBlockAt(BlockPos pos) { - return Optional.of(this.level.getBlockState(pos)); + return Optional.of(provideLive(() -> this.serverLevel.getBlockState(pos))); } @Override public Optional getLatestBlockAtIfLoaded(BlockPos pos) { - BlockState state = this.level.getBlockStateIfLoaded(pos); + BlockState state = provideLive(() -> this.serverLevel.getBlockStateIfLoaded(pos)); if (state == null) { return LoadedBlockState.UNLOADED; } @@ -25,11 +25,21 @@ public Optional getLatestBlockAtIfLoaded(BlockPos pos) { @Override public Optional getLatestTileAt(BlockPos pos) { - BlockEntity blockEntity = this.level.getBlockEntity(pos); + BlockEntity blockEntity = provideLive(() -> this.serverLevel.getBlockEntity(pos)); if (blockEntity == null) { return BlockEntityPlacement.ABSENT; } return Optional.of(new BlockEntityPlacement(false, blockEntity)); } + + + public T provideLive(Supplier valueProvider) { + SimpleBlockCapture blockCapture = this.level.getCapture(); + this.level.releaseCapture(null); + T value = valueProvider.get(); + this.level.releaseCapture(blockCapture); + + return value; + } } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java index 390f80384f8c..26d4235fc16a 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java @@ -57,40 +57,34 @@ public class MinecraftCaptureBridge implements PaperCapturingWorldLevel { private final ServerLevel parent; private final List queuedTasks = new ArrayList<>(); - private final BlockPlacementPredictor activeReadLayer; - private final BlockPlacementPredictor predictiveReadLayer; + // Effective represents plugin set blocks -> predicted blocks -> actual server level + private final BlockPlacementPredictor effectiveReadLayer; + + private SimpleBlockPlacementPredictor writeLayer; - private final SimpleBlockPlacementPredictor legacyStorage; - private boolean forwardingLevelCalls = false; + + // This is the layer that is written to in the server level potentially. + // Mostly plugin set blocks + private final SimpleBlockPlacementPredictor serverLevelOverlayLayer; + private final CapturingTickAccess blocks; private final CapturingTickAccess liquids; private Consumer sink = queuedTasks::add; - - public MinecraftCaptureBridge(ServerLevel parent) { - this(parent, new LiveBlockPlacementLayer(parent)); - } - - public MinecraftCaptureBridge(ServerLevel parent, BlockPlacementPredictor liveBlockPlacementLayer) { + public MinecraftCaptureBridge(ServerLevel parent, BlockPlacementPredictor baseReadLayer) { this.parent = parent; - SimpleBlockPlacementPredictor blockPlacementStorage = new SimpleBlockPlacementPredictor(liveBlockPlacementLayer); - this.legacyStorage = new SimpleBlockPlacementPredictor(liveBlockPlacementLayer); - + this.serverLevelOverlayLayer = new SimpleBlockPlacementPredictor(baseReadLayer); + SimpleBlockPlacementPredictor predictedBlocks = new SimpleBlockPlacementPredictor(baseReadLayer); - this.writeLayer = blockPlacementStorage; - - this.predictiveReadLayer = new LayeredBlockPlacementPredictor( - // If we are currently overlaying server level, we want to show those first. - new LegacyLayer(this.legacyStorage, () -> this.forwardingLevelCalls), - blockPlacementStorage + this.effectiveReadLayer = new LayeredBlockPlacementPredictor( + this.serverLevelOverlayLayer, // The overlay layer represents plugin set blocks! + predictedBlocks, // Now predicted blocks + baseReadLayer ); - this.activeReadLayer = new LayeredBlockPlacementPredictor( - this.predictiveReadLayer, - liveBlockPlacementLayer - ); + this.writeLayer = predictedBlocks; // Predicting this.blocks = new CapturingTickAccess<>(parent.getBlockTicks()); this.liquids = new CapturingTickAccess<>(parent.getFluidTicks()); @@ -147,21 +141,21 @@ public ChunkGenerator getGenerator() { } @Override - public SimpleBlockCapture fork() { + public SimpleBlockCapture forkCaptureSession() { return this.parent.capturer.createCaptureSession(new BlockPlacementPredictor() { @Override public Optional getLatestBlockAt(BlockPos pos) { - return MinecraftCaptureBridge.this.activeReadLayer.getLatestBlockAt(pos); + return MinecraftCaptureBridge.this.effectiveReadLayer.getLatestBlockAt(pos); } @Override public Optional getLatestBlockAtIfLoaded(BlockPos pos) { - return MinecraftCaptureBridge.this.activeReadLayer.getLatestBlockAtIfLoaded(pos); + return MinecraftCaptureBridge.this.effectiveReadLayer.getLatestBlockAtIfLoaded(pos); } @Override public Optional getLatestTileAt(BlockPos pos) { - return MinecraftCaptureBridge.this.activeReadLayer.getLatestTileAt(pos); + return MinecraftCaptureBridge.this.effectiveReadLayer.getLatestTileAt(pos); } }); @@ -210,19 +204,19 @@ public WorldBorder getWorldBorder() { @Override public @Nullable BlockEntity getBlockEntity(BlockPos pos) { - return this.activeReadLayer.getLatestTileAt(pos) + return this.effectiveReadLayer.getLatestTileAt(pos) .map(BlockPlacementPredictor.BlockEntityPlacement::blockEntity) .orElse(null); } @Override public BlockState getBlockState(BlockPos pos) { - return this.activeReadLayer.getLatestBlockAt(pos).orElseThrow(); // Should not ever be null, parent should pass value + return this.effectiveReadLayer.getLatestBlockAt(pos).orElseThrow(); // Should not ever be null, parent should pass value } @Override public @Nullable BlockState getBlockStateIfLoaded(BlockPos pos) { - return this.activeReadLayer.getLatestBlockAtIfLoaded(pos).map(BlockPlacementPredictor.LoadedBlockState::state).orElse(null); + return this.effectiveReadLayer.getLatestBlockAtIfLoaded(pos).map(BlockPlacementPredictor.LoadedBlockState::state).orElse(null); } @Override @@ -394,12 +388,12 @@ public void addTask(Consumer levelConsumer) { } public net.minecraft.world.level.block.state.BlockState getLatestBlockState(final BlockPos pos) { - return this.predictiveReadLayer.getLatestBlockAt(pos).orElse(null); + return this.effectiveReadLayer.getLatestBlockAt(pos).orElse(null); } public @Nullable Optional<@Nullable BlockEntity> getLatestBlockEntity(final BlockPos pos) { - Optional placement = this.predictiveReadLayer.getLatestTileAt(pos); - if (placement.isEmpty()) { + Optional placement = this.effectiveReadLayer.getLatestTileAt(pos); + if (placement.isEmpty() || placement.get().blockEntity() == null && !placement.get().removed()) { return null; } @@ -420,8 +414,8 @@ public void applyTasks() { // If we have changes that the plugin applied ontop of the already existing changes, we know that we can apply them. // So, do that! - if (!this.legacyStorage.isEmpty()) { - this.legacyStorage.getRecordMap().applyApiPatch(this.parent); + if (!this.serverLevelOverlayLayer.isEmpty()) { + this.serverLevelOverlayLayer.getRecordMap().applyApiPatch(this.parent); } @@ -434,8 +428,7 @@ public void applyTasks() { } public void allowWriteOnLevel() { - this.writeLayer = this.legacyStorage; - this.forwardingLevelCalls = true; + this.writeLayer = this.serverLevelOverlayLayer; } public static class CapturingTickAccess implements LevelTickAccess<@NotNull T> { @@ -474,22 +467,4 @@ public void apply() { } } - public record LegacyLayer(BlockPlacementPredictor predictor, - BooleanSupplier cond) implements BlockPlacementPredictor { - - @Override - public Optional getLatestBlockAt(BlockPos pos) { - return cond.getAsBoolean() ? this.predictor.getLatestBlockAt(pos) : Optional.empty(); - } - - @Override - public Optional getLatestBlockAtIfLoaded(BlockPos pos) { - return cond.getAsBoolean() ? this.predictor.getLatestBlockAtIfLoaded(pos) : Optional.empty(); - } - - @Override - public Optional getLatestTileAt(BlockPos pos) { - return cond.getAsBoolean() ? this.predictor.getLatestTileAt(pos) : Optional.empty(); - } - } } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java b/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java index 2be61bb6f421..d9b1ecb48cdb 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java @@ -29,6 +29,6 @@ public interface PaperCapturingWorldLevel extends WorldGenLevel { ChunkGenerator getGenerator(); - SimpleBlockCapture fork(); + SimpleBlockCapture forkCaptureSession(); } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java b/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java index b9a31db97639..a6b9a8560665 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java @@ -55,7 +55,7 @@ default ChunkGenerator getGenerator() { } @Override - default SimpleBlockCapture fork() { + default SimpleBlockCapture forkCaptureSession() { return this.handle().capturer.createCaptureSession(); } } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java index 02f4d4bdc2d0..165d2f1ca72e 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java @@ -39,11 +39,11 @@ public Map getCapturedBlockStates() { return this.capturingWorldLevel.calculateLatestBlockStates(this.level); } - public net.minecraft.world.level.block.state.BlockState getCaptureBlockState(final BlockPos pos) { + public net.minecraft.world.level.block.state.BlockState getOverlayBlockState(final BlockPos pos) { return this.capturingWorldLevel.getLatestBlockState(pos); } - public @Nullable Optional<@Nullable BlockEntity> getCaptureBlockEntity(final BlockPos pos) { + public @Nullable Optional<@Nullable BlockEntity> getOverlayBlockEntity(final BlockPos pos) { return this.capturingWorldLevel.getLatestBlockEntity(pos); } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java b/paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java index bfaa12220b83..61a02d041575 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java @@ -21,7 +21,7 @@ public SimpleBlockCapture createCaptureSession(BlockPlacementPredictor blockPlac } public SimpleBlockCapture createCaptureSession() { - return this.createCaptureSession(new LiveBlockPlacementLayer(this.world)); + return this.createCaptureSession(new LiveBlockPlacementLayer(this, world)); } public void releaseCapture(SimpleBlockCapture oldCapture) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index a85e742267f1..fd546f16d2e4 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -742,7 +742,7 @@ public boolean generateTree(Location loc, TreeType type) { @Override public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) { - try (SimpleBlockCapture capture = this.world.fork()) { + try (SimpleBlockCapture capture = this.world.forkCaptureSession()) { MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); BlockPos pos = CraftLocation.toBlockPosition(loc); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 8a592589a5fc..098a2644b302 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -38,7 +38,6 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.players.PlayerList; import net.minecraft.tags.DamageTypeTags; -import net.minecraft.util.RandomSource; import net.minecraft.util.Unit; import net.minecraft.world.Container; import net.minecraft.world.InteractionHand; @@ -2396,7 +2395,7 @@ public static boolean sendChestLockedNotifications(Vec3 pos) { } public static boolean structureEvent(ServerLevel serverLevel, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, Player player, BlockPos pos, Function worldgenCapture, TreeType type) { - try (SimpleBlockCapture capture = level.fork()) { + try (SimpleBlockCapture capture = level.forkCaptureSession()) { MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); if (worldgenCapture.apply(captureTreeGeneration)) { var states = captureTreeGeneration.calculateLatestBlockStates(serverLevel); @@ -2418,7 +2417,7 @@ public static boolean structureEvent(ServerLevel serverLevel, io.papermc.paper.u } public static boolean fertilizeBlock(ServerLevel level, Player player, BlockPos pos, Consumer worldgenCapture, boolean cancelled) { - try (SimpleBlockCapture capture = level.fork()) { + try (SimpleBlockCapture capture = level.forkCaptureSession()) { MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); worldgenCapture.accept(captureTreeGeneration); diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java index 7173ed0cda99..bd3f4a63e270 100644 --- a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java +++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java @@ -34,8 +34,8 @@ public void onEnable() { @EventHandler public void blockPlace(BlockFertilizeEvent event) { -// event.setCancelled(true); -// + //event.setCancelled(true); + // Bukkit.getPlayer("Owen1212055").sendBlockChanges(event.getBlocks()); // // new BukkitRunnable(){ @@ -67,7 +67,7 @@ public void blockPlace(StructureGrowEvent event) { @EventHandler public void blockPlace(PlayerSwapHandItemsEvent event) { - event.getPlayer().getTargetBlockExact(5).applyBoneMeal(BlockFace.UP); + event.getPlayer().getTargetBlockExact(5).applyBoneMeal(BlockFace.UP); event.getPlayer().getWorld().generateTree(event.getPlayer().getLocation(), TreeType.values()[(int) (TreeType.values().length * Math.random())]); } @@ -80,6 +80,7 @@ public void blockPlace(BlockPlaceEvent event) { Chest state = (Chest) event.getBlock().getState(false); state.getBlockInventory().setItem(0, new ItemStack(Material.STONE)); + System.out.println(state); } else if (event.getPlayer().getInventory().contains(Material.GOLD_INGOT)) { event.setCancelled(true); } From d56b5485522456118a73f092ab75efe66d365912 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:21:55 -0500 Subject: [PATCH 07/17] Fix some niche issues --- .../papermc/paper/util/capture/CaptureRecordMap.java | 8 +++++--- .../paper/util/capture/MinecraftCaptureBridge.java | 8 ++++---- .../util/capture/SimpleBlockPlacementPredictor.java | 10 ++-------- .../main/java/io/papermc/testplugin/TestPlugin.java | 10 +++++----- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java index 0d82d0edd217..0a5115bc7092 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java @@ -34,7 +34,7 @@ public final class CaptureRecordMap { private final Map recordsByPos = new HashMap<>(); public void setLatestBlockStateAt(final BlockPos pos, final BlockState state, final int flags) { - this.add(new CaptureRecord(state, pos)); + this.add(new CaptureRecord(state, pos, flags)); } public void setLatestBlockEntityAt(final BlockPos pos, final boolean remove, @Nullable final BlockEntity add) { @@ -108,6 +108,7 @@ public static class CaptureRecord { private BlockState state; private BlockEntity blockEntity; private boolean removeBe; + private int flags; public CaptureRecord(BlockPos pos, BlockState state, BlockEntity blockEntity) { this.pos = pos; @@ -115,9 +116,10 @@ public CaptureRecord(BlockPos pos, BlockState state, BlockEntity blockEntity) { this.blockEntity = blockEntity; } - public CaptureRecord(BlockState state, BlockPos pos) { + public CaptureRecord(BlockState state, BlockPos pos, int flags) { this.pos = pos; this.state = state; + this.flags = flags; } public CaptureRecord(boolean remove, @Nullable BlockEntity add, BlockPos pos) { @@ -131,7 +133,7 @@ public void applyApiPatch(ServerLevel level) { level.removeBlockEntity(this.pos); } - level.setBlock(this.pos, this.state, UPDATE_NONE | net.minecraft.world.level.block.Block.UPDATE_SKIP_ON_PLACE); + level.setBlock(this.pos, this.state, this.flags); if (this.blockEntity != null) { level.setBlockEntity(this.blockEntity); } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java index 26d4235fc16a..1be14cb54641 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java @@ -75,8 +75,8 @@ public class MinecraftCaptureBridge implements PaperCapturingWorldLevel { public MinecraftCaptureBridge(ServerLevel parent, BlockPlacementPredictor baseReadLayer) { this.parent = parent; - this.serverLevelOverlayLayer = new SimpleBlockPlacementPredictor(baseReadLayer); - SimpleBlockPlacementPredictor predictedBlocks = new SimpleBlockPlacementPredictor(baseReadLayer); + this.serverLevelOverlayLayer = new SimpleBlockPlacementPredictor(); + SimpleBlockPlacementPredictor predictedBlocks = new SimpleBlockPlacementPredictor(); this.effectiveReadLayer = new LayeredBlockPlacementPredictor( this.serverLevelOverlayLayer, // The overlay layer represents plugin set blocks! @@ -315,7 +315,7 @@ public boolean isFluidAtPosition(BlockPos pos, Predicate predicate) } public boolean silentSet(BlockPos pos, BlockState state, int flags) { - return this.writeLayer.setBlockState(pos, state, flags); + return this.writeLayer.setBlockState(this.effectiveReadLayer, pos, state, flags); } @Override @@ -323,7 +323,7 @@ public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursion BlockPos copy = pos.immutable(); this.addTask((serverLevel) -> parent.setBlock(copy, state, flags, recursionLeft)); - return this.writeLayer.setBlockState(copy, state, flags); + return this.writeLayer.setBlockState(this.effectiveReadLayer, copy, state, flags); } @Override diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java index 4032cdc9e5f4..3f0009aa4b0c 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java @@ -14,14 +14,8 @@ class SimpleBlockPlacementPredictor implements BlockPlacementPredictor { private final CaptureRecordMap guesstimationMap = new CaptureRecordMap(); - private final BlockPlacementPredictor base; - - SimpleBlockPlacementPredictor(BlockPlacementPredictor base) { - this.base = base; - } - - public boolean setBlockState(BlockPos pos, BlockState state, int flags) { - BlockState blockState = this.base.getLatestBlockAt(pos).orElse(Blocks.AIR.defaultBlockState()); + public boolean setBlockState(BlockPlacementPredictor layer, BlockPos pos, BlockState state, int flags) { + BlockState blockState = layer.getLatestBlockAt(pos).orElse(Blocks.AIR.defaultBlockState()); // Dont do any processing if the same if (blockState == state) { return false; diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java index bd3f4a63e270..a6bab74ca9ef 100644 --- a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java +++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java @@ -76,13 +76,13 @@ public void blockPlace(PlayerSwapHandItemsEvent event) { public void blockPlace(BlockPlaceEvent event) { event.getPlayer().sendActionBar("Replaced: " + event.getBlockReplacedState().getType()); if (event.getPlayer().getInventory().contains(Material.DIAMOND)) { - event.getBlock().setType(Material.CHEST); - - Chest state = (Chest) event.getBlock().getState(false); - state.getBlockInventory().setItem(0, new ItemStack(Material.STONE)); - System.out.println(state); + event.getBlock().setType(Material.AIR); } else if (event.getPlayer().getInventory().contains(Material.GOLD_INGOT)) { event.setCancelled(true); + } else if (event.getPlayer().getInventory().contains(Material.EMERALD)) { + event.getBlock().setType(Material.STONE); + } else if (event.getPlayer().getInventory().contains(Material.GOLD_NUGGET)) { + event.getBlock().getRelative(BlockFace.SOUTH).setType(Material.STONE); } event.getPlayer().sendMessage(event.getBlock().getType().toString()); } From e4114b0e0e74d2ec9ee8924bbf5c3a862d9ec24b Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:38:26 -0500 Subject: [PATCH 08/17] Adjust pre cancel --- .../features/0032-Block-Capture-System.patch | 16 +++++++++------- .../paper/util/capture/BoneMealContext.java | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/paper-server/patches/features/0032-Block-Capture-System.patch b/paper-server/patches/features/0032-Block-Capture-System.patch index 926009224b0c..16678b340142 100644 --- a/paper-server/patches/features/0032-Block-Capture-System.patch +++ b/paper-server/patches/features/0032-Block-Capture-System.patch @@ -1518,7 +1518,7 @@ index 3762d833f17d596e04845a3f391423f9c14a878b..f5d03f2a3a3866857ddaece77ac4e127 if (!blockState.isAir()) { level.setBlock(pos.above(), blockState, Block.UPDATE_ALL); diff --git a/net/minecraft/world/level/block/MushroomBlock.java b/net/minecraft/world/level/block/MushroomBlock.java -index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..519c12aeec62c7437e0f3e92718965c798280612 100644 +index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..33f8f4742a39dd761eddc5512e05b424f998240e 100644 --- a/net/minecraft/world/level/block/MushroomBlock.java +++ b/net/minecraft/world/level/block/MushroomBlock.java @@ -87,16 +87,18 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock @@ -1539,7 +1539,7 @@ index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..519c12aeec62c7437e0f3e92718965c7 + }, (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM)) { return true; } else { -+ mealContext.precancelStructureEvent = false; // Paper ++ mealContext.precancelStructureEvent = true; // Paper level.setBlock(pos, state, Block.UPDATE_ALL); return false; } @@ -1924,10 +1924,10 @@ index c57cb7b47a01e313a1be5de769c6766ac2787d95..f34731580bc880dcc5d3e96923a7f2b4 } } diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java -index 5471619a0484ece08640e2b3fd26746c351dc3e0..e9e5b7a57cd40114222d7e4d21ed1992a0c4707b 100644 +index 5471619a0484ece08640e2b3fd26746c351dc3e0..780d1f9ee0c3fd4ea3e0ccf163767b73a7db7c23 100644 --- a/net/minecraft/world/level/block/grower/TreeGrower.java +++ b/net/minecraft/world/level/block/grower/TreeGrower.java -@@ -122,7 +122,34 @@ public final class TreeGrower { +@@ -122,7 +122,36 @@ public final class TreeGrower { return this.secondaryMegaTree.isPresent() && random.nextFloat() < this.secondaryChance ? this.secondaryMegaTree.get() : this.megaTree.orElse(null); } @@ -1950,6 +1950,8 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..e9e5b7a57cd40114222d7e4d21ed1992 + if (event.callEvent()) { + captureTreeGeneration.applyTasks(); + return true; ++ } else { ++ mealContext.precancelStructureEvent = true; + } + } + @@ -1963,7 +1965,7 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..e9e5b7a57cd40114222d7e4d21ed1992 ResourceKey> configuredMegaFeature = this.getConfiguredMegaFeature(random); if (configuredMegaFeature != null) { Holder> holder = level.registryAccess() -@@ -130,7 +157,7 @@ public final class TreeGrower { +@@ -130,7 +159,7 @@ public final class TreeGrower { .get(configuredMegaFeature) .orElse(null); if (holder != null) { @@ -1972,7 +1974,7 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..e9e5b7a57cd40114222d7e4d21ed1992 for (int i = 0; i >= -1; i--) { for (int i1 = 0; i1 >= -1; i1--) { if (isTwoByTwoSapling(state, level, pos, i, i1)) { -@@ -163,7 +190,7 @@ public final class TreeGrower { +@@ -163,7 +192,7 @@ public final class TreeGrower { if (holder1 == null) { return false; } else { @@ -1981,7 +1983,7 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..e9e5b7a57cd40114222d7e4d21ed1992 ConfiguredFeature configuredFeature2 = holder1.value(); BlockState blockState1 = level.getFluidState(pos).createLegacyBlock(); level.setBlock(pos, blockState1, Block.UPDATE_NONE); -@@ -200,53 +227,53 @@ public final class TreeGrower { +@@ -200,53 +229,53 @@ public final class TreeGrower { } // CraftBukkit start diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java b/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java index a83fae0262ed..ac7123a2bc71 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java @@ -11,7 +11,7 @@ public class BoneMealContext { public ServerLevel ogServerLevel; public ServerPlayer player; - public boolean precancelStructureEvent = true; + public boolean precancelStructureEvent = false; public java.util.concurrent.atomic.AtomicReference treeHook = new AtomicReference<>(); // just make this a field From ad45c842405e882c8114dc502981943220160f2a Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:48:11 -0500 Subject: [PATCH 09/17] Bye treetype field --- .../features/0032-Block-Capture-System.patch | 48 ++++++++++++------- .../paper/util/capture/BoneMealContext.java | 3 +- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/paper-server/patches/features/0032-Block-Capture-System.patch b/paper-server/patches/features/0032-Block-Capture-System.patch index 16678b340142..4c668413904c 100644 --- a/paper-server/patches/features/0032-Block-Capture-System.patch +++ b/paper-server/patches/features/0032-Block-Capture-System.patch @@ -1365,10 +1365,10 @@ index 2a24e71d2b598792ebaa867a2f74fd79e97cc6a2..5f39cc10064c222f9009bdee5ce59bfe } } diff --git a/net/minecraft/world/level/block/FungusBlock.java b/net/minecraft/world/level/block/FungusBlock.java -index 9711efb088bd0da9168e9bcd0496bd7caddd2974..6e7e4aa0e8dc9dd3dd92f29d5603255ba176b252 100644 +index 9711efb088bd0da9168e9bcd0496bd7caddd2974..a3f9d237ae3a80ef5d28105297c2898fe33c3e79 100644 --- a/net/minecraft/world/level/block/FungusBlock.java +++ b/net/minecraft/world/level/block/FungusBlock.java -@@ -71,7 +71,7 @@ public class FungusBlock extends VegetationBlock implements BonemealableBlock { +@@ -71,14 +71,14 @@ public class FungusBlock extends VegetationBlock implements BonemealableBlock { } @Override @@ -1377,6 +1377,15 @@ index 9711efb088bd0da9168e9bcd0496bd7caddd2974..6e7e4aa0e8dc9dd3dd92f29d5603255b this.getFeature(level) // CraftBukkit start .map((value) -> { + if (this == Blocks.WARPED_FUNGUS) { +- SaplingBlock.treeType = org.bukkit.TreeType.WARPED_FUNGUS; ++ mealContext.treeHook = org.bukkit.TreeType.WARPED_FUNGUS; + } else if (this == Blocks.CRIMSON_FUNGUS) { +- SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS; ++ mealContext.treeHook = org.bukkit.TreeType.CRIMSON_FUNGUS; + } + return value; + }) diff --git a/net/minecraft/world/level/block/GlowLichenBlock.java b/net/minecraft/world/level/block/GlowLichenBlock.java index ba39497a6d7160cde961e339be8028ec131b8019..f38447dbc1880878d29e08a18a2db32319291992 100644 --- a/net/minecraft/world/level/block/GlowLichenBlock.java @@ -1633,10 +1642,18 @@ index db6b32016a1ad4264da9d8812e5ea9356d0601df..19000f09efadb6a2c616badfb837487a } diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java -index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..779b181fb8e1a65844607a85270ec1d9b732f761 100644 +index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..bde0874cf7528d4eb5eeeef76b840ab2d6ccabcd 100644 --- a/net/minecraft/world/level/block/SaplingBlock.java +++ b/net/minecraft/world/level/block/SaplingBlock.java -@@ -50,38 +50,18 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { +@@ -25,7 +25,6 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { + public static final IntegerProperty STAGE = BlockStateProperties.STAGE; + private static final VoxelShape SHAPE = Block.column(12.0, 0.0, 12.0); + protected final TreeGrower treeGrower; +- public static @javax.annotation.Nullable org.bukkit.TreeType treeType; // CraftBukkit + + @Override + public MapCodec codec() { +@@ -50,38 +49,18 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { } } @@ -1684,7 +1701,7 @@ index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..779b181fb8e1a65844607a85270ec1d9 } } -@@ -96,8 +76,8 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { +@@ -96,8 +75,8 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { } @Override @@ -1924,15 +1941,14 @@ index c57cb7b47a01e313a1be5de769c6766ac2787d95..f34731580bc880dcc5d3e96923a7f2b4 } } diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java -index 5471619a0484ece08640e2b3fd26746c351dc3e0..780d1f9ee0c3fd4ea3e0ccf163767b73a7db7c23 100644 +index 5471619a0484ece08640e2b3fd26746c351dc3e0..05c24bb6a12dfb8d9abdb71b1e8ade9032fc4349 100644 --- a/net/minecraft/world/level/block/grower/TreeGrower.java +++ b/net/minecraft/world/level/block/grower/TreeGrower.java -@@ -122,7 +122,36 @@ public final class TreeGrower { +@@ -122,7 +122,35 @@ public final class TreeGrower { return this.secondaryMegaTree.isPresent() && random.nextFloat() < this.secondaryChance ? this.secondaryMegaTree.get() : this.megaTree.orElse(null); } - public boolean growTree(ServerLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random) { -+ // TODO: SUPPORT WRAPPING + // Paper start + public boolean growTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random) { + return this.growTree0(level, chunkGenerator, pos, state, random, null); @@ -1941,11 +1957,11 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..780d1f9ee0c3fd4ea3e0ccf163767b73 + public boolean growTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.BoneMealContext mealContext) { + try (io.papermc.paper.util.capture.SimpleBlockCapture capture = level.forkCaptureSession()) { + io.papermc.paper.util.capture.MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); -+ if (this.growTree0(captureTreeGeneration, chunkGenerator, pos, state, random, mealContext.treeHook)) { ++ if (this.growTree0(captureTreeGeneration, chunkGenerator, pos, state, random, mealContext)) { + var states = captureTreeGeneration.calculateLatestBlockStates(mealContext.ogServerLevel); + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, mealContext.ogServerLevel); + java.util.List blocks = new java.util.ArrayList<>(states.values()); -+ org.bukkit.event.world.StructureGrowEvent event = new org.bukkit.event.world.StructureGrowEvent(location, mealContext.treeHook.get(), false, mealContext.getBukkitPlayer(), blocks); ++ org.bukkit.event.world.StructureGrowEvent event = new org.bukkit.event.world.StructureGrowEvent(location, mealContext.treeHook, false, mealContext.getBukkitPlayer(), blocks); + + if (event.callEvent()) { + captureTreeGeneration.applyTasks(); @@ -1960,30 +1976,30 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..780d1f9ee0c3fd4ea3e0ccf163767b73 + + return false; + } -+ private boolean growTree0(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random, java.util.concurrent.atomic.AtomicReference treeType) { ++ private boolean growTree0(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.BoneMealContext mealContext) { + // Paper end ResourceKey> configuredMegaFeature = this.getConfiguredMegaFeature(random); if (configuredMegaFeature != null) { Holder> holder = level.registryAccess() -@@ -130,7 +159,7 @@ public final class TreeGrower { +@@ -130,7 +158,7 @@ public final class TreeGrower { .get(configuredMegaFeature) .orElse(null); if (holder != null) { - this.setTreeType(holder); // CraftBukkit -+ if (treeType != null) treeType.set(this.getTreeType(holder)); // Paper ++ if (mealContext != null) mealContext.treeHook = this.getTreeType(holder); // Paper for (int i = 0; i >= -1; i--) { for (int i1 = 0; i1 >= -1; i1--) { if (isTwoByTwoSapling(state, level, pos, i, i1)) { -@@ -163,7 +192,7 @@ public final class TreeGrower { +@@ -163,7 +191,7 @@ public final class TreeGrower { if (holder1 == null) { return false; } else { - this.setTreeType(holder1); // CraftBukkit -+ treeType.set(this.getTreeType(holder1)); // Paper ++ if (mealContext != null) mealContext.treeHook = this.getTreeType(holder1); // Paper ConfiguredFeature configuredFeature2 = holder1.value(); BlockState blockState1 = level.getFluidState(pos).createLegacyBlock(); level.setBlock(pos, blockState1, Block.UPDATE_NONE); -@@ -200,53 +229,53 @@ public final class TreeGrower { +@@ -200,53 +228,53 @@ public final class TreeGrower { } // CraftBukkit start diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java b/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java index ac7123a2bc71..53e49ff42239 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java @@ -2,6 +2,7 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import org.bukkit.TreeType; import org.bukkit.entity.Player; import org.jspecify.annotations.Nullable; @@ -12,7 +13,7 @@ public class BoneMealContext { public ServerLevel ogServerLevel; public ServerPlayer player; public boolean precancelStructureEvent = false; - public java.util.concurrent.atomic.AtomicReference treeHook = new AtomicReference<>(); // just make this a field + public TreeType treeHook; // just make this a field @Nullable From f46fbbe20b4344b534458ba4b1fb14836d424a90 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:40:37 -0500 Subject: [PATCH 10/17] Fix block against logic TODO: Lillypad logic looks odd --- .../features/0032-Block-Capture-System.patch | 30 ++++++++++++++----- .../io/papermc/testplugin/TestPlugin.java | 2 ++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/paper-server/patches/features/0032-Block-Capture-System.patch b/paper-server/patches/features/0032-Block-Capture-System.patch index 4c668413904c..a66709cd8ef7 100644 --- a/paper-server/patches/features/0032-Block-Capture-System.patch +++ b/paper-server/patches/features/0032-Block-Capture-System.patch @@ -177,7 +177,7 @@ index 2818c7ad02a861270283384ceb7ecd4d44f6d624..f2cd7765da1ee5138aba9505c5935444 } } diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java -index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a2e923e9ac2a10d928bfbeb8fdf62ecbab675ddc 100644 +index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a7254db42f001a4bd3b952563307600867a1c6f0 100644 --- a/net/minecraft/world/item/BlockItem.java +++ b/net/minecraft/world/item/BlockItem.java @@ -1,13 +1,18 @@ @@ -270,7 +270,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a2e923e9ac2a10d928bfbeb8fdf62ecb level.playSound( player, clickedPos, -@@ -119,8 +96,41 @@ public class BlockItem extends Item { +@@ -119,8 +96,42 @@ public class BlockItem extends Item { soundType.getPitch() * 0.8F ); level.gameEvent(GameEvent.BLOCK_PLACE, clickedPos, GameEvent.Context.of(player, blockState)); @@ -288,10 +288,11 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a2e923e9ac2a10d928bfbeb8fdf62ecb + + capture.overlayCaptureOnLevel(); + ++ BlockPos relative = context.getHitResult().getBlockPos(); + if (blocks.size() > 1) { -+ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, clickedPos); ++ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, relative); + } else if (blocks.size() == 1) { -+ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(serverLevel, player, hand, blocks.getFirst(), clickedPos); ++ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(serverLevel, player, hand, blocks.getFirst(), relative); + } + + if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) { @@ -313,7 +314,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a2e923e9ac2a10d928bfbeb8fdf62ecb } } } -@@ -134,7 +144,7 @@ public class BlockItem extends Item { +@@ -134,7 +145,7 @@ public class BlockItem extends Item { return context; } @@ -322,7 +323,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a2e923e9ac2a10d928bfbeb8fdf62ecb BlockEntity blockEntity = level.getBlockEntity(pos); if (blockEntity != null) { blockEntity.applyComponentsFromItemStack(stack); -@@ -142,7 +152,7 @@ public class BlockItem extends Item { +@@ -142,7 +153,7 @@ public class BlockItem extends Item { } } @@ -331,7 +332,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a2e923e9ac2a10d928bfbeb8fdf62ecb return updateCustomBlockEntityTag(level, player, pos, stack); } -@@ -151,7 +161,7 @@ public class BlockItem extends Item { +@@ -151,7 +162,7 @@ public class BlockItem extends Item { return stateForPlacement != null && this.canPlace(context, stateForPlacement) ? stateForPlacement : null; } @@ -340,7 +341,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a2e923e9ac2a10d928bfbeb8fdf62ecb BlockItemStateProperties blockItemStateProperties = stack.getOrDefault(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY); if (blockItemStateProperties.isEmpty()) { return state; -@@ -186,11 +196,11 @@ public class BlockItem extends Item { +@@ -186,11 +197,11 @@ public class BlockItem extends Item { return true; } @@ -688,6 +689,19 @@ index 3a41cf80e347ae3b30858879fb91f719625c8bb6..334f30499b8e043bf74ebe7001ded66c } return flag; +diff --git a/net/minecraft/world/item/context/UseOnContext.java b/net/minecraft/world/item/context/UseOnContext.java +index fc841780fe6e0ca2aca119ce3ba78da8e1c10d77..cd5137fdd17a1403c4dbc5b91a22002478a859e4 100644 +--- a/net/minecraft/world/item/context/UseOnContext.java ++++ b/net/minecraft/world/item/context/UseOnContext.java +@@ -29,7 +29,7 @@ public class UseOnContext { + this.level = level; + } + +- protected final BlockHitResult getHitResult() { ++ public final BlockHitResult getHitResult() { // PAPER: TODO AT + return this.hitResult; + } + diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java index 579bbba4e823d4d0318e58759ca732b7c8e4d865..d9a116d9c15dab1396a51d86cc661d925500a657 100644 --- a/net/minecraft/world/level/Level.java diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java index a6bab74ca9ef..7ea6124b9e8e 100644 --- a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java +++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java @@ -85,6 +85,8 @@ public void blockPlace(BlockPlaceEvent event) { event.getBlock().getRelative(BlockFace.SOUTH).setType(Material.STONE); } event.getPlayer().sendMessage(event.getBlock().getType().toString()); + + event.getBlockAgainst().setType(Material.DIAMOND_BLOCK); } @EventHandler From 84a73d8984e053431d61195b090283957aaf63af Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Wed, 11 Feb 2026 21:06:32 +0100 Subject: [PATCH 11/17] format --- .../features/0032-Block-Capture-System.patch | 341 +++++++----------- .../util/capture/BlockPlacementPredictor.java | 17 +- .../paper/util/capture/BoneMealContext.java | 9 +- .../paper/util/capture/CaptureRecordMap.java | 43 +-- .../LayeredBlockPlacementPredictor.java | 14 +- .../util/capture/LiveBlockPlacementLayer.java | 13 +- .../util/capture/MinecraftCaptureBridge.java | 87 +++-- .../capture/PaperCapturingWorldLevel.java | 12 +- .../ServerLevelPaperCapturingWorldLevel.java | 18 +- .../util/capture/SimpleBlockCapture.java | 23 +- .../SimpleBlockPlacementPredictor.java | 25 +- .../paper/util/capture/WorldCapturer.java | 22 +- .../paper/util/capture/package-info.java | 4 + .../org/bukkit/craftbukkit/CraftWorld.java | 6 +- .../craftbukkit/event/CraftEventFactory.java | 55 ++- .../io/papermc/testplugin/TestPlugin.java | 23 +- 16 files changed, 305 insertions(+), 407 deletions(-) create mode 100644 paper-server/src/main/java/io/papermc/paper/util/capture/package-info.java diff --git a/paper-server/patches/features/0032-Block-Capture-System.patch b/paper-server/patches/features/0032-Block-Capture-System.patch index a66709cd8ef7..cd0706608fe3 100644 --- a/paper-server/patches/features/0032-Block-Capture-System.patch +++ b/paper-server/patches/features/0032-Block-Capture-System.patch @@ -19,21 +19,11 @@ big todos: - Dispenser related stuff - Bone meal related stuff, we need to pass more context in order to get the tree event properly working - - diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java -index bfefb5031544caa59230f0073e8880c2b39ebf4d..0cb28b27e39670b307148377d28398645c4451c4 100644 +index bfefb5031544caa59230f0073e8880c2b39ebf4d..40810b410c92f8fd2edaec7443450c403171ee11 100644 --- a/net/minecraft/core/dispenser/DispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -9,6 +9,7 @@ import net.minecraft.core.Direction; - import net.minecraft.core.component.DataComponents; - import net.minecraft.core.particles.ParticleTypes; - import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; - import net.minecraft.sounds.SoundEvents; - import net.minecraft.sounds.SoundSource; - import net.minecraft.tags.BlockTags; -@@ -400,46 +401,22 @@ public interface DispenseItemBehavior { +@@ -400,46 +400,22 @@ public interface DispenseItemBehavior { this.setSuccess(true); Level level = blockSource.level(); BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING)); @@ -57,7 +47,7 @@ index bfefb5031544caa59230f0073e8880c2b39ebf4d..0cb28b27e39670b307148377d2839864 + (res) -> BoneMealItem.growWaterPlant(item, res, blockPos, null, boneMealContext), + boneMealContext.precancelStructureEvent + )) { -+ // Paper end ++ // Paper end this.setSuccess(false); } else if (!level.isClientSide()) { level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, blockPos, 15); @@ -93,7 +83,7 @@ index bfefb5031544caa59230f0073e8880c2b39ebf4d..0cb28b27e39670b307148377d2839864 return item; } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index dc65503a2d785d64d37b76b0303f51cf66d9769a..9ea4dc4d45f4060955e24b0c80143b852539027d 100644 +index dc65503a2d785d64d37b76b0303f51cf66d9769a..1b4ccfbbbebe36656d65facaf87dd7530c415bd6 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -181,7 +181,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter; @@ -101,7 +91,7 @@ index dc65503a2d785d64d37b76b0303f51cf66d9769a..9ea4dc4d45f4060955e24b0c80143b85 import org.slf4j.Logger; -public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevelReader, ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel { // Paper - rewrite chunk system // Paper - chunk tick iteration -+public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevelReader, ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel, io.papermc.paper.util.capture.ServerLevelPaperCapturingWorldLevel { // Paper - rewrite chunk system // Paper - chunk tick iteration // Paper - ++public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevelReader, ca.spottedleaf.moonrise.patches.chunk_tick_iteration.ChunkTickServerLevel, io.papermc.paper.util.capture.ServerLevelPaperCapturingWorldLevel { // Paper - rewrite chunk system // Paper - chunk tick iteration public static final BlockPos END_SPAWN_POINT = new BlockPos(100, 50, 0); public static final IntProvider RAIN_DELAY = UniformInt.of(12000, 180000); public static final IntProvider RAIN_DURATION = UniformInt.of(12000, 24000); @@ -119,19 +109,21 @@ index dc65503a2d785d64d37b76b0303f51cf66d9769a..9ea4dc4d45f4060955e24b0c80143b85 this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, null, orientation); } -@@ -2669,6 +2667,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2669,6 +2667,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe return this.randomSequences; } ++ // Paper start - block placement capturing + @Override + public ServerLevel handle() { + return this; + } ++ // Paper end - block placement capturing + public GameRules getGameRules() { return this.serverLevelData.getGameRules(); } -@@ -2683,17 +2686,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2683,17 +2688,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe return range <= 0 ? 64.0 * 64.0 : range * range; // 64 is taken from default in ServerLevel#levelEvent } // Paper end - respect global sound events gamerule @@ -150,7 +142,7 @@ index dc65503a2d785d64d37b76b0303f51cf66d9769a..9ea4dc4d45f4060955e24b0c80143b85 @Override public CrashReportCategory fillReportDetails(CrashReport report) { diff --git a/net/minecraft/world/entity/ai/behavior/UseBonemeal.java b/net/minecraft/world/entity/ai/behavior/UseBonemeal.java -index d815149ba20d815ec11fd7d4c203aa407e7aa8b2..78d5ab312a806b94403ddf38babea69a307690c1 100644 +index d815149ba20d815ec11fd7d4c203aa407e7aa8b2..67118f27b9546c0e6afcf9e4e1dedacc90445774 100644 --- a/net/minecraft/world/entity/ai/behavior/UseBonemeal.java +++ b/net/minecraft/world/entity/ai/behavior/UseBonemeal.java @@ -113,7 +113,7 @@ public class UseBonemeal extends Behavior { @@ -158,12 +150,12 @@ index d815149ba20d815ec11fd7d4c203aa407e7aa8b2..78d5ab312a806b94403ddf38babea69a } - if (!itemStack.isEmpty() && BoneMealItem.growCrop(itemStack, level, blockPos)) { -+ if (!itemStack.isEmpty() && BoneMealItem.growCrop(itemStack, level, blockPos, null)) { ++ if (!itemStack.isEmpty() && BoneMealItem.growCrop(itemStack, level, blockPos, null)) { // Paper level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, blockPos, 15); this.cropPos = this.pickNextTarget(level, owner); this.setCurrentCropAsTarget(owner); diff --git a/net/minecraft/world/item/BedItem.java b/net/minecraft/world/item/BedItem.java -index 2818c7ad02a861270283384ceb7ecd4d44f6d624..f2cd7765da1ee5138aba9505c59354447ad061f8 100644 +index 2818c7ad02a861270283384ceb7ecd4d44f6d624..56823e158da5e852d914b06ed24289038c90d685 100644 --- a/net/minecraft/world/item/BedItem.java +++ b/net/minecraft/world/item/BedItem.java @@ -10,7 +10,7 @@ public class BedItem extends BlockItem { @@ -172,34 +164,15 @@ index 2818c7ad02a861270283384ceb7ecd4d44f6d624..f2cd7765da1ee5138aba9505c5935444 @Override - protected boolean placeBlock(BlockPlaceContext context, BlockState state) { - return context.getLevel().setBlock(context.getClickedPos(), state, Block.UPDATE_CLIENTS | Block.UPDATE_IMMEDIATE | Block.UPDATE_KNOWN_SHAPE); -+ protected boolean placeBlock(BlockPlaceContext context, BlockState state, io.papermc.paper.util.capture.PaperCapturingWorldLevel paperCapturingWorldLevel) { -+ return paperCapturingWorldLevel.setBlock(context.getClickedPos(), state, Block.UPDATE_CLIENTS | Block.UPDATE_IMMEDIATE | Block.UPDATE_KNOWN_SHAPE); ++ protected boolean placeBlock(BlockPlaceContext context, BlockState state, io.papermc.paper.util.capture.PaperCapturingWorldLevel level) { // Paper - block placement capturing ++ return level.setBlock(context.getClickedPos(), state, Block.UPDATE_CLIENTS | Block.UPDATE_IMMEDIATE | Block.UPDATE_KNOWN_SHAPE); // Paper - block placement capturing } } diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java -index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a7254db42f001a4bd3b952563307600867a1c6f0 100644 +index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..3707f24b93a24f2d6622d86e056267077f005b43 100644 --- a/net/minecraft/world/item/BlockItem.java +++ b/net/minecraft/world/item/BlockItem.java -@@ -1,13 +1,18 @@ - package net.minecraft.world.item; - - import java.util.Map; -+ -+import io.papermc.paper.util.capture.PaperCapturingWorldLevel; -+import io.papermc.paper.util.capture.SimpleBlockCapture; - import net.minecraft.advancements.CriteriaTriggers; - import net.minecraft.core.BlockPos; - import net.minecraft.core.component.DataComponents; -+import net.minecraft.server.level.ServerLevel; - import net.minecraft.server.level.ServerPlayer; - import net.minecraft.server.permissions.Permissions; - import net.minecraft.sounds.SoundEvent; - import net.minecraft.sounds.SoundSource; -+import net.minecraft.world.InteractionHand; - import net.minecraft.world.InteractionResult; - import net.minecraft.world.entity.item.ItemEntity; - import net.minecraft.world.entity.player.Player; -@@ -57,59 +62,31 @@ public class BlockItem extends Item { +@@ -57,59 +57,31 @@ public class BlockItem extends Item { return InteractionResult.FAIL; } else { BlockState placementState = this.getPlacementState(blockPlaceContext); @@ -217,8 +190,8 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a7254db42f001a4bd3b9525633076008 - return InteractionResult.FAIL; - } else { + } -+ // Paper start -+ try ( SimpleBlockCapture capture = ((ServerLevel) context.getLevel()).forkCaptureSession()) { ++ // Paper start ++ try (io.papermc.paper.util.capture.SimpleBlockCapture capture = ((net.minecraft.server.level.ServerLevel) context.getLevel()).forkCaptureSession()) { + boolean placeRes = this.placeBlock(blockPlaceContext, placementState, capture.capturingWorldLevel()); + if (!placeRes) { + return InteractionResult.FAIL; @@ -270,29 +243,29 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a7254db42f001a4bd3b9525633076008 level.playSound( player, clickedPos, -@@ -119,8 +96,42 @@ public class BlockItem extends Item { +@@ -119,8 +91,42 @@ public class BlockItem extends Item { soundType.getPitch() * 0.8F ); level.gameEvent(GameEvent.BLOCK_PLACE, clickedPos, GameEvent.Context.of(player, blockState)); + // Paper start -+ InteractionHand hand = blockPlaceContext.getHand(); -+ ServerLevel serverLevel = (ServerLevel) blockPlaceContext.getLevel(); ++ net.minecraft.world.InteractionHand hand = blockPlaceContext.getHand(); ++ net.minecraft.server.level.ServerLevel serverLevel = (net.minecraft.server.level.ServerLevel) blockPlaceContext.getLevel(); + org.bukkit.event.block.BlockPlaceEvent placeEvent = null; + -+ var newStates = capture.getCapturedBlockStates(); ++ Map snapshots = capture.getCapturedSnapshots(); + -+ java.util.List blocks = new java.util.ArrayList<>(newStates.size()); -+ newStates.values().forEach((state) -> { ++ java.util.List blocks = new java.util.ArrayList<>(snapshots.size()); ++ snapshots.values().forEach((state) -> { + blocks.add(state.getBlock().getState()); + }); + + capture.overlayCaptureOnLevel(); + -+ BlockPos relative = context.getHitResult().getBlockPos(); ++ BlockPos relativePos = context.getHitResult().getBlockPos(); + if (blocks.size() > 1) { -+ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, relative); ++ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, relativePos); + } else if (blocks.size() == 1) { -+ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(serverLevel, player, hand, blocks.getFirst(), relative); ++ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(serverLevel, player, hand, blocks.getFirst(), relativePos); + } + + if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) { @@ -314,7 +287,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a7254db42f001a4bd3b9525633076008 } } } -@@ -134,7 +145,7 @@ public class BlockItem extends Item { +@@ -134,7 +140,7 @@ public class BlockItem extends Item { return context; } @@ -323,7 +296,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a7254db42f001a4bd3b9525633076008 BlockEntity blockEntity = level.getBlockEntity(pos); if (blockEntity != null) { blockEntity.applyComponentsFromItemStack(stack); -@@ -142,7 +153,7 @@ public class BlockItem extends Item { +@@ -142,7 +148,7 @@ public class BlockItem extends Item { } } @@ -332,7 +305,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a7254db42f001a4bd3b9525633076008 return updateCustomBlockEntityTag(level, player, pos, stack); } -@@ -151,7 +162,7 @@ public class BlockItem extends Item { +@@ -151,7 +157,7 @@ public class BlockItem extends Item { return stateForPlacement != null && this.canPlace(context, stateForPlacement) ? stateForPlacement : null; } @@ -341,14 +314,14 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a7254db42f001a4bd3b9525633076008 BlockItemStateProperties blockItemStateProperties = stack.getOrDefault(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY); if (blockItemStateProperties.isEmpty()) { return state; -@@ -186,11 +197,11 @@ public class BlockItem extends Item { +@@ -186,11 +192,11 @@ public class BlockItem extends Item { return true; } - protected boolean placeBlock(BlockPlaceContext context, BlockState state) { - return context.getLevel().setBlock(context.getClickedPos(), state, Block.UPDATE_ALL_IMMEDIATE); -+ protected boolean placeBlock(BlockPlaceContext context, BlockState state, PaperCapturingWorldLevel paperCapturingWorldLevel) { -+ return paperCapturingWorldLevel.setBlock(context.getClickedPos(), state, Block.UPDATE_ALL_IMMEDIATE); ++ protected boolean placeBlock(BlockPlaceContext context, BlockState state, io.papermc.paper.util.capture.PaperCapturingWorldLevel level) { // Paper - block placement capturing ++ return level.setBlock(context.getClickedPos(), state, Block.UPDATE_ALL_IMMEDIATE); // Paper - block placement capturing } - public static boolean updateCustomBlockEntityTag(Level level, @Nullable Player player, BlockPos pos, ItemStack stack) { @@ -357,43 +330,29 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..a7254db42f001a4bd3b9525633076008 return false; } else { diff --git a/net/minecraft/world/item/BoneMealItem.java b/net/minecraft/world/item/BoneMealItem.java -index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..f28e2f96485776099c80a3692f2ea688f9ecc89c 100644 +index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..664a6a6a8a44233d7c2d13f432789fe6031dd746 100644 --- a/net/minecraft/world/item/BoneMealItem.java +++ b/net/minecraft/world/item/BoneMealItem.java -@@ -6,11 +6,13 @@ import net.minecraft.core.Holder; - import net.minecraft.core.particles.ParticleTypes; - import net.minecraft.core.registries.BuiltInRegistries; - import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; - import net.minecraft.tags.BiomeTags; - import net.minecraft.tags.BlockTags; - import net.minecraft.util.ParticleUtils; - import net.minecraft.util.RandomSource; - import net.minecraft.world.InteractionResult; -+import net.minecraft.world.entity.player.Player; - import net.minecraft.world.item.context.UseOnContext; - import net.minecraft.world.level.Level; - import net.minecraft.world.level.LevelAccessor; -@@ -44,7 +46,12 @@ public class BoneMealItem extends Item { +@@ -44,7 +44,12 @@ public class BoneMealItem extends Item { BlockPos clickedPos = context.getClickedPos(); BlockPos blockPos = clickedPos.relative(context.getClickedFace()); ItemStack itemInHand = context.getItemInHand(); - if (growCrop(itemInHand, level, clickedPos)) { + // Paper start + io.papermc.paper.util.capture.BoneMealContext boneMealContext = new io.papermc.paper.util.capture.BoneMealContext(); -+ boneMealContext.player = (ServerPlayer) context.getPlayer(); ++ boneMealContext.player = (net.minecraft.server.level.ServerPlayer) context.getPlayer(); + boneMealContext.ogServerLevel = level.getMinecraftWorld(); + // Paper end + if (growCrop(itemInHand, level, clickedPos, boneMealContext)) { // Paper if (!level.isClientSide()) { if (context.getPlayer() != null) itemInHand.causeUseVibration(context.getPlayer(), GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518 level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, clickedPos, 15); -@@ -54,7 +61,16 @@ public class BoneMealItem extends Item { +@@ -54,7 +59,16 @@ public class BoneMealItem extends Item { } else { BlockState blockState = level.getBlockState(clickedPos); boolean isFaceSturdy = blockState.isFaceSturdy(level, clickedPos, context.getClickedFace()); - if (isFaceSturdy && growWaterPlant(itemInHand, level, blockPos, context.getClickedFace())) { -+ ++ // Paper start + boolean result = org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( + (ServerLevel) level, + boneMealContext.getBukkitPlayer(), @@ -401,12 +360,12 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..f28e2f96485776099c80a3692f2ea688 + (res) -> growWaterPlant(itemInHand, res, blockPos, context.getClickedFace(), boneMealContext), + boneMealContext.precancelStructureEvent + ); -+ + if (isFaceSturdy && result) { ++ // Paper end if (!level.isClientSide()) { if (context.getPlayer() != null) itemInHand.causeUseVibration(context.getPlayer(), GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518 level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, blockPos, 15); -@@ -67,12 +83,18 @@ public class BoneMealItem extends Item { +@@ -67,12 +81,20 @@ public class BoneMealItem extends Item { } } @@ -417,6 +376,7 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..f28e2f96485776099c80a3692f2ea688 if (level instanceof ServerLevel) { if (bonemealableBlock.isBonemealSuccess(level, level.random, pos, blockState)) { - bonemealableBlock.performBonemeal((ServerLevel)level, level.random, pos, blockState); ++ // Paper start + org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( + (ServerLevel) level, + mealContext.getBukkitPlayer(), @@ -424,6 +384,7 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..f28e2f96485776099c80a3692f2ea688 + (res) -> bonemealableBlock.performBonemeal(res, level.random, pos, blockState, mealContext), + mealContext.precancelStructureEvent + ); ++ // Paper end } stack.shrink(1); @@ -441,7 +402,7 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..f28e2f96485776099c80a3692f2ea688 if (i == 0 && clickedSide != null && clickedSide.getAxis().isHorizontal()) { blockState = BuiltInRegistries.BLOCK - .getRandomElementOf(BlockTags.WALL_CORALS, level.random) -+ .getRandomElementOf(BlockTags.WALL_CORALS, level.getRandom()) // Paper ++ .getRandomElementOf(BlockTags.WALL_CORALS, level.getRandom()) // Paper .map(holder -> holder.value().defaultBlockState()) .orElse(blockState); if (blockState.hasProperty(BaseCoralWallFanBlock.FACING)) { @@ -464,23 +425,21 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..f28e2f96485776099c80a3692f2ea688 } } diff --git a/net/minecraft/world/item/DoubleHighBlockItem.java b/net/minecraft/world/item/DoubleHighBlockItem.java -index e6406e4d7ed3c340e3e3137165e9a2fa7e4f3656..f5faccc8951718b1bfe3fd644fcfe3993b6b5665 100644 +index e6406e4d7ed3c340e3e3137165e9a2fa7e4f3656..6b245cf238851900c409fa2f0c885c6ebee69a91 100644 --- a/net/minecraft/world/item/DoubleHighBlockItem.java +++ b/net/minecraft/world/item/DoubleHighBlockItem.java -@@ -13,11 +13,11 @@ public class DoubleHighBlockItem extends BlockItem { +@@ -13,11 +13,10 @@ public class DoubleHighBlockItem extends BlockItem { } @Override - protected boolean placeBlock(BlockPlaceContext context, BlockState state) { -+ protected boolean placeBlock(BlockPlaceContext context, BlockState state, io.papermc.paper.util.capture.PaperCapturingWorldLevel paperCapturingWorldLevel) { - Level level = context.getLevel(); +- Level level = context.getLevel(); ++ protected boolean placeBlock(BlockPlaceContext context, BlockState state, io.papermc.paper.util.capture.PaperCapturingWorldLevel level) { // Paper - block placement capturing BlockPos blockPos = context.getClickedPos().above(); -- BlockState blockState = level.isWaterAt(blockPos) ? Blocks.WATER.defaultBlockState() : Blocks.AIR.defaultBlockState(); -- level.setBlock(blockPos, blockState, Block.UPDATE_ALL_IMMEDIATE | Block.UPDATE_KNOWN_SHAPE); + BlockState blockState = level.isWaterAt(blockPos) ? Blocks.WATER.defaultBlockState() : Blocks.AIR.defaultBlockState(); + level.setBlock(blockPos, blockState, Block.UPDATE_ALL_IMMEDIATE | Block.UPDATE_KNOWN_SHAPE); - return super.placeBlock(context, state); -+ BlockState blockState = paperCapturingWorldLevel.isWaterAt(blockPos) ? Blocks.WATER.defaultBlockState() : Blocks.AIR.defaultBlockState(); -+ paperCapturingWorldLevel.setBlock(blockPos, blockState, Block.UPDATE_ALL_IMMEDIATE | Block.UPDATE_KNOWN_SHAPE); -+ return super.placeBlock(context, state, paperCapturingWorldLevel); ++ return super.placeBlock(context, state, level); // Paper - block placement capturing } } diff --git a/net/minecraft/world/item/ItemStack.java b/net/minecraft/world/item/ItemStack.java @@ -703,7 +662,7 @@ index fc841780fe6e0ca2aca119ce3ba78da8e1c10d77..cd5137fdd17a1403c4dbc5b91a220024 } diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java -index 579bbba4e823d4d0318e58759ca732b7c8e4d865..d9a116d9c15dab1396a51d86cc661d925500a657 100644 +index 579bbba4e823d4d0318e58759ca732b7c8e4d865..cdebff0efb78b98b941a8aac2cd1115b9d593b52 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -143,10 +143,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl @@ -727,7 +686,7 @@ index 579bbba4e823d4d0318e58759ca732b7c8e4d865..d9a116d9c15dab1396a51d86cc661d92 - CraftBlockState previous = this.capturedBlockStates.get(pos); - if (previous != null) { - return previous.getHandle(); -+ // Paper start - Perf: Optimize capturedTileEntities lookup ++ // Paper start + if (this.capturer.isCapturing() && this.capturer.getCapture().isOverlayingCaptureOnLevel()) { + BlockState guess = this.capturer.getCapture().getCaptureBlockStateIfLoaded(pos); + if (guess != null) { @@ -735,7 +694,7 @@ index 579bbba4e823d4d0318e58759ca732b7c8e4d865..d9a116d9c15dab1396a51d86cc661d92 } } - // CraftBukkit end -+ // Paper end - Perf: Optimize capturedTileEntities lookup ++ // Paper end if (this.isOutsideBuildHeight(pos)) { return Blocks.VOID_AIR.defaultBlockState(); } else { @@ -757,16 +716,16 @@ index 579bbba4e823d4d0318e58759ca732b7c8e4d865..d9a116d9c15dab1396a51d86cc661d92 - blockstate.setData(state); - blockstate.setFlags(flags); - return true; -+ // Paper start - Perf: Optimize capturedTileEntities lookup ++ // Paper start + if (this.capturer.isCapturing() && this.capturer.getCapture().isOverlayingCaptureOnLevel()) { + return this.capturer.getCapture().capturingWorldLevel().setBlockSilent(pos, state, flags, recursionLeft); } - // CraftBukkit end -+ // Paper end - Perf: Optimize capturedTileEntities lookup ++ // Paper end if (!this.isInValidBounds(pos)) { return false; } else if (!this.isClientSide() && this.isDebug()) { -@@ -1066,40 +1052,20 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +@@ -1066,32 +1052,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl } else { LevelChunk chunkAt = this.getChunkAt(pos); Block block = state.getBlock(); @@ -799,33 +758,22 @@ index 579bbba4e823d4d0318e58759ca732b7c8e4d865..d9a116d9c15dab1396a51d86cc661d92 if (blockState1 == state) { if (blockState != blockState1) { this.setBlocksDirty(pos, blockState, blockState1); - } - - if ((flags & Block.UPDATE_CLIENTS) != 0 -- && (!this.isClientSide() || (flags & Block.UPDATE_INVISIBLE) == 0) -- && (this.isClientSide() || chunkAt.getFullStatus() != null && chunkAt.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING))) { -+ && (!this.isClientSide() || (flags & Block.UPDATE_INVISIBLE) == 0) -+ && (this.isClientSide() || chunkAt.getFullStatus() != null && chunkAt.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING))) { - this.sendBlockUpdated(pos, blockState, state, flags); - } - -@@ -1112,74 +1078,27 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - +@@ -1113,73 +1079,27 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl if ((flags & Block.UPDATE_KNOWN_SHAPE) == 0 && recursionLeft > 0) { int i = flags & ~(Block.UPDATE_SUPPRESS_DROPS | Block.UPDATE_NEIGHBORS); -- blockState.updateIndirectNeighbourShapes(this, pos, i, recursionLeft - 1); -+ // CraftBukkit start -+ blockState.updateIndirectNeighbourShapes(this, pos, i, recursionLeft - 1); // Don't call an event for the old block to limit event spam -+ boolean cancelledUpdates = false; // Paper - Fix block place logic -+ if (((ServerLevel)this).hasPhysicsEvent) { // Paper - BlockPhysicsEvent + blockState.updateIndirectNeighbourShapes(this, pos, i, recursionLeft - 1); ++ // Paper start - call BlockPhysicsEvent ++ // Don't call an event for the old block to limit event spam ^ ++ boolean cancelledUpdates = false; ++ if (((ServerLevel)this).hasPhysicsEvent) { + org.bukkit.event.block.BlockPhysicsEvent event = new org.bukkit.event.block.BlockPhysicsEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this, pos), CraftBlockData.fromData(state)); -+ cancelledUpdates = !event.callEvent(); // Paper - Fix block place logic ++ cancelledUpdates = !event.callEvent(); + } -+ // CraftBukkit end -+ if (!cancelledUpdates) { // Paper - Fix block place logic ++ if (!cancelledUpdates) { ++ // Paper end - call BlockPhysicsEvent state.updateNeighbourShapes(this, pos, i, recursionLeft - 1); state.updateIndirectNeighbourShapes(this, pos, i, recursionLeft - 1); -+ } // Paper - Fix block place logic ++ } // Paper - call BlockPhysicsEvent } this.updatePOIOnBlockStateChange(pos, blockState, blockState1); @@ -894,54 +842,61 @@ index 579bbba4e823d4d0318e58759ca732b7c8e4d865..d9a116d9c15dab1396a51d86cc661d92 // CraftBukkit end public void updatePOIOnBlockStateChange(BlockPos pos, BlockState oldState, BlockState newState) { } -@@ -1288,12 +1207,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl +@@ -1287,14 +1207,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + @Override public BlockState getBlockState(BlockPos pos) { - // CraftBukkit start - tree generation +- // CraftBukkit start - tree generation - if (this.captureTreeGeneration) { - CraftBlockState previous = this.capturedBlockStates.get(pos); // Paper - if (previous != null) { - return previous.getHandle(); -+ // Paper start - Perf: Optimize capturedTileEntities lookup ++ // Paper start + if (this.capturer.isCapturing() && this.capturer.getCapture().isOverlayingCaptureOnLevel()) { + BlockState guess = this.capturer.getCapture().getOverlayBlockState(pos); + if (guess != null) { + return guess; } } -+ // Paper end - Perf: Optimize capturedTileEntities lookup - // CraftBukkit end +- // CraftBukkit end ++ // Paper end if (!this.isInValidBounds(pos)) { return Blocks.VOID_AIR.defaultBlockState(); -@@ -1576,9 +1497,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + } else { +@@ -1575,12 +1495,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + @Override public @Nullable BlockEntity getBlockEntity(BlockPos pos) { - // Paper start - Perf: Optimize capturedTileEntities lookup +- // Paper start - Perf: Optimize capturedTileEntities lookup - net.minecraft.world.level.block.entity.BlockEntity blockEntity; - if (!this.capturedTileEntities.isEmpty() && (blockEntity = this.capturedTileEntities.get(pos)) != null) { - return blockEntity; ++ // Paper start + if (this.capturer.isCapturing() && this.capturer.getCapture().isOverlayingCaptureOnLevel()) { -+ var guess = this.capturer.getCapture().getOverlayBlockEntity(pos); ++ java.util.Optional guess = this.capturer.getCapture().getOverlayBlockEntity(pos); + if (guess != null) { + return guess.orElse(null); + } } - // Paper end - Perf: Optimize capturedTileEntities lookup +- // Paper end - Perf: Optimize capturedTileEntities lookup ++ // Paper end if (!this.isInValidBounds(pos)) { -@@ -1593,12 +1516,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + return null; + } else { +@@ -1593,12 +1515,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl public void setBlockEntity(BlockEntity blockEntity) { BlockPos blockPos = blockEntity.getBlockPos(); if (this.isInValidBounds(blockPos)) { - // CraftBukkit start - if (this.captureBlockStates) { - this.capturedTileEntities.put(blockPos.immutable(), blockEntity); -+ // Paper start - Perf: Optimize capturedTileEntities lookup ++ // Paper start + if (this.capturer.isCapturing() && this.capturer.getCapture().isOverlayingCaptureOnLevel()) { + this.capturer.getCapture().capturingWorldLevel().setBlockEntity(blockEntity); return; } - // CraftBukkit end -+ // Paper end - Perf: Optimize capturedTileEntities lookup ++ // Paper end + this.getChunkAt(blockPos).addAndRegisterBlockEntity(blockEntity); } @@ -1012,7 +967,7 @@ index 81e2a279d4c29f5fe52387875489239515a8c82b..af797be959d91e30b444459f2ccc99c2 BlockPos blockPos = pos.below(2); BlockState blockState1 = level.getBlockState(blockPos); diff --git a/net/minecraft/world/level/block/BedBlock.java b/net/minecraft/world/level/block/BedBlock.java -index 7bfc62120cae4c5e83cb0d86b759b7ffb2336a95..45ee613f2021f769c9f940524a1a61db352decd5 100644 +index 7bfc62120cae4c5e83cb0d86b759b7ffb2336a95..3d5ed3d705c80772ac8cb7fff7b213bb3e92c41c 100644 --- a/net/minecraft/world/level/block/BedBlock.java +++ b/net/minecraft/world/level/block/BedBlock.java @@ -330,16 +330,11 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock @@ -1020,8 +975,9 @@ index 7bfc62120cae4c5e83cb0d86b759b7ffb2336a95..45ee613f2021f769c9f940524a1a61db @Override - public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { -+ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { - super.setPlacedBy(level, pos, state, placer, stack); +- super.setPlacedBy(level, pos, state, placer, stack); ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing ++ super.setPlacedBy(level, pos, state, placer, stack); // Paper - block placement capturing if (!level.isClientSide()) { BlockPos blockPos = pos.relative(state.getValue(FACING)); level.setBlock(blockPos, state.setValue(PART, BedPart.HEAD), Block.UPDATE_ALL); @@ -1034,23 +990,15 @@ index 7bfc62120cae4c5e83cb0d86b759b7ffb2336a95..45ee613f2021f769c9f940524a1a61db state.updateNeighbourShapes(level, pos, Block.UPDATE_ALL); } diff --git a/net/minecraft/world/level/block/BeetrootBlock.java b/net/minecraft/world/level/block/BeetrootBlock.java -index dbc912d514120a33f22959d6dc36ccf6ebc6be80..6fb29d9c58a4b304591463f9937f616f6c4d68e5 100644 +index dbc912d514120a33f22959d6dc36ccf6ebc6be80..f27840bd23d51b86f00cac7eede270cc39b04c77 100644 --- a/net/minecraft/world/level/block/BeetrootBlock.java +++ b/net/minecraft/world/level/block/BeetrootBlock.java -@@ -8,6 +8,7 @@ import net.minecraft.world.item.Items; - import net.minecraft.world.level.BlockGetter; - import net.minecraft.world.level.ItemLike; - import net.minecraft.world.level.Level; -+import net.minecraft.world.level.LevelAccessor; - import net.minecraft.world.level.block.state.BlockBehaviour; - import net.minecraft.world.level.block.state.BlockState; - import net.minecraft.world.level.block.state.StateDefinition; -@@ -54,7 +55,7 @@ public class BeetrootBlock extends CropBlock { +@@ -54,7 +54,7 @@ public class BeetrootBlock extends CropBlock { } @Override - protected int getBonemealAgeIncrease(Level level) { -+ protected int getBonemealAgeIncrease(LevelAccessor level) { // Paper ++ protected int getBonemealAgeIncrease(net.minecraft.world.level.LevelAccessor level) { // Paper return super.getBonemealAgeIncrease(level) / 3; } @@ -1094,19 +1042,21 @@ index 94b4143449c99ee35db44ab8e2a766d924aa6410..9b9cc245b1fc42d9747de68049189935 public boolean isPossibleToRespawnInThis(BlockState state) { diff --git a/net/minecraft/world/level/block/BonemealableBlock.java b/net/minecraft/world/level/block/BonemealableBlock.java -index 676cc6cb2ea0ac25c4e4bd6a32856f07784c7b49..959c4dacca5c8cadb2cb0e63f88e355af95617e5 100644 +index 676cc6cb2ea0ac25c4e4bd6a32856f07784c7b49..5eba300c458f2b897b89409eb7d2873efd5974d2 100644 --- a/net/minecraft/world/level/block/BonemealableBlock.java +++ b/net/minecraft/world/level/block/BonemealableBlock.java -@@ -15,14 +15,18 @@ public interface BonemealableBlock { +@@ -15,14 +15,20 @@ public interface BonemealableBlock { boolean isBonemealSuccess(Level level, RandomSource random, BlockPos pos, BlockState state); - void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state); ++ // Paper start + default void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state) { + performBonemeal(level, random, pos, state, null); + } + -+ void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext); // Paper ++ void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext); ++ // Paper end static boolean hasSpreadableNeighbourPos(LevelReader level, BlockPos pos, BlockState state) { return getSpreadableNeighbourPos(Direction.Plane.HORIZONTAL.stream().toList(), level, pos, state).isPresent(); @@ -1120,7 +1070,7 @@ index 676cc6cb2ea0ac25c4e4bd6a32856f07784c7b49..959c4dacca5c8cadb2cb0e63f88e355a private static Optional getSpreadableNeighbourPos(List directions, LevelReader level, BlockPos pos, BlockState state) { diff --git a/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java b/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java -index ee701a4c5042aec359271533680d292a6169d4db..9f0a3e2225b17cbacfd9de2e40c64bcec568555c 100644 +index ee701a4c5042aec359271533680d292a6169d4db..a272f5d9f5fe36909e97d7ad10636c6ef98f43bf 100644 --- a/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java +++ b/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java @@ -41,11 +41,11 @@ public class BonemealableFeaturePlacerBlock extends Block implements Bonemealabl @@ -1133,7 +1083,7 @@ index ee701a4c5042aec359271533680d292a6169d4db..9f0a3e2225b17cbacfd9de2e40c64bce .lookup(Registries.CONFIGURED_FEATURE) .flatMap(registry -> registry.get(this.feature)) - .ifPresent(reference -> reference.value().place(level, level.getChunkSource().getGenerator(), random, pos.above())); -+ .ifPresent(reference -> reference.value().place(level, level.getGenerator(), random, pos.above())); ++ .ifPresent(reference -> reference.value().place(level, level.getGenerator(), random, pos.above())); // Paper } @Override @@ -1247,35 +1197,27 @@ index c5fe15844d405a27cdae18c903dd481c25b437de..abb7d98c6608ec1bbed169327114245d level.scheduleTick(pos, this, 4); } diff --git a/net/minecraft/world/level/block/CropBlock.java b/net/minecraft/world/level/block/CropBlock.java -index 1cf40fafd822d976ef4822335c60d8017659916f..e9b49027c02160304b917bd5f1b03b2929e0e87a 100644 +index 1cf40fafd822d976ef4822335c60d8017659916f..171b7f79244bae8aab49cf8b2db6f03662b3d990 100644 --- a/net/minecraft/world/level/block/CropBlock.java +++ b/net/minecraft/world/level/block/CropBlock.java -@@ -13,6 +13,7 @@ import net.minecraft.world.item.Items; - import net.minecraft.world.level.BlockGetter; - import net.minecraft.world.level.ItemLike; - import net.minecraft.world.level.Level; -+import net.minecraft.world.level.LevelAccessor; - import net.minecraft.world.level.LevelReader; - import net.minecraft.world.level.block.state.BlockBehaviour; - import net.minecraft.world.level.block.state.BlockState; -@@ -104,13 +105,13 @@ public class CropBlock extends VegetationBlock implements BonemealableBlock { +@@ -104,13 +104,13 @@ public class CropBlock extends VegetationBlock implements BonemealableBlock { } } - public void growCrops(Level level, BlockPos pos, BlockState state) { -+ public void growCrops(LevelAccessor level, BlockPos pos, BlockState state) { // Paper ++ public void growCrops(net.minecraft.world.level.LevelAccessor level, BlockPos pos, BlockState state) { // Paper int min = Math.min(this.getMaxAge(), this.getAge(state) + this.getBonemealAgeIncrease(level)); org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, this.getStateForAge(min), Block.UPDATE_CLIENTS); // CraftBukkit } - protected int getBonemealAgeIncrease(Level level) { - return Mth.nextInt(level.random, 2, 5); -+ protected int getBonemealAgeIncrease(LevelAccessor level) { ++ protected int getBonemealAgeIncrease(net.minecraft.world.level.LevelAccessor level) { // Paper + return Mth.nextInt(level.getRandom(), 2, 5); // Paper } protected static float getGrowthSpeed(Block block, BlockGetter level, BlockPos pos) { -@@ -196,7 +197,7 @@ public class CropBlock extends VegetationBlock implements BonemealableBlock { +@@ -196,7 +196,7 @@ public class CropBlock extends VegetationBlock implements BonemealableBlock { } @Override @@ -1285,7 +1227,7 @@ index 1cf40fafd822d976ef4822335c60d8017659916f..e9b49027c02160304b917bd5f1b03b29 } diff --git a/net/minecraft/world/level/block/DiodeBlock.java b/net/minecraft/world/level/block/DiodeBlock.java -index 02ffb5569e2405d86b3a4a695dd17c9372169ff7..f8bee20b29fddd83bb60526eb9ef4ea7e43092d8 100644 +index 02ffb5569e2405d86b3a4a695dd17c9372169ff7..9a038e60c48942d5b252e0e12f99ca7ce499a85e 100644 --- a/net/minecraft/world/level/block/DiodeBlock.java +++ b/net/minecraft/world/level/block/DiodeBlock.java @@ -164,10 +164,14 @@ public abstract class DiodeBlock extends HorizontalDirectionalBlock { @@ -1296,14 +1238,14 @@ index 02ffb5569e2405d86b3a4a695dd17c9372169ff7..f8bee20b29fddd83bb60526eb9ef4ea7 - if (this.shouldTurnOn(level, pos, state)) { - level.scheduleTick(pos, this, 1); - } ++ // Paper start - block placement capturing + public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { -+ // Paper start + level.addTask((serverLevel) -> { + if (this.shouldTurnOn(serverLevel, pos, state)) { + level.scheduleTick(pos, this, 1); + } + }); -+ // Paper end ++ // Paper end - block placement capturing } @Override @@ -1541,10 +1483,10 @@ index 3762d833f17d596e04845a3f391423f9c14a878b..f5d03f2a3a3866857ddaece77ac4e127 if (!blockState.isAir()) { level.setBlock(pos.above(), blockState, Block.UPDATE_ALL); diff --git a/net/minecraft/world/level/block/MushroomBlock.java b/net/minecraft/world/level/block/MushroomBlock.java -index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..33f8f4742a39dd761eddc5512e05b424f998240e 100644 +index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..0033bbcf49da014a0b12a17b47833af6dddafc50 100644 --- a/net/minecraft/world/level/block/MushroomBlock.java +++ b/net/minecraft/world/level/block/MushroomBlock.java -@@ -87,16 +87,18 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock +@@ -87,16 +87,20 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock return blockState.is(BlockTags.MUSHROOM_GROW_BLOCK) || level.getRawBrightness(pos, 0) < 13 && this.mayPlaceOn(blockState, level, blockPos); } @@ -1557,16 +1499,18 @@ index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..33f8f4742a39dd761eddc5512e05b424 level.removeBlock(pos, false); - SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit - if (optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) { ++ // Paper start + if (org.bukkit.craftbukkit.event.CraftEventFactory.structureEvent(mealContext.ogServerLevel, level, mealContext.getBukkitPlayer(), pos, (gen) -> { + return optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos); + }, (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM)) { ++ // Paper end return true; } else { + mealContext.precancelStructureEvent = true; // Paper level.setBlock(pos, state, Block.UPDATE_ALL); return false; } -@@ -114,7 +116,7 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock +@@ -114,7 +118,7 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock } @Override @@ -1656,7 +1600,7 @@ index db6b32016a1ad4264da9d8812e5ea9356d0601df..19000f09efadb6a2c616badfb837487a } diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java -index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..bde0874cf7528d4eb5eeeef76b840ab2d6ccabcd 100644 +index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..12d17a4cc61e35d199ba3f0d39435e8bfb1112d7 100644 --- a/net/minecraft/world/level/block/SaplingBlock.java +++ b/net/minecraft/world/level/block/SaplingBlock.java @@ -25,7 +25,6 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { @@ -1667,7 +1611,7 @@ index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..bde0874cf7528d4eb5eeeef76b840ab2 @Override public MapCodec codec() { -@@ -50,38 +49,18 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { +@@ -50,38 +49,19 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { } } @@ -1678,8 +1622,9 @@ index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..bde0874cf7528d4eb5eeeef76b840ab2 + mealContext.ogServerLevel = level; + this.advanceTree(level, pos, state, random, mealContext); + } ++ + public void advanceTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.BoneMealContext mealContext) { -+ // Paper end ++ // Paper end if (state.getValue(STAGE) == 0) { level.setBlock(pos, state.cycle(STAGE), Block.UPDATE_NONE); } else { @@ -1711,11 +1656,11 @@ index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..bde0874cf7528d4eb5eeeef76b840ab2 - } - } - // CraftBukkit end -+ this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random, mealContext); ++ this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random, mealContext); // Paper } } -@@ -96,8 +75,8 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { +@@ -96,8 +76,8 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { } @Override @@ -1855,7 +1800,7 @@ index f0514cd9df52b3a459ff92812ae5ad9b0df85763..53a8c2130037a1de75f8cc9b63f035f7 .ifPresent(blockPos -> level.setBlockAndUpdate(blockPos, Blocks.SHORT_DRY_GRASS.defaultBlockState())); } diff --git a/net/minecraft/world/level/block/TallFlowerBlock.java b/net/minecraft/world/level/block/TallFlowerBlock.java -index 4be72e53848b13eb68a2149749a88a83a4cb1e5c..365092462b7963f273297f3f9ae56291b1a6b056 100644 +index 4be72e53848b13eb68a2149749a88a83a4cb1e5c..6feb3bb080f51904bb119f2a69f9fe3ce511a582 100644 --- a/net/minecraft/world/level/block/TallFlowerBlock.java +++ b/net/minecraft/world/level/block/TallFlowerBlock.java @@ -33,7 +33,7 @@ public class TallFlowerBlock extends DoublePlantBlock implements BonemealableBlo @@ -1865,7 +1810,7 @@ index 4be72e53848b13eb68a2149749a88a83a4cb1e5c..365092462b7963f273297f3f9ae56291 - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { - popResource(level, pos, new ItemStack(this)); + public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper -+ level.addTask((serverLevel) -> popResource(serverLevel, pos, new ItemStack(this))); ++ level.addTask((serverLevel) -> popResource(serverLevel, pos, new ItemStack(this))); // Paper } } diff --git a/net/minecraft/world/level/block/TallGrassBlock.java b/net/minecraft/world/level/block/TallGrassBlock.java @@ -1882,23 +1827,15 @@ index 6c5259371fb8e2e131b2c69f354521b1e902ac4e..195bf95f843f459fab58382e5ceba207 } diff --git a/net/minecraft/world/level/block/TorchflowerCropBlock.java b/net/minecraft/world/level/block/TorchflowerCropBlock.java -index 18f8c389c33fcdc84a48f44cefab9c31b0a3e9ca..4358ec97e2ca9a7fdff4b6e84a1216ac87e77ebd 100644 +index 18f8c389c33fcdc84a48f44cefab9c31b0a3e9ca..4ebd44bb524836abe01e15b1915d565588591a36 100644 --- a/net/minecraft/world/level/block/TorchflowerCropBlock.java +++ b/net/minecraft/world/level/block/TorchflowerCropBlock.java -@@ -8,6 +8,7 @@ import net.minecraft.world.item.Items; - import net.minecraft.world.level.BlockGetter; - import net.minecraft.world.level.ItemLike; - import net.minecraft.world.level.Level; -+import net.minecraft.world.level.LevelAccessor; - import net.minecraft.world.level.block.state.BlockBehaviour; - import net.minecraft.world.level.block.state.BlockState; - import net.minecraft.world.level.block.state.StateDefinition; -@@ -70,7 +71,7 @@ public class TorchflowerCropBlock extends CropBlock { +@@ -70,7 +70,7 @@ public class TorchflowerCropBlock extends CropBlock { } @Override - protected int getBonemealAgeIncrease(Level level) { -+ protected int getBonemealAgeIncrease(LevelAccessor level) { // Paper ++ protected int getBonemealAgeIncrease(net.minecraft.world.level.LevelAccessor level) { // Paper return 1; } } @@ -1955,7 +1892,7 @@ index c57cb7b47a01e313a1be5de769c6766ac2787d95..f34731580bc880dcc5d3e96923a7f2b4 } } diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java -index 5471619a0484ece08640e2b3fd26746c351dc3e0..05c24bb6a12dfb8d9abdb71b1e8ade9032fc4349 100644 +index 5471619a0484ece08640e2b3fd26746c351dc3e0..166120e22e7caaae708e5e447a30496b772ed91f 100644 --- a/net/minecraft/world/level/block/grower/TreeGrower.java +++ b/net/minecraft/world/level/block/grower/TreeGrower.java @@ -122,7 +122,35 @@ public final class TreeGrower { @@ -1964,6 +1901,7 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..05c24bb6a12dfb8d9abdb71b1e8ade90 - public boolean growTree(ServerLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random) { + // Paper start ++ @Deprecated @io.papermc.paper.annotation.DoNotUse + public boolean growTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random) { + return this.growTree0(level, chunkGenerator, pos, state, random, null); + } @@ -1972,9 +1910,9 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..05c24bb6a12dfb8d9abdb71b1e8ade90 + try (io.papermc.paper.util.capture.SimpleBlockCapture capture = level.forkCaptureSession()) { + io.papermc.paper.util.capture.MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); + if (this.growTree0(captureTreeGeneration, chunkGenerator, pos, state, random, mealContext)) { -+ var states = captureTreeGeneration.calculateLatestBlockStates(mealContext.ogServerLevel); ++ Map snapshots = captureTreeGeneration.calculateLatestSnapshots(mealContext.ogServerLevel); + org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, mealContext.ogServerLevel); -+ java.util.List blocks = new java.util.ArrayList<>(states.values()); ++ java.util.List blocks = new java.util.ArrayList<>(snapshots.values()); + org.bukkit.event.world.StructureGrowEvent event = new org.bukkit.event.world.StructureGrowEvent(location, mealContext.treeHook, false, mealContext.getBukkitPlayer(), blocks); + + if (event.callEvent()) { @@ -1984,14 +1922,13 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..05c24bb6a12dfb8d9abdb71b1e8ade90 + mealContext.precancelStructureEvent = true; + } + } -+ + } + -+ + return false; + } ++ + private boolean growTree0(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.BoneMealContext mealContext) { -+ // Paper end ++ // Paper end ResourceKey> configuredMegaFeature = this.getConfiguredMegaFeature(random); if (configuredMegaFeature != null) { Holder> holder = level.registryAccess() @@ -2135,15 +2072,15 @@ index 3acc1374a7ef968d88e9f566ce7b812fb8d580af..4c9a8e3efc4621f0aff1f82965a9fa4a CompoundTag compoundTag = this.pendingBlockEntities.remove(pos); if (compoundTag != null) { diff --git a/net/minecraft/world/level/levelgen/feature/treedecorators/BeehiveDecorator.java b/net/minecraft/world/level/levelgen/feature/treedecorators/BeehiveDecorator.java -index aca28c507cb642ae10c70f8e8393db10c7bf6165..290b6f3f284e3fca03e0106d0f067e55fc73c2ca 100644 +index aca28c507cb642ae10c70f8e8393db10c7bf6165..cf5e7cbc7ccb4e749ee74168946dd9fa84aed6c6 100644 --- a/net/minecraft/world/level/levelgen/feature/treedecorators/BeehiveDecorator.java +++ b/net/minecraft/world/level/levelgen/feature/treedecorators/BeehiveDecorator.java -@@ -41,7 +41,7 @@ public class BeehiveDecorator extends TreeDecorator { - List list1 = context.logs(); - if (!list1.isEmpty()) { - RandomSource randomSource = context.random(); -- if (!(randomSource.nextFloat() >= this.probability)) { -+ if (!(randomSource.nextFloat() >= 1)) { - int i = !list.isEmpty() - ? Math.max(list.getFirst().getY() - 1, list1.getFirst().getY() + 1) - : Math.min(list1.getFirst().getY() + 1 + randomSource.nextInt(3), list1.getLast().getY()); +@@ -27,7 +27,7 @@ public class BeehiveDecorator extends TreeDecorator { + private final float probability; + + public BeehiveDecorator(float probability) { +- this.probability = probability; ++ this.probability = 1; // Paper - TODO revert + } + + @Override diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/BlockPlacementPredictor.java b/paper-server/src/main/java/io/papermc/paper/util/capture/BlockPlacementPredictor.java index 9106ca7c15a4..035df5f4f6dc 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/BlockPlacementPredictor.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/BlockPlacementPredictor.java @@ -1,10 +1,10 @@ package io.papermc.paper.util.capture; +import java.util.Optional; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; - -import java.util.Optional; +import org.jspecify.annotations.Nullable; public interface BlockPlacementPredictor { @@ -12,24 +12,23 @@ public interface BlockPlacementPredictor { Optional getLatestBlockAtIfLoaded(BlockPos pos); - Optional getLatestTileAt(BlockPos pos); - + Optional getLatestBlockEntityAt(BlockPos pos); - record BlockEntityPlacement(boolean removed, BlockEntity blockEntity) { + record BlockEntityPlacement(boolean removed, @Nullable BlockEntity blockEntity) { public static final Optional ABSENT = Optional.of(new BlockEntityPlacement(false, null)); - public BlockEntity res() { + public @Nullable BlockEntity res() { return this.removed ? null : this.blockEntity; } } - record LoadedBlockState(boolean present, BlockState state) { + record LoadedBlockState(boolean present, @Nullable BlockState state) { public static final Optional UNLOADED = Optional.of(new LoadedBlockState(false, null)); - public BlockState res() { - return this.present ? state : null; + public @Nullable BlockState res() { + return this.present ? this.state : null; } } } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java b/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java index 53e49ff42239..0776d3fa45f9 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java @@ -6,19 +6,14 @@ import org.bukkit.entity.Player; import org.jspecify.annotations.Nullable; -import java.util.concurrent.atomic.AtomicReference; - public class BoneMealContext { public ServerLevel ogServerLevel; - public ServerPlayer player; + public @Nullable ServerPlayer player; public boolean precancelStructureEvent = false; public TreeType treeHook; // just make this a field - - @Nullable - public Player getBukkitPlayer() { + public @Nullable Player getBukkitPlayer() { return this.player == null ? null : this.player.getBukkitEntity(); } - } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java index 0a5115bc7092..1517025d3675 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java @@ -1,26 +1,18 @@ package io.papermc.paper.util.capture; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import org.bukkit.Location; import org.bukkit.craftbukkit.block.CraftBlockStates; import org.bukkit.craftbukkit.util.CraftLocation; -import org.jetbrains.annotations.NotNull; import org.jspecify.annotations.Nullable; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import static net.minecraft.world.level.block.Block.UPDATE_NONE; - /* The premise of this storage is to essentially "mock" block placement in the game. This should attempt to mirror block placement the best it can, in order to provide a read solution of possibly what blocks @@ -33,18 +25,18 @@ public final class CaptureRecordMap { private final Map recordsByPos = new HashMap<>(); - public void setLatestBlockStateAt(final BlockPos pos, final BlockState state, final int flags) { + public void setLatestBlockStateAt(BlockPos pos, BlockState state, @Block.UpdateFlags int flags) { this.add(new CaptureRecord(state, pos, flags)); } - public void setLatestBlockEntityAt(final BlockPos pos, final boolean remove, @Nullable final BlockEntity add) { + public void setLatestBlockEntityAt(BlockPos pos, boolean remove, @Nullable BlockEntity add) { CaptureRecord oldRecord = this.recordsByPos.get(pos); if (oldRecord != null) { oldRecord.setBlockEntity(remove, add); } } - private void add(final CaptureRecord record) { + private void add(CaptureRecord record) { this.recordsByPos.put(record.pos, record); } @@ -52,7 +44,7 @@ public boolean isEmpty() { return this.recordsByPos.isEmpty(); } - public @Nullable BlockState getLatestBlockStateAt(final BlockPos pos) { + public @Nullable BlockState getLatestBlockStateAt(BlockPos pos) { CaptureRecord record = this.recordsByPos.get(pos); if (record == null) { return null; @@ -61,9 +53,9 @@ public boolean isEmpty() { return record.state; } - // Null indicates that its not present, no override + // Null indicates that it's not present, no override // Optional empty indicates its being removed - public @Nullable Optional<@Nullable BlockEntity> getLatestBlockEntityAt(final BlockPos pos) { + public @Nullable Optional getLatestBlockEntityAt(BlockPos pos) { CaptureRecord record = this.recordsByPos.get(pos); if (record == null) { return null; @@ -74,22 +66,22 @@ public boolean isEmpty() { public void applyBlockEntities(ServerLevel parent) { this.recordsByPos.keySet().forEach((pos) -> { - var res = getLatestBlockEntityAt(pos); + Optional res = this.getLatestBlockEntityAt(pos); if (res != null && res.isPresent()) { parent.setBlockEntity(res.get()); } }); } - public void applyApiPatch(final ServerLevel level) { + public void applyApiPatch(ServerLevel level) { this.recordsByPos.keySet().forEach((pos) -> { this.recordsByPos.get(pos).applyApiPatch(level); }); } // TODO: Clean this up - public Map calculateLatestBlockStates(ServerLevel level) { - final Map out = new HashMap<>(); + public Map calculateLatestSnapshots(ServerLevel level) { + Map out = new HashMap<>(); this.recordsByPos.keySet().forEach((pos) -> { @@ -100,7 +92,6 @@ public Map calculateLatestBlockStates(Ser return out; } - public static class CaptureRecord { private final BlockPos pos; @@ -108,7 +99,7 @@ public static class CaptureRecord { private BlockState state; private BlockEntity blockEntity; private boolean removeBe; - private int flags; + private @Block.UpdateFlags int flags; public CaptureRecord(BlockPos pos, BlockState state, BlockEntity blockEntity) { this.pos = pos; @@ -116,7 +107,7 @@ public CaptureRecord(BlockPos pos, BlockState state, BlockEntity blockEntity) { this.blockEntity = blockEntity; } - public CaptureRecord(BlockState state, BlockPos pos, int flags) { + public CaptureRecord(BlockState state, BlockPos pos, @Block.UpdateFlags int flags) { this.pos = pos; this.state = state; this.flags = flags; @@ -144,4 +135,4 @@ public void setBlockEntity(boolean remove, @Nullable BlockEntity add) { this.blockEntity = add; } } -} \ No newline at end of file +} diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/LayeredBlockPlacementPredictor.java b/paper-server/src/main/java/io/papermc/paper/util/capture/LayeredBlockPlacementPredictor.java index 332c0f5ec641..6beb6bc83408 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/LayeredBlockPlacementPredictor.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/LayeredBlockPlacementPredictor.java @@ -1,14 +1,13 @@ package io.papermc.paper.util.capture; +import java.util.Optional; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.state.BlockState; -import org.jspecify.annotations.Nullable; - -import java.util.Optional; public record LayeredBlockPlacementPredictor( - BlockPlacementPredictor... predictors + BlockPlacementPredictor... predictors ) implements BlockPlacementPredictor { + @Override public Optional getLatestBlockAt(BlockPos pos) { for (BlockPlacementPredictor predictor : this.predictors) { @@ -18,7 +17,6 @@ public Optional getLatestBlockAt(BlockPos pos) { } } - return Optional.empty(); } @@ -31,20 +29,18 @@ public Optional getLatestBlockAtIfLoaded(BlockPos pos) { } } - return Optional.empty(); } @Override - public Optional<@Nullable BlockEntityPlacement> getLatestTileAt(BlockPos pos) { + public Optional getLatestBlockEntityAt(BlockPos pos) { for (BlockPlacementPredictor predictor : this.predictors) { - Optional state = predictor.getLatestTileAt(pos); + Optional state = predictor.getLatestBlockEntityAt(pos); if (state.isPresent()) { return state; } } - return Optional.empty(); } } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java b/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java index 00e7b46fa2d4..600679921300 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java @@ -1,13 +1,15 @@ package io.papermc.paper.util.capture; +import java.util.Optional; +import java.util.function.Supplier; import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import org.jspecify.annotations.Nullable; -import java.util.Optional; -import java.util.function.Supplier; +public record LiveBlockPlacementLayer(WorldCapturer level, ServerLevel serverLevel) implements BlockPlacementPredictor { -public record LiveBlockPlacementLayer(WorldCapturer level, net.minecraft.server.level.ServerLevel serverLevel) implements BlockPlacementPredictor { @Override public Optional getLatestBlockAt(BlockPos pos) { return Optional.of(provideLive(() -> this.serverLevel.getBlockState(pos))); @@ -24,7 +26,7 @@ public Optional getLatestBlockAtIfLoaded(BlockPos pos) { } @Override - public Optional getLatestTileAt(BlockPos pos) { + public Optional getLatestBlockEntityAt(BlockPos pos) { BlockEntity blockEntity = provideLive(() -> this.serverLevel.getBlockEntity(pos)); if (blockEntity == null) { return BlockEntityPlacement.ABSENT; @@ -33,8 +35,7 @@ public Optional getLatestTileAt(BlockPos pos) { return Optional.of(new BlockEntityPlacement(false, blockEntity)); } - - public T provideLive(Supplier valueProvider) { + public @Nullable T provideLive(Supplier<@Nullable T> valueProvider) { SimpleBlockCapture blockCapture = this.level.getCapture(); this.level.releaseCapture(null); T value = valueProvider.get(); diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java index 1be14cb54641..dfc3d27a6da3 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java @@ -1,6 +1,14 @@ package io.papermc.paper.util.capture; import io.papermc.paper.configuration.WorldConfiguration; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Holder; @@ -40,27 +48,15 @@ import net.minecraft.world.ticks.LevelTickAccess; import net.minecraft.world.ticks.ScheduledTick; import org.bukkit.Location; -import org.jetbrains.annotations.NotNull; import org.jspecify.annotations.Nullable; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.BooleanSupplier; -import java.util.function.Consumer; -import java.util.function.Predicate; - public class MinecraftCaptureBridge implements PaperCapturingWorldLevel { private final ServerLevel parent; private final List queuedTasks = new ArrayList<>(); + // Effective represents plugin set blocks -> predicted blocks -> actual server level private final BlockPlacementPredictor effectiveReadLayer; - - private SimpleBlockPlacementPredictor writeLayer; // This is the layer that is written to in the server level potentially. @@ -70,7 +66,7 @@ public class MinecraftCaptureBridge implements PaperCapturingWorldLevel { private final CapturingTickAccess blocks; private final CapturingTickAccess liquids; - private Consumer sink = queuedTasks::add; + private Consumer sink = this.queuedTasks::add; public MinecraftCaptureBridge(ServerLevel parent, BlockPlacementPredictor baseReadLayer) { this.parent = parent; @@ -126,8 +122,8 @@ public ServerChunkCache getChunkSource() { } @Override - public boolean setBlockAndUpdate(BlockPos blockPos, BlockState blockState) { - return this.setBlock(blockPos, blockState, Block.UPDATE_ALL); + public boolean setBlockAndUpdate(BlockPos pos, BlockState state) { + return this.setBlock(pos, state, Block.UPDATE_ALL); } @Override @@ -154,8 +150,8 @@ public Optional getLatestBlockAtIfLoaded(BlockPos pos) { } @Override - public Optional getLatestTileAt(BlockPos pos) { - return MinecraftCaptureBridge.this.effectiveReadLayer.getLatestTileAt(pos); + public Optional getLatestBlockEntityAt(BlockPos pos) { + return MinecraftCaptureBridge.this.effectiveReadLayer.getLatestBlockEntityAt(pos); } }); @@ -169,17 +165,17 @@ public RandomSource getRandom() { @Override public void playSound(@Nullable Entity entity, BlockPos pos, SoundEvent sound, SoundSource source, float volume, float pitch) { - this.addTask((serverLevel) -> serverLevel.playSound(entity, pos, sound, source, volume, pitch)); + this.addTask((level) -> level.playSound(entity, pos, sound, source, volume, pitch)); } @Override public void addParticle(ParticleOptions options, double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) { - this.addTask((serverLevel) -> serverLevel.addParticle(options, x, y, z, xSpeed, ySpeed, zSpeed)); + this.addTask((level) -> level.addParticle(options, x, y, z, xSpeed, ySpeed, zSpeed)); } @Override public void levelEvent(@Nullable Entity entity, int type, BlockPos pos, int data) { - this.addTask((serverLevel) -> serverLevel.levelEvent(entity, type, pos, data)); + this.addTask((level) -> level.levelEvent(entity, type, pos, data)); } @Override @@ -204,7 +200,7 @@ public WorldBorder getWorldBorder() { @Override public @Nullable BlockEntity getBlockEntity(BlockPos pos) { - return this.effectiveReadLayer.getLatestTileAt(pos) + return this.effectiveReadLayer.getLatestBlockEntityAt(pos) .map(BlockPlacementPredictor.BlockEntityPlacement::blockEntity) .orElse(null); } @@ -314,14 +310,14 @@ public boolean isFluidAtPosition(BlockPos pos, Predicate predicate) return predicate.test(this.getFluidState(pos)); } - public boolean silentSet(BlockPos pos, BlockState state, int flags) { + public boolean silentSet(BlockPos pos, BlockState state, @Block.UpdateFlags int flags) { return this.writeLayer.setBlockState(this.effectiveReadLayer, pos, state, flags); } @Override - public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft) { + public boolean setBlock(BlockPos pos, BlockState state, @Block.UpdateFlags int flags, int recursionLeft) { BlockPos copy = pos.immutable(); - this.addTask((serverLevel) -> parent.setBlock(copy, state, flags, recursionLeft)); + this.addTask((level) -> parent.setBlock(copy, state, flags, recursionLeft)); return this.writeLayer.setBlockState(this.effectiveReadLayer, copy, state, flags); } @@ -329,7 +325,7 @@ public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursion @Override public boolean removeBlock(BlockPos pos, boolean movedByPiston) { BlockPos copy = pos.immutable(); - this.addTask((serverLevel) -> serverLevel.removeBlock(copy, movedByPiston)); + this.addTask((level) -> level.removeBlock(copy, movedByPiston)); FluidState fluidState = this.getFluidState(copy); return this.silentSet(copy, fluidState.createLegacyBlock(), Block.UPDATE_ALL | (movedByPiston ? Block.UPDATE_MOVE_BY_PISTON : 0)); @@ -338,7 +334,7 @@ public boolean removeBlock(BlockPos pos, boolean movedByPiston) { @Override public boolean destroyBlock(BlockPos pos, boolean dropBlock, @Nullable Entity entity, int recursionLeft) { BlockPos copy = pos.immutable(); - this.addTask((serverLevel) -> serverLevel.destroyBlock(copy, dropBlock, entity, recursionLeft)); + this.addTask((level) -> level.destroyBlock(copy, dropBlock, entity, recursionLeft)); BlockState blockState = this.getBlockState(copy); if (blockState.isAir()) { @@ -351,9 +347,9 @@ public boolean destroyBlock(BlockPos pos, boolean dropBlock, @Nullable Entity en } @Override - public void sendBlockUpdated(BlockPos pos, BlockState state, BlockState blockState1, int updateClients) { + public void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, @Block.UpdateFlags int flags) { BlockPos copy = pos.immutable(); - this.addTask((serverLevel) -> serverLevel.sendBlockUpdated(copy, state, blockState1, updateClients)); + this.addTask((level) -> level.sendBlockUpdated(copy, oldState, newState, flags)); } @Override @@ -362,7 +358,7 @@ public void setBlockEntity(BlockEntity blockEntity) { } @Override - public boolean setBlockSilent(BlockPos pos, BlockState state, int flags, int recursionLeft) { + public boolean setBlockSilent(BlockPos pos, BlockState state, @Block.UpdateFlags int flags, int recursionLeft) { BlockPos copy = pos.immutable(); return this.silentSet(copy, state, flags); } @@ -383,16 +379,16 @@ public GameRules getGameRules() { } @Override - public void addTask(Consumer levelConsumer) { - this.sink.accept(() -> levelConsumer.accept(parent)); + public void addTask(Consumer level) { + this.sink.accept(() -> level.accept(this.parent)); } - public net.minecraft.world.level.block.state.BlockState getLatestBlockState(final BlockPos pos) { + public net.minecraft.world.level.block.state.@Nullable BlockState getLatestBlockState(BlockPos pos) { return this.effectiveReadLayer.getLatestBlockAt(pos).orElse(null); } - public @Nullable Optional<@Nullable BlockEntity> getLatestBlockEntity(final BlockPos pos) { - Optional placement = this.effectiveReadLayer.getLatestTileAt(pos); + public @Nullable Optional getLatestBlockEntity(BlockPos pos) { + Optional placement = this.effectiveReadLayer.getLatestBlockEntityAt(pos); if (placement.isEmpty() || placement.get().blockEntity() == null && !placement.get().removed()) { return null; } @@ -400,8 +396,8 @@ public net.minecraft.world.level.block.state.BlockState getLatestBlockState(fina return placement.map(BlockPlacementPredictor.BlockEntityPlacement::blockEntity); } - public Map calculateLatestBlockStates(ServerLevel level) { - return this.writeLayer.getRecordMap().calculateLatestBlockStates(level); + public Map calculateLatestSnapshots(ServerLevel level) { + return this.writeLayer.getRecordMap().calculateLatestSnapshots(level); } public void applyTasks() { @@ -412,7 +408,7 @@ public void applyTasks() { this.blocks.apply(); this.liquids.apply(); - // If we have changes that the plugin applied ontop of the already existing changes, we know that we can apply them. + // If we have changes that the plugin applied on top of the already existing changes, we know that we can apply them. // So, do that! if (!this.serverLevelOverlayLayer.isEmpty()) { this.serverLevelOverlayLayer.getRecordMap().applyApiPatch(this.parent); @@ -431,29 +427,29 @@ public void allowWriteOnLevel() { this.writeLayer = this.serverLevelOverlayLayer; } - public static class CapturingTickAccess implements LevelTickAccess<@NotNull T> { + public static class CapturingTickAccess implements LevelTickAccess { - private final LevelTickAccess<@NotNull T> wrapped; + private final LevelTickAccess wrapped; private final Set scheduled = new HashSet<>(); - private final List> ticks = new ArrayList<>(); + private final List> ticks = new ArrayList<>(); - public CapturingTickAccess(LevelTickAccess<@NotNull T> wrapped) { + public CapturingTickAccess(LevelTickAccess wrapped) { this.wrapped = wrapped; } @Override - public boolean willTickThisTick(@NotNull BlockPos pos, T type) { + public boolean willTickThisTick(BlockPos pos, T type) { return this.wrapped.willTickThisTick(pos, type); } @Override - public void schedule(ScheduledTick<@NotNull T> tick) { + public void schedule(ScheduledTick tick) { this.scheduled.add(tick.pos()); this.ticks.add(tick); } @Override - public boolean hasScheduledTick(@NotNull BlockPos pos, T type) { + public boolean hasScheduledTick(BlockPos pos, T type) { return this.wrapped.hasScheduledTick(pos, type) || this.scheduled.contains(pos); } @@ -466,5 +462,4 @@ public void apply() { this.ticks.forEach(this.wrapped::schedule); } } - } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java b/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java index d9b1ecb48cdb..a2ca65367a63 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java @@ -1,9 +1,12 @@ package io.papermc.paper.util.capture; import io.papermc.paper.configuration.WorldConfiguration; +import java.util.function.Consumer; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkGenerator; @@ -13,17 +16,17 @@ public interface PaperCapturingWorldLevel extends WorldGenLevel { GameRules getGameRules(); - void addTask(java.util.function.Consumer levelConsumer); + void addTask(Consumer level); - void sendBlockUpdated(BlockPos pos, BlockState state, BlockState blockState1, int updateClients); + void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, @Block.UpdateFlags int flags); void setBlockEntity(BlockEntity blockEntity); - boolean setBlockSilent(BlockPos pos, BlockState state, int flags, int recursionLeft); + boolean setBlockSilent(BlockPos pos, BlockState state, @Block.UpdateFlags int flags, int recursionLeft); ServerChunkCache getChunkSource(); - boolean setBlockAndUpdate(BlockPos blockPos, BlockState blockState); + boolean setBlockAndUpdate(BlockPos pos, BlockState state); WorldConfiguration paperConfig(); @@ -31,4 +34,3 @@ public interface PaperCapturingWorldLevel extends WorldGenLevel { SimpleBlockCapture forkCaptureSession(); } - diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java b/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java index a6b9a8560665..5c1f86885256 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java @@ -1,15 +1,15 @@ package io.papermc.paper.util.capture; import io.papermc.paper.configuration.WorldConfiguration; +import java.util.function.Consumer; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.gamerules.GameRules; -import java.util.function.Consumer; - public interface ServerLevelPaperCapturingWorldLevel extends PaperCapturingWorldLevel { ServerLevel handle(); @@ -20,13 +20,13 @@ default GameRules getGameRules() { } @Override - default void addTask(Consumer levelConsumer) { - levelConsumer.accept(this.handle()); + default void addTask(Consumer level) { + level.accept(this.handle()); } @Override - default void sendBlockUpdated(BlockPos pos, BlockState state, BlockState blockState1, int updateClients) { - this.handle().sendBlockUpdated(pos, state, blockState1, updateClients); + default void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, @Block.UpdateFlags int flags) { + this.handle().sendBlockUpdated(pos, oldState, newState, flags); } @Override @@ -35,13 +35,13 @@ default void setBlockEntity(BlockEntity blockEntity) { } @Override - default boolean setBlockSilent(BlockPos pos, BlockState state, int flags, int recursionLeft) { + default boolean setBlockSilent(BlockPos pos, BlockState state, @Block.UpdateFlags int flags, int recursionLeft) { return this.handle().setBlock(pos, state, flags, recursionLeft); } @Override - default boolean setBlockAndUpdate(BlockPos blockPos, BlockState blockState) { - return this.handle().setBlockAndUpdate(blockPos, blockState); + default boolean setBlockAndUpdate(BlockPos pos, BlockState state) { + return this.handle().setBlockAndUpdate(pos, state); } @Override diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java index 165d2f1ca72e..cb9482af544e 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java @@ -1,5 +1,7 @@ package io.papermc.paper.util.capture; +import java.util.Map; +import java.util.Optional; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.entity.BlockEntity; @@ -7,9 +9,6 @@ import org.bukkit.block.BlockState; import org.jspecify.annotations.Nullable; -import java.util.Map; -import java.util.Optional; - public class SimpleBlockCapture implements AutoCloseable { private final MinecraftCaptureBridge capturingWorldLevel; @@ -17,13 +16,11 @@ public class SimpleBlockCapture implements AutoCloseable { private boolean isOverlayingCaptureOnLevel = false; - private final SimpleBlockCapture oldCapture; - - public SimpleBlockCapture(BlockPlacementPredictor base, ServerLevel world, SimpleBlockCapture oldCapture) { - this.capturingWorldLevel = new MinecraftCaptureBridge(world, base); - this.level = world; + public SimpleBlockCapture(BlockPlacementPredictor base, ServerLevel level, SimpleBlockCapture oldCapture) { + this.capturingWorldLevel = new MinecraftCaptureBridge(level, base); + this.level = level; this.oldCapture = oldCapture; } @@ -35,15 +32,15 @@ public boolean isCapturing() { return true; } - public Map getCapturedBlockStates() { - return this.capturingWorldLevel.calculateLatestBlockStates(this.level); + public Map getCapturedSnapshots() { + return this.capturingWorldLevel.calculateLatestSnapshots(this.level); } - public net.minecraft.world.level.block.state.BlockState getOverlayBlockState(final BlockPos pos) { + public net.minecraft.world.level.block.state.@Nullable BlockState getOverlayBlockState(BlockPos pos) { return this.capturingWorldLevel.getLatestBlockState(pos); } - public @Nullable Optional<@Nullable BlockEntity> getOverlayBlockEntity(final BlockPos pos) { + public @Nullable Optional getOverlayBlockEntity(BlockPos pos) { return this.capturingWorldLevel.getLatestBlockEntity(pos); } @@ -68,7 +65,7 @@ public void close() { this.level.capturer.releaseCapture(this.oldCapture); } - public net.minecraft.world.level.block.state.BlockState getCaptureBlockStateIfLoaded(BlockPos pos) { + public net.minecraft.world.level.block.state.@Nullable BlockState getCaptureBlockStateIfLoaded(BlockPos pos) { return this.capturingWorldLevel.getBlockStateIfLoaded(pos); } } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java index 3f0009aa4b0c..7b1f59171ce5 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java @@ -1,5 +1,6 @@ package io.papermc.paper.util.capture; +import java.util.Optional; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; @@ -7,14 +8,12 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; -import java.util.Optional; - // Block state class SimpleBlockPlacementPredictor implements BlockPlacementPredictor { private final CaptureRecordMap guesstimationMap = new CaptureRecordMap(); - public boolean setBlockState(BlockPlacementPredictor layer, BlockPos pos, BlockState state, int flags) { + public boolean setBlockState(BlockPlacementPredictor layer, BlockPos pos, BlockState state, @Block.UpdateFlags int flags) { BlockState blockState = layer.getLatestBlockAt(pos).orElse(Blocks.AIR.defaultBlockState()); // Dont do any processing if the same if (blockState == state) { @@ -40,13 +39,13 @@ public boolean setBlockState(BlockPlacementPredictor layer, BlockPos pos, BlockS // if ((differentState || block instanceof BaseRailBlock) && ((flags & Block.UPDATE_NEIGHBORS) != 0 || updateMoveByPiston)) { // BlockState finalBlockState = blockState; -// this.capturingWorldLevel.addTask((serverLevel) -> { -// finalBlockState.affectNeighborsAfterRemoval(serverLevel, pos, updateMoveByPiston); +// this.capturingWorldLevel.addTask((level) -> { +// finalBlockState.affectNeighborsAfterRemoval(level, pos, updateMoveByPiston); // }); // } if (state.hasBlockEntity()) { - BlockEntity blockEntity = this.getLatestTileAt(pos).map(BlockEntityPlacement::blockEntity).orElse(null); + BlockEntity blockEntity = this.getLatestBlockEntityAt(pos).map(BlockEntityPlacement::blockEntity).orElse(null); if (blockEntity != null && !blockEntity.isValidBlockState(state)) { blockEntity = null; } @@ -80,8 +79,8 @@ public boolean isEmpty() { } @Override - public Optional getLatestTileAt(BlockPos pos) { - var value = this.guesstimationMap.getLatestBlockEntityAt(pos); + public Optional getLatestBlockEntityAt(BlockPos pos) { + Optional value = this.guesstimationMap.getLatestBlockEntityAt(pos); if (value == null) { return Optional.empty(); } else { @@ -102,13 +101,11 @@ public Optional getLatestBlockAtIfLoaded(BlockPos pos) { .map((state) -> new LoadedBlockState(true, state)); } - - public void setLatestBlockAt(BlockPos pos, BlockState data, int flags) { - this.guesstimationMap.setLatestBlockStateAt(pos, data, flags); + public void setLatestBlockAt(BlockPos pos, BlockState state, @Block.UpdateFlags int flags) { + this.guesstimationMap.setLatestBlockStateAt(pos, state, flags); } public CaptureRecordMap getRecordMap() { - return guesstimationMap; + return this.guesstimationMap; } - -} \ No newline at end of file +} diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java b/paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java index 61a02d041575..f9e182771fe3 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/WorldCapturer.java @@ -2,37 +2,37 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; +import org.jspecify.annotations.Nullable; // TODO: Cleanup this state, because its held on the server world. I had proper state handling but threw it away at some point public class WorldCapturer { - public final ServerLevel world; + public final ServerLevel level; - private SimpleBlockCapture capture; + private @Nullable SimpleBlockCapture capture; - public WorldCapturer(Level world) { - this.world = (ServerLevel) world; + public WorldCapturer(Level level) { + this.level = (ServerLevel) level; } public SimpleBlockCapture createCaptureSession(BlockPlacementPredictor blockPlacementPredictor) { - this.capture = new SimpleBlockCapture(blockPlacementPredictor, world, this.capture); - + this.capture = new SimpleBlockCapture(blockPlacementPredictor, this.level, this.capture); return this.capture; } public SimpleBlockCapture createCaptureSession() { - return this.createCaptureSession(new LiveBlockPlacementLayer(this, world)); + return this.createCaptureSession(new LiveBlockPlacementLayer(this, this.level)); } - public void releaseCapture(SimpleBlockCapture oldCapture) { + public void releaseCapture(@Nullable SimpleBlockCapture oldCapture) { this.capture = oldCapture; } - public SimpleBlockCapture getCapture() { - return capture; + public @Nullable SimpleBlockCapture getCapture() { + return this.capture; } public boolean isCapturing() { return this.capture != null; } -} \ No newline at end of file +} diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/package-info.java b/paper-server/src/main/java/io/papermc/paper/util/capture/package-info.java new file mode 100644 index 000000000000..a35a6fb4f7ce --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package io.papermc.paper.util.capture; + +import org.jspecify.annotations.NullMarked; diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index fd546f16d2e4..c2e0ec4d6e2e 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -106,7 +106,6 @@ import org.bukkit.boss.DragonBattle; import org.bukkit.craftbukkit.block.CraftBiome; import org.bukkit.craftbukkit.block.CraftBlock; -import org.bukkit.craftbukkit.block.CraftBlockState; import org.bukkit.craftbukkit.block.CraftBlockType; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.craftbukkit.boss.CraftDragonBattle; @@ -142,7 +141,6 @@ import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; import org.bukkit.event.weather.LightningStrikeEvent; import org.bukkit.event.world.SpawnChangeEvent; -import org.bukkit.event.world.StructureGrowEvent; import org.bukkit.event.world.TimeSkipEvent; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.BlockPopulator; @@ -748,9 +746,9 @@ public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate del BlockPos pos = CraftLocation.toBlockPosition(loc); boolean res = this.generateTree(captureTreeGeneration, this.getHandle().getMinecraftWorld().getChunkSource().getGenerator(), pos, new RandomSourceWrapper(CraftWorld.rand), type); if (res) { - var states = captureTreeGeneration.calculateLatestBlockStates(this.world); + Map snapshots = captureTreeGeneration.calculateLatestSnapshots(this.world); - java.util.List blocks = new java.util.ArrayList<>(states.values()); + List blocks = new ArrayList<>(snapshots.values()); for (BlockState state : blocks) { delegate.setBlockData(state.getX(), state.getY(), state.getZ(), state.getBlockData()); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 098a2644b302..5046ce461db4 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -6,17 +6,6 @@ import com.google.common.collect.Lists; import com.mojang.authlib.GameProfile; import com.mojang.datafixers.util.Either; -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.BooleanSupplier; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import java.util.stream.Stream; import io.papermc.paper.adventure.PaperAdventure; import io.papermc.paper.block.bed.BedEnterProblem; import io.papermc.paper.connection.HorriblePlayerLoginEventHack; @@ -28,6 +17,15 @@ import io.papermc.paper.util.capture.MinecraftCaptureBridge; import io.papermc.paper.util.capture.PaperCapturingWorldLevel; import io.papermc.paper.util.capture.SimpleBlockCapture; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.network.Connection; @@ -50,14 +48,9 @@ import net.minecraft.world.entity.Leashable; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.PathfinderMob; -import net.minecraft.world.entity.animal.fish.AbstractFish; -import net.minecraft.world.entity.animal.golem.AbstractGolem; import net.minecraft.world.entity.animal.Animal; -import net.minecraft.world.entity.animal.fish.WaterAnimal; +import net.minecraft.world.entity.animal.fish.AbstractFish; import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.entity.monster.Ghast; -import net.minecraft.world.entity.monster.Monster; -import net.minecraft.world.entity.monster.Slime; import net.minecraft.world.entity.monster.illager.SpellcasterIllager; import net.minecraft.world.entity.projectile.FireworkRocketEntity; import net.minecraft.world.entity.raid.Raid; @@ -77,7 +70,6 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.SignBlockEntity; import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; -import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.gamerules.GameRule; import net.minecraft.world.level.redstone.Redstone; import net.minecraft.world.level.storage.loot.LootContext; @@ -121,6 +113,7 @@ import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.craftbukkit.inventory.CraftItemType; import org.bukkit.craftbukkit.potion.CraftPotionUtil; +import org.bukkit.craftbukkit.util.CraftLocation; import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.bukkit.craftbukkit.util.CraftVector; import org.bukkit.entity.AbstractHorse; @@ -1301,8 +1294,8 @@ public static PlayerExpChangeEvent callPlayerExpChangeEvent(net.minecraft.world. return event; } - public static boolean handleBlockGrowEvent(LevelAccessor world, BlockPos pos, net.minecraft.world.level.block.state.BlockState state, @net.minecraft.world.level.block.Block.UpdateFlags int flags) { - CraftBlockState snapshot = CraftBlockStates.getBlockState(world, pos); + public static boolean handleBlockGrowEvent(LevelAccessor level, BlockPos pos, net.minecraft.world.level.block.state.BlockState state, @net.minecraft.world.level.block.Block.UpdateFlags int flags) { + CraftBlockState snapshot = CraftBlockStates.getBlockState(level, pos); snapshot.setData(state); BlockGrowEvent event = new BlockGrowEvent(snapshot.getBlock(), snapshot); @@ -2394,14 +2387,14 @@ public static boolean sendChestLockedNotifications(Vec3 pos) { return false; } - public static boolean structureEvent(ServerLevel serverLevel, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, Player player, BlockPos pos, Function worldgenCapture, TreeType type) { + public static boolean structureEvent(ServerLevel serverLevel, PaperCapturingWorldLevel level, Player player, BlockPos pos, Function worldGenCapture, TreeType type) { try (SimpleBlockCapture capture = level.forkCaptureSession()) { MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); - if (worldgenCapture.apply(captureTreeGeneration)) { - var states = captureTreeGeneration.calculateLatestBlockStates(serverLevel); - org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, serverLevel); + if (worldGenCapture.apply(captureTreeGeneration)) { + Map snapshots = captureTreeGeneration.calculateLatestSnapshots(serverLevel); + Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, serverLevel); - java.util.List blocks = new java.util.ArrayList<>(states.values()); + List blocks = new ArrayList<>(snapshots.values()); StructureGrowEvent structureEvent = new StructureGrowEvent(location, type, false, player, blocks); if (structureEvent.callEvent()) { @@ -2411,20 +2404,18 @@ public static boolean structureEvent(ServerLevel serverLevel, io.papermc.paper.u } } - - return false; } - public static boolean fertilizeBlock(ServerLevel level, Player player, BlockPos pos, Consumer worldgenCapture, boolean cancelled) { + public static boolean fertilizeBlock(ServerLevel level, Player player, BlockPos pos, Consumer worldGenCapture, boolean cancelled) { try (SimpleBlockCapture capture = level.forkCaptureSession()) { MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); - worldgenCapture.accept(captureTreeGeneration); - var states = captureTreeGeneration.calculateLatestBlockStates(level); - org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level); + worldGenCapture.accept(captureTreeGeneration); + Map snapshots = captureTreeGeneration.calculateLatestSnapshots(level); + Location location = CraftLocation.toBukkit(pos, level); - java.util.List blocks = new java.util.ArrayList<>(states.values()); + List blocks = new ArrayList<>(snapshots.values()); BlockFertilizeEvent structureEvent = new BlockFertilizeEvent(location.getBlock(), player, blocks); structureEvent.setCancelled(cancelled); diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java index 7ea6124b9e8e..fb067a9af683 100644 --- a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java +++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java @@ -19,9 +19,8 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitRunnable; -import org.jetbrains.annotations.NotNull; -import java.util.function.Consumer; +import static net.kyori.adventure.text.Component.text; public final class TestPlugin extends JavaPlugin implements Listener { @@ -33,24 +32,23 @@ public void onEnable() { } @EventHandler - public void blockPlace(BlockFertilizeEvent event) { + public void on(BlockFertilizeEvent event) { //event.setCancelled(true); -// Bukkit.getPlayer("Owen1212055").sendBlockChanges(event.getBlocks()); +// event.getPlayer().sendBlockChanges(event.getBlocks()); // // new BukkitRunnable(){ // // @Override // public void run() { -// event.getBlocks().forEach((state) -> Bukkit.getPlayer("Owen1212055").sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData())); +// event.getBlocks().forEach((state) -> event.getPlayer().sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData())); // } // }.runTaskLater(this, 20 * 2); - } @EventHandler - public void blockPlace(StructureGrowEvent event) { + public void on(StructureGrowEvent event) { // event.setCancelled(true); // // event.getPlayer().sendBlockChanges(event.getBlocks()); @@ -66,15 +64,15 @@ public void blockPlace(StructureGrowEvent event) { @EventHandler - public void blockPlace(PlayerSwapHandItemsEvent event) { + public void on(PlayerSwapHandItemsEvent event) { event.getPlayer().getTargetBlockExact(5).applyBoneMeal(BlockFace.UP); event.getPlayer().getWorld().generateTree(event.getPlayer().getLocation(), TreeType.values()[(int) (TreeType.values().length * Math.random())]); } @EventHandler - public void blockPlace(BlockPlaceEvent event) { - event.getPlayer().sendActionBar("Replaced: " + event.getBlockReplacedState().getType()); + public void on(BlockPlaceEvent event) { + event.getPlayer().sendActionBar(text("Replaced: " + event.getBlockReplacedState().getType())); if (event.getPlayer().getInventory().contains(Material.DIAMOND)) { event.getBlock().setType(Material.AIR); } else if (event.getPlayer().getInventory().contains(Material.GOLD_INGOT)) { @@ -91,14 +89,11 @@ public void blockPlace(BlockPlaceEvent event) { @EventHandler public void blockPlace(BlockMultiPlaceEvent event) { - event.getPlayer().sendActionBar("Replaced: " + String.join(",", event.getReplacedBlockStates().stream().map(BlockState::getType).map(Material::toString).toList())); + event.getPlayer().sendActionBar(text("Replaced: " + event.getReplacedBlockStates().stream().map(BlockState::getBlockData).toList())); event.getPlayer().sendMessage(event.getBlock().getType().toString()); if (event.getPlayer().getInventory().contains(Material.DIAMOND)) { event.setCancelled(true); } } - - - } From bcf5a3fffe6112ea3dca73e99aa8812229a7f216 Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Wed, 11 Feb 2026 22:20:10 +0100 Subject: [PATCH 12/17] allow to bonemeal the ocean floor --- .../features/0032-Block-Capture-System.patch | 24 +++++-------------- .../paper/util/capture/CaptureRecordMap.java | 12 ++++------ .../util/capture/MinecraftCaptureBridge.java | 5 ++-- 3 files changed, 13 insertions(+), 28 deletions(-) diff --git a/paper-server/patches/features/0032-Block-Capture-System.patch b/paper-server/patches/features/0032-Block-Capture-System.patch index cd0706608fe3..16014597d5d0 100644 --- a/paper-server/patches/features/0032-Block-Capture-System.patch +++ b/paper-server/patches/features/0032-Block-Capture-System.patch @@ -3,21 +3,6 @@ From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:31:13 -0500 Subject: [PATCH] Block Capture System -ATM Fixes: -this one: https://github.com/PaperMC/Paper/issues/11728 -https://github.com/PaperMC/Paper/issues/6458 (better fix) -bee hive issue -https://github.com/PaperMC/Paper/issues/12114 (tripwire hook duping) -https://github.com/PaperMC/Paper/issues/13586 (working towards) -end portal counting towards block place event -any block modification in item interaction counting as block place event -fixes edge cases required by random block types -https://github.com/PaperMC/Paper/issues/13536 root dupe -fixes edge cases with physics being fired on structure placement despite being canceled - -big todos: -- Dispenser related stuff -- Bone meal related stuff, we need to pass more context in order to get the tree event properly working diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java index bfefb5031544caa59230f0073e8880c2b39ebf4d..40810b410c92f8fd2edaec7443450c403171ee11 100644 @@ -330,7 +315,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..3707f24b93a24f2d6622d86e05626707 return false; } else { diff --git a/net/minecraft/world/item/BoneMealItem.java b/net/minecraft/world/item/BoneMealItem.java -index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..664a6a6a8a44233d7c2d13f432789fe6031dd746 100644 +index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..d4b0defd9236003ee739fe8ceee37b0a913fd340 100644 --- a/net/minecraft/world/item/BoneMealItem.java +++ b/net/minecraft/world/item/BoneMealItem.java @@ -44,7 +44,12 @@ public class BoneMealItem extends Item { @@ -388,15 +373,18 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..664a6a6a8a44233d7c2d13f432789fe6 } stack.shrink(1); -@@ -84,7 +106,7 @@ public class BoneMealItem extends Item { +@@ -84,9 +106,9 @@ public class BoneMealItem extends Item { } } - public static boolean growWaterPlant(ItemStack stack, Level level, BlockPos pos, @Nullable Direction clickedSide) { + public static boolean growWaterPlant(ItemStack stack, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, @Nullable Direction clickedSide, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper if (level.getBlockState(pos).is(Blocks.WATER) && level.getFluidState(pos).getAmount() == 8) { - if (!(level instanceof ServerLevel)) { +- if (!(level instanceof ServerLevel)) { ++ if (false) { // Paper return true; + } else { + RandomSource random = level.getRandom(); @@ -107,7 +129,7 @@ public class BoneMealItem extends Item { if (biome.is(BiomeTags.PRODUCES_CORALS_FROM_BONEMEAL)) { if (i == 0 && clickedSide != null && clickedSide.getAxis().isHorizontal()) { diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java index 1517025d3675..745efb402164 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java @@ -83,12 +83,10 @@ public void applyApiPatch(ServerLevel level) { public Map calculateLatestSnapshots(ServerLevel level) { Map out = new HashMap<>(); - this.recordsByPos.keySet().forEach((pos) -> { - - CaptureRecord captureRecord = this.recordsByPos.get(pos); - out.put(CraftLocation.toBukkit(pos), CraftBlockStates.getBlockState(level.getWorld(), CraftLocation.toBlockPosition(CraftLocation.toBukkit(pos)), captureRecord.state, captureRecord.blockEntity)); - }); - + for (Map.Entry entry : this.recordsByPos.entrySet()) { + CaptureRecord captureRecord = entry.getValue(); + out.put(CraftLocation.toBukkit(entry.getKey()), CraftBlockStates.getBlockState(level.getWorld(), entry.getKey(), captureRecord.state, captureRecord.blockEntity)); + } return out; } @@ -97,7 +95,7 @@ public static class CaptureRecord { private final BlockPos pos; private BlockState state; - private BlockEntity blockEntity; + private @Nullable BlockEntity blockEntity; private boolean removeBe; private @Block.UpdateFlags int flags; diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java index dfc3d27a6da3..3f20f79ee5da 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java @@ -152,7 +152,6 @@ public Optional getLatestBlockAtIfLoaded(BlockPos pos) { @Override public Optional getLatestBlockEntityAt(BlockPos pos) { return MinecraftCaptureBridge.this.effectiveReadLayer.getLatestBlockEntityAt(pos); - } }); } @@ -317,7 +316,7 @@ public boolean silentSet(BlockPos pos, BlockState state, @Block.UpdateFlags int @Override public boolean setBlock(BlockPos pos, BlockState state, @Block.UpdateFlags int flags, int recursionLeft) { BlockPos copy = pos.immutable(); - this.addTask((level) -> parent.setBlock(copy, state, flags, recursionLeft)); + this.addTask((level) -> level.setBlock(copy, state, flags, recursionLeft)); return this.writeLayer.setBlockState(this.effectiveReadLayer, copy, state, flags); } @@ -416,7 +415,7 @@ public void applyTasks() { // Apply block entities, those may have been written in our estimation. So just apply them. - // I dont really like this, as I in general dont really want to apply things from the prediction layer. + // I don't really like this, as I in general don't really want to apply things from the prediction layer. // But, these may be mutated by anything. if (!this.writeLayer.getRecordMap().isEmpty()) { this.writeLayer.getRecordMap().applyBlockEntities(this.parent); From 298a8bfd32006fba1576777466ceef0d0e8e043e Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Thu, 12 Feb 2026 21:04:26 +0100 Subject: [PATCH 13/17] remove CapturedBlockState and capturedFlags field --- .../bukkit/event/block/BlockIgniteEvent.java | 3 +- .../features/0032-Block-Capture-System.patch | 14 ++++ .../craftbukkit/block/CapturedBlockState.java | 80 ------------------- .../craftbukkit/block/CraftBlockState.java | 14 ---- .../util/BlockStateListPopulator.java | 25 +++--- 5 files changed, 30 insertions(+), 106 deletions(-) delete mode 100644 paper-server/src/main/java/org/bukkit/craftbukkit/block/CapturedBlockState.java diff --git a/paper-api/src/main/java/org/bukkit/event/block/BlockIgniteEvent.java b/paper-api/src/main/java/org/bukkit/event/block/BlockIgniteEvent.java index 3b8b88c907fd..dea449829740 100644 --- a/paper-api/src/main/java/org/bukkit/event/block/BlockIgniteEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/block/BlockIgniteEvent.java @@ -10,8 +10,7 @@ import org.jetbrains.annotations.Nullable; /** - * Called when a block is ignited. If you want to catch when a Player places - * fire, you need to use {@link BlockPlaceEvent}. + * Called when a block is ignited. *

* If this event is cancelled, the block will not be ignited. */ diff --git a/paper-server/patches/features/0032-Block-Capture-System.patch b/paper-server/patches/features/0032-Block-Capture-System.patch index 16014597d5d0..ec6da9e162a0 100644 --- a/paper-server/patches/features/0032-Block-Capture-System.patch +++ b/paper-server/patches/features/0032-Block-Capture-System.patch @@ -1728,6 +1728,20 @@ index d5821239d33aad9213b9a87d225e942934623857..18316ed939130115890c5ce4cd9da502 } } +diff --git a/net/minecraft/world/level/block/SpongeBlock.java b/net/minecraft/world/level/block/SpongeBlock.java +index 298e51da2da64fdaa860108cc34e59d214b5f9a7..86d6a41750e15b0c4c49706ffd827e4fbd14b47a 100644 +--- a/net/minecraft/world/level/block/SpongeBlock.java ++++ b/net/minecraft/world/level/block/SpongeBlock.java +@@ -121,7 +121,8 @@ public class SpongeBlock extends Block { + dropResources(blockState, level, blockPos, blockEntity); + } + } +- snapshot.place(snapshot.getFlags()); ++ int flags = blockList.getEffectiveFlags(snapshot.getPosition()); ++ snapshot.place(flags); + } + + return true; diff --git a/net/minecraft/world/level/block/StemBlock.java b/net/minecraft/world/level/block/StemBlock.java index 82ea0ce3895108d477d10e901e756a27a3a9cedc..14694eaea6d2f27401c3e656f7e9e9133f215494 100644 --- a/net/minecraft/world/level/block/StemBlock.java diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CapturedBlockState.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CapturedBlockState.java deleted file mode 100644 index 6b5853ced761..000000000000 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CapturedBlockState.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.bukkit.craftbukkit.block; - -import net.minecraft.core.BlockPos; -import net.minecraft.util.RandomSource; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.WorldGenLevel; -import net.minecraft.world.level.block.entity.BeehiveBlockEntity; -import net.minecraft.world.level.block.entity.BlockEntityType; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; - -@Deprecated(forRemoval = true) -public final class CapturedBlockState extends CraftBlockState { - - private final boolean treeBlock; - - public CapturedBlockState(Block block, @net.minecraft.world.level.block.Block.UpdateFlags int capturedFlags, boolean treeBlock) { - super(block, capturedFlags); - - this.treeBlock = treeBlock; - } - - private CapturedBlockState(CapturedBlockState state, Location location) { - super(state, location); - this.treeBlock = state.treeBlock; - } - - @Override - public boolean update(boolean force, boolean applyPhysics) { - boolean result = super.update(force, applyPhysics); - - if (result) { - this.addBees(); - } - - return result; - } - - @Override - public boolean place(@net.minecraft.world.level.block.Block.UpdateFlags int flags) { - boolean result = super.place(flags); - this.addBees(); - - return result; - } - - private void addBees() { - // SPIGOT-5537: Horrible hack to manually add bees given Level#captureTreeGeneration does not support block entities - if (this.treeBlock && this.getType() == Material.BEE_NEST) { - WorldGenLevel worldGenLevel = this.world.getHandle(); - BlockPos pos = this.getPosition(); - RandomSource randomSource = worldGenLevel.getRandom(); - - // Begin copied block from BeehiveDecorator - worldGenLevel.getBlockEntity(pos, BlockEntityType.BEEHIVE).ifPresent(beehiveBlockEntity -> { - int i1 = 2 + randomSource.nextInt(2); - - for (int i2 = 0; i2 < i1; i2++) { - beehiveBlockEntity.storeBee(BeehiveBlockEntity.Occupant.create(randomSource.nextInt(599))); - } - }); - // End copied block - } - } - - @Override - public CapturedBlockState copy() { - return new CapturedBlockState(this, null); - } - - @Override - public CapturedBlockState copy(Location location) { - return new CapturedBlockState(this, location); - } - - public static CapturedBlockState getTreeBlockState(Level world, BlockPos pos, @net.minecraft.world.level.block.Block.UpdateFlags int flags) { - return new CapturedBlockState(CraftBlock.at(world, pos), flags, true); - } -} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java index 3036f3fa8b58..b3512ef0fbde 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java @@ -28,21 +28,16 @@ public class CraftBlockState implements BlockState { protected final CraftWorld world; private final BlockPos position; protected net.minecraft.world.level.block.state.BlockState data; - @net.minecraft.world.level.block.Block.UpdateFlags - protected int capturedFlags; // todo move out of this class private WeakReference weakWorld; protected CraftBlockState(final Block block) { this(block.getWorld(), ((CraftBlock) block).getPosition(), ((CraftBlock) block).getNMS()); - this.capturedFlags = net.minecraft.world.level.block.Block.UPDATE_ALL; - this.setWorldHandle(((CraftBlock) block).getHandle()); } @Deprecated protected CraftBlockState(final Block block, @net.minecraft.world.level.block.Block.UpdateFlags int capturedFlags) { this(block); - this.capturedFlags = capturedFlags; } // world can be null for non-placed BlockStates. @@ -62,7 +57,6 @@ protected CraftBlockState(CraftBlockState state, @Nullable Location location) { this.position = CraftLocation.toBlockPosition(location); } this.data = state.data; - this.capturedFlags = state.capturedFlags; this.setWorldHandle(state.getWorldHandle()); } @@ -182,14 +176,6 @@ public Material getType() { return this.data.getBukkitMaterial(); } - public void setFlags(@net.minecraft.world.level.block.Block.UpdateFlags int flags) { - this.capturedFlags = flags; - } - - public @net.minecraft.world.level.block.Block.UpdateFlags int getFlags() { - return this.capturedFlags; - } - @Override public byte getLightLevel() { return this.getBlock().getLightLevel(); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java b/paper-server/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java index 989b2f751a59..08fae23da42a 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/util/BlockStateListPopulator.java @@ -15,10 +15,10 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.storage.LevelData; -import org.bukkit.block.BlockState; import org.bukkit.craftbukkit.block.CraftBlockState; import org.bukkit.craftbukkit.block.CraftBlockStates; @@ -34,7 +34,7 @@ public BlockStateListPopulator(LevelAccessor level) { } @Override - public net.minecraft.world.level.block.state.BlockState getBlockState(BlockPos pos) { + public BlockState getBlockState(BlockPos pos) { CapturedBlock block = this.blocks.get(pos); return block != null ? block.state() : this.level.getBlockState(pos); } @@ -52,7 +52,7 @@ public BlockEntity getBlockEntity(BlockPos pos) { } @Override - public boolean setBlock(BlockPos pos, net.minecraft.world.level.block.state.BlockState state, @Block.UpdateFlags int flags, int recursionLeft) { + public boolean setBlock(BlockPos pos, BlockState state, @Block.UpdateFlags int flags, int recursionLeft) { pos = pos.immutable(); // remove first to keep last updated order this.blocks.remove(pos); @@ -77,7 +77,7 @@ public boolean setBlock(BlockPos pos, net.minecraft.world.level.block.state.Bloc @Override public boolean destroyBlock(BlockPos pos, boolean dropBlock, Entity entity, int recursionLeft) { - net.minecraft.world.level.block.state.BlockState blockState = this.getBlockState(pos); + BlockState blockState = this.getBlockState(pos); if (blockState.isAir()) { return false; } @@ -97,7 +97,6 @@ private void iterateSnapshots(Consumer callback) { CraftBlockState snapshot = CraftBlockStates.getBlockState( this.getMinecraftWorld().getWorld(), entry.getKey(), block.state(), block.blockEntity() ); - snapshot.setFlags(block.flags()); snapshot.setWorldHandle(this.level); callback.accept(snapshot); } @@ -107,7 +106,7 @@ public void placeBlocks() { this.placeSomeBlocks($ -> true); } - public void placeSomeBlocks(Predicate filter) { + public void placeSomeBlocks(Predicate filter) { this.placeSomeBlocks($ -> {}, filter); } @@ -115,22 +114,28 @@ public void placeBlocks(Consumer beforeRun) { this.placeSomeBlocks(beforeRun, $ -> true); } - public void placeSomeBlocks(Consumer beforeRun, Predicate filter) { + public void placeSomeBlocks(Consumer beforeRun, Predicate filter) { for (CraftBlockState snapshot : this.getSnapshotBlocks()) { if (filter.test(snapshot)) { + int flags = this.getEffectiveFlags(snapshot.getPosition()); beforeRun.accept(snapshot); - snapshot.place(snapshot.getFlags()); + snapshot.place(flags); } } } + public @Block.UpdateFlags int getEffectiveFlags(BlockPos pos) { + CapturedBlock block = this.blocks.get(pos); + return block != null ? block.flags() : Block.UPDATE_ALL; // fallback for new API-added blocks + } + public List getSnapshotBlocks() { if (this.snapshots == null) { List snapshots = new ArrayList<>(); this.iterateSnapshots(snapshots::add); this.snapshots = snapshots; } - return snapshots; + return this.snapshots; } // For tree generation @@ -151,7 +156,7 @@ public int getHeight() { } @Override - public boolean isStateAtPosition(BlockPos pos, Predicate state) { + public boolean isStateAtPosition(BlockPos pos, Predicate state) { return state.test(this.getBlockState(pos)); } From 4397b319a7d92d93b742402c5aad1337421e5824 Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Thu, 12 Feb 2026 22:28:19 +0100 Subject: [PATCH 14/17] fix relative pos for placed block on water --- .../features/0032-Block-Capture-System.patch | 15 +++++++++------ .../util/capture/MinecraftCaptureBridge.java | 1 - .../paper/util/capture/SimpleBlockCapture.java | 5 ++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/paper-server/patches/features/0032-Block-Capture-System.patch b/paper-server/patches/features/0032-Block-Capture-System.patch index ec6da9e162a0..fbc5197428af 100644 --- a/paper-server/patches/features/0032-Block-Capture-System.patch +++ b/paper-server/patches/features/0032-Block-Capture-System.patch @@ -154,7 +154,7 @@ index 2818c7ad02a861270283384ceb7ecd4d44f6d624..56823e158da5e852d914b06ed2428903 } } diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java -index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..3707f24b93a24f2d6622d86e056267077f005b43 100644 +index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..9fd5fe79576ac75e9b7a0c690006d739bfebafce 100644 --- a/net/minecraft/world/item/BlockItem.java +++ b/net/minecraft/world/item/BlockItem.java @@ -57,59 +57,31 @@ public class BlockItem extends Item { @@ -228,7 +228,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..3707f24b93a24f2d6622d86e05626707 level.playSound( player, clickedPos, -@@ -119,8 +91,42 @@ public class BlockItem extends Item { +@@ -119,8 +91,45 @@ public class BlockItem extends Item { soundType.getPitch() * 0.8F ); level.gameEvent(GameEvent.BLOCK_PLACE, clickedPos, GameEvent.Context.of(player, blockState)); @@ -247,6 +247,9 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..3707f24b93a24f2d6622d86e05626707 + capture.overlayCaptureOnLevel(); + + BlockPos relativePos = context.getHitResult().getBlockPos(); ++ if (this instanceof PlaceOnWaterBlockItem) { // see PlaceOnWaterBlockItem#use ++ relativePos = relativePos.below(); ++ } + if (blocks.size() > 1) { + placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, relativePos); + } else if (blocks.size() == 1) { @@ -272,7 +275,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..3707f24b93a24f2d6622d86e05626707 } } } -@@ -134,7 +140,7 @@ public class BlockItem extends Item { +@@ -134,7 +143,7 @@ public class BlockItem extends Item { return context; } @@ -281,7 +284,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..3707f24b93a24f2d6622d86e05626707 BlockEntity blockEntity = level.getBlockEntity(pos); if (blockEntity != null) { blockEntity.applyComponentsFromItemStack(stack); -@@ -142,7 +148,7 @@ public class BlockItem extends Item { +@@ -142,7 +151,7 @@ public class BlockItem extends Item { } } @@ -290,7 +293,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..3707f24b93a24f2d6622d86e05626707 return updateCustomBlockEntityTag(level, player, pos, stack); } -@@ -151,7 +157,7 @@ public class BlockItem extends Item { +@@ -151,7 +160,7 @@ public class BlockItem extends Item { return stateForPlacement != null && this.canPlace(context, stateForPlacement) ? stateForPlacement : null; } @@ -299,7 +302,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..3707f24b93a24f2d6622d86e05626707 BlockItemStateProperties blockItemStateProperties = stack.getOrDefault(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY); if (blockItemStateProperties.isEmpty()) { return state; -@@ -186,11 +192,11 @@ public class BlockItem extends Item { +@@ -186,11 +195,11 @@ public class BlockItem extends Item { return true; } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java index 3f20f79ee5da..c6e32da29447 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java @@ -413,7 +413,6 @@ public void applyTasks() { this.serverLevelOverlayLayer.getRecordMap().applyApiPatch(this.parent); } - // Apply block entities, those may have been written in our estimation. So just apply them. // I don't really like this, as I in general don't really want to apply things from the prediction layer. // But, these may be mutated by anything. diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java index cb9482af544e..be1e12ed2a08 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java @@ -13,12 +13,11 @@ public class SimpleBlockCapture implements AutoCloseable { private final MinecraftCaptureBridge capturingWorldLevel; private final ServerLevel level; + private final @Nullable SimpleBlockCapture oldCapture; private boolean isOverlayingCaptureOnLevel = false; - private final SimpleBlockCapture oldCapture; - - public SimpleBlockCapture(BlockPlacementPredictor base, ServerLevel level, SimpleBlockCapture oldCapture) { + public SimpleBlockCapture(BlockPlacementPredictor base, ServerLevel level, @Nullable SimpleBlockCapture oldCapture) { this.capturingWorldLevel = new MinecraftCaptureBridge(level, base); this.level = level; this.oldCapture = oldCapture; From d4895653fa1a6700a5538b4ce1b8f0541db9c24b Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Fri, 13 Feb 2026 20:31:06 +0100 Subject: [PATCH 15/17] bunch of bonemeal fixes Track bonemeal usage for StructureGrowEvent and propagate context properly between the two events. Call the StructureGrowEvent for fungus, only call the BlockFertilizeEvent if the placement is sucessful. --- .../event/block/BlockMultiPlaceEvent.java | 14 +- .../bukkit/event/block/BlockPlaceEvent.java | 12 +- .../features/0032-Block-Capture-System.patch | 307 +++++++++--------- .../paper/util/capture/BoneMealContext.java | 78 ++++- .../paper/util/capture/CaptureRecordMap.java | 16 +- .../util/capture/MinecraftCaptureBridge.java | 9 +- .../ServerLevelPaperCapturingWorldLevel.java | 2 +- .../util/capture/SimpleBlockCapture.java | 13 +- .../org/bukkit/craftbukkit/CraftWorld.java | 4 +- .../craftbukkit/event/CraftEventFactory.java | 55 ++-- 10 files changed, 296 insertions(+), 214 deletions(-) diff --git a/paper-api/src/main/java/org/bukkit/event/block/BlockMultiPlaceEvent.java b/paper-api/src/main/java/org/bukkit/event/block/BlockMultiPlaceEvent.java index 0df9dfa44dcc..7f417d5b9f7f 100644 --- a/paper-api/src/main/java/org/bukkit/event/block/BlockMultiPlaceEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/block/BlockMultiPlaceEvent.java @@ -18,18 +18,18 @@ */ public class BlockMultiPlaceEvent extends BlockPlaceEvent { - private final List states; + private final List replacedStates; @ApiStatus.Internal @Deprecated(forRemoval = true) - public BlockMultiPlaceEvent(@NotNull List states, @NotNull Block clicked, @NotNull ItemStack itemInHand, @NotNull Player thePlayer, boolean canBuild) { - this(states, clicked, itemInHand, thePlayer, canBuild, org.bukkit.inventory.EquipmentSlot.HAND); + public BlockMultiPlaceEvent(@NotNull List replacedStates, @NotNull Block clicked, @NotNull ItemStack itemInHand, @NotNull Player thePlayer, boolean canBuild) { + this(replacedStates, clicked, itemInHand, thePlayer, canBuild, org.bukkit.inventory.EquipmentSlot.HAND); } @ApiStatus.Internal - public BlockMultiPlaceEvent(@NotNull List states, @NotNull Block clicked, @NotNull ItemStack itemInHand, @NotNull Player thePlayer, boolean canBuild, @NotNull org.bukkit.inventory.EquipmentSlot hand) { - super(states.get(0).getBlock(), states.get(0), clicked, itemInHand, thePlayer, canBuild, hand); - this.states = ImmutableList.copyOf(states); + public BlockMultiPlaceEvent(@NotNull List replacedStates, @NotNull Block clicked, @NotNull ItemStack itemInHand, @NotNull Player thePlayer, boolean canBuild, @NotNull org.bukkit.inventory.EquipmentSlot hand) { + super(replacedStates.getFirst().getBlock(), replacedStates.getFirst(), clicked, itemInHand, thePlayer, canBuild, hand); + this.replacedStates = ImmutableList.copyOf(replacedStates); } /** @@ -41,6 +41,6 @@ public BlockMultiPlaceEvent(@NotNull List states, @NotNull Block cli */ @NotNull public List getReplacedBlockStates() { - return this.states; + return this.replacedStates; } } diff --git a/paper-api/src/main/java/org/bukkit/event/block/BlockPlaceEvent.java b/paper-api/src/main/java/org/bukkit/event/block/BlockPlaceEvent.java index 81dd17b033ea..394ae727e3ee 100644 --- a/paper-api/src/main/java/org/bukkit/event/block/BlockPlaceEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/block/BlockPlaceEvent.java @@ -22,7 +22,7 @@ public class BlockPlaceEvent extends BlockEvent implements Cancellable { protected Block placedAgainst; protected ItemStack itemInHand; protected Player player; - protected BlockState replacedBlockState; + protected BlockState replacedState; protected boolean canBuild; protected EquipmentSlot hand; @@ -30,17 +30,17 @@ public class BlockPlaceEvent extends BlockEvent implements Cancellable { @ApiStatus.Internal @Deprecated(since = "1.9", forRemoval = true) - public BlockPlaceEvent(@NotNull final Block placedBlock, @NotNull final BlockState replacedBlockState, @NotNull final Block placedAgainst, @NotNull final ItemStack itemInHand, @NotNull final Player thePlayer, final boolean canBuild) { - this(placedBlock, replacedBlockState, placedAgainst, itemInHand, thePlayer, canBuild, EquipmentSlot.HAND); + public BlockPlaceEvent(@NotNull final Block placedBlock, @NotNull final BlockState replacedState, @NotNull final Block placedAgainst, @NotNull final ItemStack itemInHand, @NotNull final Player thePlayer, final boolean canBuild) { + this(placedBlock, replacedState, placedAgainst, itemInHand, thePlayer, canBuild, EquipmentSlot.HAND); } @ApiStatus.Internal - public BlockPlaceEvent(@NotNull final Block placedBlock, @NotNull final BlockState replacedBlockState, @NotNull final Block placedAgainst, @NotNull final ItemStack itemInHand, @NotNull final Player thePlayer, final boolean canBuild, @NotNull final EquipmentSlot hand) { + public BlockPlaceEvent(@NotNull final Block placedBlock, @NotNull final BlockState replacedState, @NotNull final Block placedAgainst, @NotNull final ItemStack itemInHand, @NotNull final Player thePlayer, final boolean canBuild, @NotNull final EquipmentSlot hand) { super(placedBlock); this.placedAgainst = placedAgainst; this.itemInHand = itemInHand; this.player = thePlayer; - this.replacedBlockState = replacedBlockState; + this.replacedState = replacedState; this.canBuild = canBuild; this.hand = hand; } @@ -95,7 +95,7 @@ public Block getBlockPlaced() { */ @NotNull public BlockState getBlockReplacedState() { - return this.replacedBlockState; + return this.replacedState; } /** diff --git a/paper-server/patches/features/0032-Block-Capture-System.patch b/paper-server/patches/features/0032-Block-Capture-System.patch index fbc5197428af..4b042abdb10a 100644 --- a/paper-server/patches/features/0032-Block-Capture-System.patch +++ b/paper-server/patches/features/0032-Block-Capture-System.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Block Capture System diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java -index bfefb5031544caa59230f0073e8880c2b39ebf4d..40810b410c92f8fd2edaec7443450c403171ee11 100644 +index bfefb5031544caa59230f0073e8880c2b39ebf4d..bab79efd6357c389fbde8425e7073abcd172e2f3 100644 --- a/net/minecraft/core/dispenser/DispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -400,46 +400,22 @@ public interface DispenseItemBehavior { +@@ -400,46 +400,21 @@ public interface DispenseItemBehavior { this.setSuccess(true); Level level = blockSource.level(); BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING)); @@ -22,15 +22,14 @@ index bfefb5031544caa59230f0073e8880c2b39ebf4d..40810b410c92f8fd2edaec7443450c40 - level.captureTreeGeneration = true; // CraftBukkit - if (!BoneMealItem.growCrop(item, level, blockPos) && !BoneMealItem.growWaterPlant(item, level, blockPos, null)) { + // Paper start -+ io.papermc.paper.util.capture.BoneMealContext boneMealContext = new io.papermc.paper.util.capture.BoneMealContext(); -+ boneMealContext.ogServerLevel = level.getMinecraftWorld(); -+ ++ io.papermc.paper.util.capture.BoneMealContext boneMealContext = new io.papermc.paper.util.capture.BoneMealContext(level.getMinecraftWorld()); ++ boneMealContext.usedBoneMeal = true; + if (!BoneMealItem.growCrop(item, level, blockPos, boneMealContext) && !org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( -+ (ServerLevel) level, -+ boneMealContext.getBukkitPlayer(), -+ blockPos, -+ (res) -> BoneMealItem.growWaterPlant(item, res, blockPos, null, boneMealContext), -+ boneMealContext.precancelStructureEvent ++ (ServerLevel) level, ++ boneMealContext.getBukkitPlayer(), ++ blockPos, ++ world -> BoneMealItem.growWaterPlant(item, world, blockPos, null, boneMealContext), ++ boneMealContext + )) { + // Paper end this.setSuccess(false); @@ -154,10 +153,10 @@ index 2818c7ad02a861270283384ceb7ecd4d44f6d624..56823e158da5e852d914b06ed2428903 } } diff --git a/net/minecraft/world/item/BlockItem.java b/net/minecraft/world/item/BlockItem.java -index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..9fd5fe79576ac75e9b7a0c690006d739bfebafce 100644 +index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..58ee47c73c4f6a21852cf459de90f53205c0dace 100644 --- a/net/minecraft/world/item/BlockItem.java +++ b/net/minecraft/world/item/BlockItem.java -@@ -57,59 +57,31 @@ public class BlockItem extends Item { +@@ -57,59 +57,30 @@ public class BlockItem extends Item { return InteractionResult.FAIL; } else { BlockState placementState = this.getPlacementState(blockPlaceContext); @@ -177,8 +176,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..9fd5fe79576ac75e9b7a0c690006d739 + } + // Paper start + try (io.papermc.paper.util.capture.SimpleBlockCapture capture = ((net.minecraft.server.level.ServerLevel) context.getLevel()).forkCaptureSession()) { -+ boolean placeRes = this.placeBlock(blockPlaceContext, placementState, capture.capturingWorldLevel()); -+ if (!placeRes) { ++ if (!this.placeBlock(blockPlaceContext, placementState, capture.capturingWorldLevel())) { + return InteractionResult.FAIL; + } + // Paper end @@ -228,44 +226,39 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..9fd5fe79576ac75e9b7a0c690006d739 level.playSound( player, clickedPos, -@@ -119,8 +91,45 @@ public class BlockItem extends Item { +@@ -119,8 +90,40 @@ public class BlockItem extends Item { soundType.getPitch() * 0.8F ); level.gameEvent(GameEvent.BLOCK_PLACE, clickedPos, GameEvent.Context.of(player, blockState)); + // Paper start -+ net.minecraft.world.InteractionHand hand = blockPlaceContext.getHand(); -+ net.minecraft.server.level.ServerLevel serverLevel = (net.minecraft.server.level.ServerLevel) blockPlaceContext.getLevel(); -+ org.bukkit.event.block.BlockPlaceEvent placeEvent = null; -+ -+ Map snapshots = capture.getCapturedSnapshots(); ++ if (player != null) { // todo dispensed shulker ++ net.minecraft.world.InteractionHand hand = blockPlaceContext.getHand(); ++ net.minecraft.server.level.ServerLevel serverLevel = (net.minecraft.server.level.ServerLevel) blockPlaceContext.getLevel(); + -+ java.util.List blocks = new java.util.ArrayList<>(snapshots.size()); -+ snapshots.values().forEach((state) -> { -+ blocks.add(state.getBlock().getState()); -+ }); -+ -+ capture.overlayCaptureOnLevel(); ++ java.util.List blocks = capture.getAffectedBlocks().map(org.bukkit.block.Block::getState).toList(); ++ BlockPos relativePos = context.getHitResult().getBlockPos(); ++ if (this instanceof PlaceOnWaterBlockItem) { // see PlaceOnWaterBlockItem#use ++ relativePos = relativePos.below(); ++ } ++ capture.overlayCaptureOnLevel(); + -+ BlockPos relativePos = context.getHitResult().getBlockPos(); -+ if (this instanceof PlaceOnWaterBlockItem) { // see PlaceOnWaterBlockItem#use -+ relativePos = relativePos.below(); -+ } -+ if (blocks.size() > 1) { -+ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, relativePos); -+ } else if (blocks.size() == 1) { -+ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(serverLevel, player, hand, blocks.getFirst(), relativePos); -+ } ++ final org.bukkit.event.block.BlockPlaceEvent placeEvent; ++ if (blocks.size() > 1) { ++ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(serverLevel, player, hand, blocks, relativePos); ++ } else { ++ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(serverLevel, player, hand, blocks.getFirst(), relativePos); ++ } + -+ if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) { -+ // PAIL: Remove this when MC-99075 fixed -+ player.containerMenu.forceHeldSlot(hand); -+ return InteractionResult.FAIL; -+ } else { ++ if (placeEvent.isCancelled() || !placeEvent.canBuild()) { ++ // PAIL: Remove this when MC-99075 fixed ++ player.containerMenu.forceHeldSlot(hand); ++ return InteractionResult.FAIL; ++ } + // We are good! + capture.finalizePlacement(); -+ if (player instanceof ServerPlayer) { -+ CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer)player, clickedPos, itemInHand); -+ } ++ } ++ if (player instanceof ServerPlayer) { ++ CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer)player, clickedPos, itemInHand); + } + // Paper end + @@ -275,7 +268,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..9fd5fe79576ac75e9b7a0c690006d739 } } } -@@ -134,7 +143,7 @@ public class BlockItem extends Item { +@@ -134,7 +137,7 @@ public class BlockItem extends Item { return context; } @@ -284,7 +277,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..9fd5fe79576ac75e9b7a0c690006d739 BlockEntity blockEntity = level.getBlockEntity(pos); if (blockEntity != null) { blockEntity.applyComponentsFromItemStack(stack); -@@ -142,7 +151,7 @@ public class BlockItem extends Item { +@@ -142,7 +145,7 @@ public class BlockItem extends Item { } } @@ -293,7 +286,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..9fd5fe79576ac75e9b7a0c690006d739 return updateCustomBlockEntityTag(level, player, pos, stack); } -@@ -151,7 +160,7 @@ public class BlockItem extends Item { +@@ -151,7 +154,7 @@ public class BlockItem extends Item { return stateForPlacement != null && this.canPlace(context, stateForPlacement) ? stateForPlacement : null; } @@ -302,7 +295,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..9fd5fe79576ac75e9b7a0c690006d739 BlockItemStateProperties blockItemStateProperties = stack.getOrDefault(DataComponents.BLOCK_STATE, BlockItemStateProperties.EMPTY); if (blockItemStateProperties.isEmpty()) { return state; -@@ -186,11 +195,11 @@ public class BlockItem extends Item { +@@ -186,11 +189,11 @@ public class BlockItem extends Item { return true; } @@ -318,7 +311,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..9fd5fe79576ac75e9b7a0c690006d739 return false; } else { diff --git a/net/minecraft/world/item/BoneMealItem.java b/net/minecraft/world/item/BoneMealItem.java -index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..d4b0defd9236003ee739fe8ceee37b0a913fd340 100644 +index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..168db7a6c623ccb2562eaea699dd21f8f4c7cd2d 100644 --- a/net/minecraft/world/item/BoneMealItem.java +++ b/net/minecraft/world/item/BoneMealItem.java @@ -44,7 +44,12 @@ public class BoneMealItem extends Item { @@ -327,9 +320,9 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..d4b0defd9236003ee739fe8ceee37b0a ItemStack itemInHand = context.getItemInHand(); - if (growCrop(itemInHand, level, clickedPos)) { + // Paper start -+ io.papermc.paper.util.capture.BoneMealContext boneMealContext = new io.papermc.paper.util.capture.BoneMealContext(); -+ boneMealContext.player = (net.minecraft.server.level.ServerPlayer) context.getPlayer(); -+ boneMealContext.ogServerLevel = level.getMinecraftWorld(); ++ io.papermc.paper.util.capture.BoneMealContext boneMealContext = new io.papermc.paper.util.capture.BoneMealContext(level.getMinecraftWorld()); ++ boneMealContext.player = context.getPlayer(); ++ boneMealContext.usedBoneMeal = true; + // Paper end + if (growCrop(itemInHand, level, clickedPos, boneMealContext)) { // Paper if (!level.isClientSide()) { @@ -342,11 +335,11 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..d4b0defd9236003ee739fe8ceee37b0a - if (isFaceSturdy && growWaterPlant(itemInHand, level, blockPos, context.getClickedFace())) { + // Paper start + boolean result = org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( -+ (ServerLevel) level, -+ boneMealContext.getBukkitPlayer(), -+ blockPos, -+ (res) -> growWaterPlant(itemInHand, res, blockPos, context.getClickedFace(), boneMealContext), -+ boneMealContext.precancelStructureEvent ++ (ServerLevel) level, ++ boneMealContext.getBukkitPlayer(), ++ blockPos, ++ world -> growWaterPlant(itemInHand, world, blockPos, context.getClickedFace(), boneMealContext), ++ boneMealContext + ); + if (isFaceSturdy && result) { + // Paper end @@ -365,12 +358,12 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..d4b0defd9236003ee739fe8ceee37b0a if (bonemealableBlock.isBonemealSuccess(level, level.random, pos, blockState)) { - bonemealableBlock.performBonemeal((ServerLevel)level, level.random, pos, blockState); + // Paper start -+ org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( -+ (ServerLevel) level, -+ mealContext.getBukkitPlayer(), -+ pos, -+ (res) -> bonemealableBlock.performBonemeal(res, level.random, pos, blockState, mealContext), -+ mealContext.precancelStructureEvent ++ org.bukkit.craftbukkit.event.CraftEventFactory.fertilizedBlock( ++ (ServerLevel) level, ++ mealContext.getBukkitPlayer(), ++ pos, ++ world -> bonemealableBlock.performBonemeal(world, level.random, pos, blockState, mealContext), ++ mealContext + ); + // Paper end } @@ -936,7 +929,7 @@ index 88c204ad9d4ead792eb618dbb8611cca863e798c..baf1f1b9e445c5caf2784f73db49ae83 } } diff --git a/net/minecraft/world/level/block/BambooStalkBlock.java b/net/minecraft/world/level/block/BambooStalkBlock.java -index 81e2a279d4c29f5fe52387875489239515a8c82b..af797be959d91e30b444459f2ccc99c29b680fc7 100644 +index 81e2a279d4c29f5fe52387875489239515a8c82b..6955c72fd5d1af64769a13b69216ef016d349f03 100644 --- a/net/minecraft/world/level/block/BambooStalkBlock.java +++ b/net/minecraft/world/level/block/BambooStalkBlock.java @@ -166,7 +166,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { @@ -957,6 +950,15 @@ index 81e2a279d4c29f5fe52387875489239515a8c82b..af797be959d91e30b444459f2ccc99c2 BlockState blockState = level.getBlockState(pos.below()); BlockPos blockPos = pos.below(2); BlockState blockState1 = level.getBlockState(blockPos); +@@ -221,7 +221,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { + protected int getHeightAboveUpToMax(BlockGetter level, BlockPos pos) { + int i = 0; + +- while (i < ((Level) level).paperConfig().maxGrowthHeight.bamboo.max && level.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO)) { // Paper - Configurable cactus/bamboo/reed growth height ++ while (i < ((Level) level).paperConfig().maxGrowthHeight.bamboo.max && level.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO)) { // Paper - Configurable cactus/bamboo/reed growth height // todo ClassCastException when bonemealing a bamboo + i++; + } + diff --git a/net/minecraft/world/level/block/BedBlock.java b/net/minecraft/world/level/block/BedBlock.java index 7bfc62120cae4c5e83cb0d86b759b7ffb2336a95..3d5ed3d705c80772ac8cb7fff7b213bb3e92c41c 100644 --- a/net/minecraft/world/level/block/BedBlock.java @@ -1312,27 +1314,37 @@ index 2a24e71d2b598792ebaa867a2f74fd79e97cc6a2..5f39cc10064c222f9009bdee5ce59bfe } } diff --git a/net/minecraft/world/level/block/FungusBlock.java b/net/minecraft/world/level/block/FungusBlock.java -index 9711efb088bd0da9168e9bcd0496bd7caddd2974..a3f9d237ae3a80ef5d28105297c2898fe33c3e79 100644 +index 9711efb088bd0da9168e9bcd0496bd7caddd2974..8dbaabce9509b7daec8bbdff5e37cf963f2ae04b 100644 --- a/net/minecraft/world/level/block/FungusBlock.java +++ b/net/minecraft/world/level/block/FungusBlock.java -@@ -71,14 +71,14 @@ public class FungusBlock extends VegetationBlock implements BonemealableBlock { +@@ -71,18 +71,14 @@ public class FungusBlock extends VegetationBlock implements BonemealableBlock { } @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper - this.getFeature(level) - // CraftBukkit start - .map((value) -> { - if (this == Blocks.WARPED_FUNGUS) { +- this.getFeature(level) +- // CraftBukkit start +- .map((value) -> { +- if (this == Blocks.WARPED_FUNGUS) { - SaplingBlock.treeType = org.bukkit.TreeType.WARPED_FUNGUS; -+ mealContext.treeHook = org.bukkit.TreeType.WARPED_FUNGUS; - } else if (this == Blocks.CRIMSON_FUNGUS) { +- } else if (this == Blocks.CRIMSON_FUNGUS) { - SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS; -+ mealContext.treeHook = org.bukkit.TreeType.CRIMSON_FUNGUS; - } - return value; - }) +- } +- return value; +- }) +- .ifPresent(holder -> holder.value().place(level, level.getChunkSource().getGenerator(), random, pos)); +- // CraftBukkit end ++ // Paper start ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { ++ this.getFeature(level).ifPresent(holder -> { ++ mealContext.treeHook = io.papermc.paper.util.capture.BoneMealContext.getTreeType(holder); ++ org.bukkit.craftbukkit.event.CraftEventFactory.structureEvent(level, pos, $ -> { ++ return holder.value().place(level, level.getChunkSource().getGenerator(), random, pos); ++ }, mealContext); ++ }); ++ // Paper end + } + } diff --git a/net/minecraft/world/level/block/GlowLichenBlock.java b/net/minecraft/world/level/block/GlowLichenBlock.java index ba39497a6d7160cde961e339be8028ec131b8019..f38447dbc1880878d29e08a18a2db32319291992 100644 --- a/net/minecraft/world/level/block/GlowLichenBlock.java @@ -1369,10 +1381,10 @@ index 368f60ecce691ea161120743150e87b32efc3ca4..8a74e7b53ec7d3c3ad7b71856ef01abe } diff --git a/net/minecraft/world/level/block/GrowingPlantBodyBlock.java b/net/minecraft/world/level/block/GrowingPlantBodyBlock.java -index 314d198617e34f91f72a2952a2a62ce0a3b9147d..a043d0956de69914736346241071cd59d8514d66 100644 +index 314d198617e34f91f72a2952a2a62ce0a3b9147d..3ccaf85f148ce738fb122842b2aef5e94aa49cdc 100644 --- a/net/minecraft/world/level/block/GrowingPlantBodyBlock.java +++ b/net/minecraft/world/level/block/GrowingPlantBodyBlock.java -@@ -74,7 +74,7 @@ public abstract class GrowingPlantBodyBlock extends GrowingPlantBlock implements +@@ -74,11 +74,11 @@ public abstract class GrowingPlantBodyBlock extends GrowingPlantBlock implements } @Override @@ -1381,6 +1393,11 @@ index 314d198617e34f91f72a2952a2a62ce0a3b9147d..a043d0956de69914736346241071cd59 Optional headPos = this.getHeadPos(level, pos, state.getBlock()); if (headPos.isPresent()) { BlockState blockState = level.getBlockState(headPos.get()); +- ((GrowingPlantHeadBlock)blockState.getBlock()).performBonemeal(level, random, headPos.get(), blockState); ++ ((GrowingPlantHeadBlock)blockState.getBlock()).performBonemeal(level, random, headPos.get(), blockState, mealContext); // Paper + } + } + diff --git a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java index bac7f990282fd7c676c2f8c40d7fd87badb1e284..1a1dcfe8abff794663f3aaeefff693686cc54bce 100644 --- a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java @@ -1474,10 +1491,10 @@ index 3762d833f17d596e04845a3f391423f9c14a878b..f5d03f2a3a3866857ddaece77ac4e127 if (!blockState.isAir()) { level.setBlock(pos.above(), blockState, Block.UPDATE_ALL); diff --git a/net/minecraft/world/level/block/MushroomBlock.java b/net/minecraft/world/level/block/MushroomBlock.java -index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..0033bbcf49da014a0b12a17b47833af6dddafc50 100644 +index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..b2c8c755af383aa5e15cde5207282f7481a60012 100644 --- a/net/minecraft/world/level/block/MushroomBlock.java +++ b/net/minecraft/world/level/block/MushroomBlock.java -@@ -87,16 +87,20 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock +@@ -87,14 +87,18 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock return blockState.is(BlockTags.MUSHROOM_GROW_BLOCK) || level.getRawBrightness(pos, 0) < 13 && this.mayPlaceOn(blockState, level, blockPos); } @@ -1491,16 +1508,14 @@ index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..0033bbcf49da014a0b12a17b47833af6 - SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit - if (optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) { + // Paper start -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.structureEvent(mealContext.ogServerLevel, level, mealContext.getBukkitPlayer(), pos, (gen) -> { ++ mealContext.treeHook = io.papermc.paper.util.capture.BoneMealContext.getTreeType(optional.get()); ++ if (org.bukkit.craftbukkit.event.CraftEventFactory.structureEvent(level, pos, $ -> { + return optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos); -+ }, (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM)) { ++ }, mealContext)) { + // Paper end return true; } else { -+ mealContext.precancelStructureEvent = true; // Paper level.setBlock(pos, state, Block.UPDATE_ALL); - return false; - } @@ -114,7 +118,7 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock } @@ -1591,7 +1606,7 @@ index db6b32016a1ad4264da9d8812e5ea9356d0601df..19000f09efadb6a2c616badfb837487a } diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java -index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..12d17a4cc61e35d199ba3f0d39435e8bfb1112d7 100644 +index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..0c20853ebcad4b0d55577ef361c551eeee4c93d3 100644 --- a/net/minecraft/world/level/block/SaplingBlock.java +++ b/net/minecraft/world/level/block/SaplingBlock.java @@ -25,7 +25,6 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { @@ -1602,16 +1617,14 @@ index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..12d17a4cc61e35d199ba3f0d39435e8b @Override public MapCodec codec() { -@@ -50,38 +49,19 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { +@@ -50,38 +49,17 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { } } - public void advanceTree(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) { + // Paper start + public void advanceTree(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) { // Paper -+ io.papermc.paper.util.capture.BoneMealContext mealContext = new io.papermc.paper.util.capture.BoneMealContext(); -+ mealContext.ogServerLevel = level; -+ this.advanceTree(level, pos, state, random, mealContext); ++ this.advanceTree(level, pos, state, random, new io.papermc.paper.util.capture.BoneMealContext(level)); + } + + public void advanceTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.BoneMealContext mealContext) { @@ -1651,7 +1664,7 @@ index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..12d17a4cc61e35d199ba3f0d39435e8b } } -@@ -96,8 +76,8 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { +@@ -96,8 +74,8 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { } @Override @@ -1702,7 +1715,7 @@ index 1df47e0ea401267027721342aaf26639b2622e13..a0188c77806f4fe21cffcb43aa83cde6 } } diff --git a/net/minecraft/world/level/block/SmallDripleafBlock.java b/net/minecraft/world/level/block/SmallDripleafBlock.java -index d5821239d33aad9213b9a87d225e942934623857..18316ed939130115890c5ce4cd9da502a98cf057 100644 +index d5821239d33aad9213b9a87d225e942934623857..a3c8baf014ae2e26f022bff78d4e0e7d6808fa80 100644 --- a/net/minecraft/world/level/block/SmallDripleafBlock.java +++ b/net/minecraft/world/level/block/SmallDripleafBlock.java @@ -64,7 +64,7 @@ public class SmallDripleafBlock extends DoublePlantBlock implements Bonemealable @@ -1710,7 +1723,7 @@ index d5821239d33aad9213b9a87d225e942934623857..18316ed939130115890c5ce4cd9da502 @Override - public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { -+ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper ++ public void setPlacedBy(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) { // Paper - block placement capturing if (!level.isClientSide()) { BlockPos blockPos = pos.above(); BlockState blockState = DoublePlantBlock.copyWaterloggedFrom( @@ -1897,10 +1910,10 @@ index c57cb7b47a01e313a1be5de769c6766ac2787d95..f34731580bc880dcc5d3e96923a7f2b4 } } diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java -index 5471619a0484ece08640e2b3fd26746c351dc3e0..166120e22e7caaae708e5e447a30496b772ed91f 100644 +index 5471619a0484ece08640e2b3fd26746c351dc3e0..6dd4619f3c5d620150f8039282fc8e968b8fa876 100644 --- a/net/minecraft/world/level/block/grower/TreeGrower.java +++ b/net/minecraft/world/level/block/grower/TreeGrower.java -@@ -122,7 +122,35 @@ public final class TreeGrower { +@@ -122,7 +122,34 @@ public final class TreeGrower { return this.secondaryMegaTree.isPresent() && random.nextFloat() < this.secondaryChance ? this.secondaryMegaTree.get() : this.megaTree.orElse(null); } @@ -1915,13 +1928,12 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..166120e22e7caaae708e5e447a30496b + try (io.papermc.paper.util.capture.SimpleBlockCapture capture = level.forkCaptureSession()) { + io.papermc.paper.util.capture.MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); + if (this.growTree0(captureTreeGeneration, chunkGenerator, pos, state, random, mealContext)) { -+ Map snapshots = captureTreeGeneration.calculateLatestSnapshots(mealContext.ogServerLevel); -+ org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, mealContext.ogServerLevel); -+ java.util.List blocks = new java.util.ArrayList<>(snapshots.values()); -+ org.bukkit.event.world.StructureGrowEvent event = new org.bukkit.event.world.StructureGrowEvent(location, mealContext.treeHook, false, mealContext.getBukkitPlayer(), blocks); ++ org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, mealContext.originalLevel); ++ java.util.List blocks = captureTreeGeneration.calculateLatestSnapshots(mealContext.originalLevel); ++ org.bukkit.event.world.StructureGrowEvent event = new org.bukkit.event.world.StructureGrowEvent(location, mealContext.treeHook, mealContext.usedBoneMeal, mealContext.getBukkitPlayer(), blocks); + + if (event.callEvent()) { -+ captureTreeGeneration.applyTasks(); ++ captureTreeGeneration.applyTasks(); // todo block list is mutable + return true; + } else { + mealContext.precancelStructureEvent = true; @@ -1937,102 +1949,83 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..166120e22e7caaae708e5e447a30496b ResourceKey> configuredMegaFeature = this.getConfiguredMegaFeature(random); if (configuredMegaFeature != null) { Holder> holder = level.registryAccess() -@@ -130,7 +158,7 @@ public final class TreeGrower { +@@ -130,7 +157,7 @@ public final class TreeGrower { .get(configuredMegaFeature) .orElse(null); if (holder != null) { - this.setTreeType(holder); // CraftBukkit -+ if (mealContext != null) mealContext.treeHook = this.getTreeType(holder); // Paper ++ mealContext.treeHook = io.papermc.paper.util.capture.BoneMealContext.getTreeType(holder); // Paper for (int i = 0; i >= -1; i--) { for (int i1 = 0; i1 >= -1; i1--) { if (isTwoByTwoSapling(state, level, pos, i, i1)) { -@@ -163,7 +191,7 @@ public final class TreeGrower { +@@ -163,7 +190,7 @@ public final class TreeGrower { if (holder1 == null) { return false; } else { - this.setTreeType(holder1); // CraftBukkit -+ if (mealContext != null) mealContext.treeHook = this.getTreeType(holder1); // Paper ++ mealContext.treeHook = io.papermc.paper.util.capture.BoneMealContext.getTreeType(holder1); // Paper ConfiguredFeature configuredFeature2 = holder1.value(); BlockState blockState1 = level.getFluidState(pos).createLegacyBlock(); level.setBlock(pos, blockState1, Block.UPDATE_NONE); -@@ -200,53 +228,53 @@ public final class TreeGrower { - } +@@ -198,58 +225,4 @@ public final class TreeGrower { - // CraftBukkit start + return false; + } +- +- // CraftBukkit start - private void setTreeType(Holder> feature) { -+ public org.bukkit.TreeType getTreeType(Holder> feature) { - if (feature.is(TreeFeatures.OAK) || feature.is(TreeFeatures.OAK_BEES_005)) { +- if (feature.is(TreeFeatures.OAK) || feature.is(TreeFeatures.OAK_BEES_005)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TREE; -+ return org.bukkit.TreeType.TREE; - } else if (feature.is(TreeFeatures.HUGE_RED_MUSHROOM)) { +- } else if (feature.is(TreeFeatures.HUGE_RED_MUSHROOM)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.RED_MUSHROOM; -+ return org.bukkit.TreeType.RED_MUSHROOM; - } else if (feature.is(TreeFeatures.HUGE_BROWN_MUSHROOM)) { +- } else if (feature.is(TreeFeatures.HUGE_BROWN_MUSHROOM)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BROWN_MUSHROOM; -+ return org.bukkit.TreeType.BROWN_MUSHROOM; - } else if (feature.is(TreeFeatures.JUNGLE_TREE)) { +- } else if (feature.is(TreeFeatures.JUNGLE_TREE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.COCOA_TREE; -+ return org.bukkit.TreeType.COCOA_TREE; - } else if (feature.is(TreeFeatures.JUNGLE_TREE_NO_VINE)) { +- } else if (feature.is(TreeFeatures.JUNGLE_TREE_NO_VINE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SMALL_JUNGLE; -+ return org.bukkit.TreeType.SMALL_JUNGLE; - } else if (feature.is(TreeFeatures.PINE)) { +- } else if (feature.is(TreeFeatures.PINE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_REDWOOD; -+ return org.bukkit.TreeType.TALL_REDWOOD; - } else if (feature.is(TreeFeatures.SPRUCE)) { +- } else if (feature.is(TreeFeatures.SPRUCE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.REDWOOD; -+ return org.bukkit.TreeType.REDWOOD; - } else if (feature.is(TreeFeatures.ACACIA)) { +- } else if (feature.is(TreeFeatures.ACACIA)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.ACACIA; -+ return org.bukkit.TreeType.ACACIA; - } else if (feature.is(TreeFeatures.BIRCH) || feature.is(TreeFeatures.BIRCH_BEES_005)) { +- } else if (feature.is(TreeFeatures.BIRCH) || feature.is(TreeFeatures.BIRCH_BEES_005)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIRCH; -+ return org.bukkit.TreeType.BIRCH; - } else if (feature.is(TreeFeatures.SUPER_BIRCH_BEES_0002)) { +- } else if (feature.is(TreeFeatures.SUPER_BIRCH_BEES_0002)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_BIRCH; -+ return org.bukkit.TreeType.TALL_BIRCH; - } else if (feature.is(TreeFeatures.SWAMP_OAK)) { +- } else if (feature.is(TreeFeatures.SWAMP_OAK)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.SWAMP; -+ return org.bukkit.TreeType.SWAMP; - } else if (feature.is(TreeFeatures.FANCY_OAK) || feature.is(TreeFeatures.FANCY_OAK_BEES_005)) { +- } else if (feature.is(TreeFeatures.FANCY_OAK) || feature.is(TreeFeatures.FANCY_OAK_BEES_005)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.BIG_TREE; -+ return org.bukkit.TreeType.BIG_TREE; - } else if (feature.is(TreeFeatures.JUNGLE_BUSH)) { +- } else if (feature.is(TreeFeatures.JUNGLE_BUSH)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE_BUSH; -+ return org.bukkit.TreeType.JUNGLE_BUSH; - } else if (feature.is(TreeFeatures.DARK_OAK)) { +- } else if (feature.is(TreeFeatures.DARK_OAK)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.DARK_OAK; -+ return org.bukkit.TreeType.DARK_OAK; - } else if (feature.is(TreeFeatures.MEGA_SPRUCE)) { +- } else if (feature.is(TreeFeatures.MEGA_SPRUCE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_REDWOOD; -+ return org.bukkit.TreeType.MEGA_REDWOOD; - } else if (feature.is(TreeFeatures.MEGA_PINE)) { +- } else if (feature.is(TreeFeatures.MEGA_PINE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MEGA_PINE; -+ return org.bukkit.TreeType.MEGA_PINE; - } else if (feature.is(TreeFeatures.MEGA_JUNGLE_TREE)) { +- } else if (feature.is(TreeFeatures.MEGA_JUNGLE_TREE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.JUNGLE; -+ return org.bukkit.TreeType.JUNGLE; - } else if (feature.is(TreeFeatures.AZALEA_TREE)) { +- } else if (feature.is(TreeFeatures.AZALEA_TREE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.AZALEA; -+ return org.bukkit.TreeType.AZALEA; - } else if (feature.is(TreeFeatures.MANGROVE)) { +- } else if (feature.is(TreeFeatures.MANGROVE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.MANGROVE; -+ return org.bukkit.TreeType.MANGROVE; - } else if (feature.is(TreeFeatures.TALL_MANGROVE)) { +- } else if (feature.is(TreeFeatures.TALL_MANGROVE)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.TALL_MANGROVE; -+ return org.bukkit.TreeType.TALL_MANGROVE; - } else if (feature.is(TreeFeatures.CHERRY) || feature.is(TreeFeatures.CHERRY_BEES_005)) { +- } else if (feature.is(TreeFeatures.CHERRY) || feature.is(TreeFeatures.CHERRY_BEES_005)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.CHERRY; -+ return org.bukkit.TreeType.CHERRY; - } else if (feature.is(TreeFeatures.PALE_OAK) || feature.is(TreeFeatures.PALE_OAK_BONEMEAL)) { +- } else if (feature.is(TreeFeatures.PALE_OAK) || feature.is(TreeFeatures.PALE_OAK_BONEMEAL)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK; -+ return org.bukkit.TreeType.PALE_OAK; - } else if (feature.is(TreeFeatures.PALE_OAK_CREAKING)) { +- } else if (feature.is(TreeFeatures.PALE_OAK_CREAKING)) { - net.minecraft.world.level.block.SaplingBlock.treeType = org.bukkit.TreeType.PALE_OAK_CREAKING; -+ return org.bukkit.TreeType.PALE_OAK_CREAKING; - } else { - throw new IllegalArgumentException("Unknown tree generator " + feature); - } +- } else { +- throw new IllegalArgumentException("Unknown tree generator " + feature); +- } +- } +- // CraftBukkit end + } diff --git a/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/net/minecraft/world/level/block/piston/PistonBaseBlock.java index 3bbfa079a2a2da3de361352738b6101894acf82c..c743c9fb8a829f57758a0c68139bfdf06e4a299f 100644 --- a/net/minecraft/world/level/block/piston/PistonBaseBlock.java diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java b/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java index 0776d3fa45f9..1e2a1c31009c 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java @@ -1,19 +1,85 @@ package io.papermc.paper.util.capture; +import net.minecraft.Optionull; +import net.minecraft.core.Holder; +import net.minecraft.data.worldgen.features.TreeFeatures; import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; import org.bukkit.TreeType; -import org.bukkit.entity.Player; import org.jspecify.annotations.Nullable; public class BoneMealContext { - public ServerLevel ogServerLevel; - public @Nullable ServerPlayer player; + public final ServerLevel originalLevel; + + public @Nullable Player player; public boolean precancelStructureEvent = false; public TreeType treeHook; // just make this a field + public boolean usedBoneMeal; // names are kinda messed + + public BoneMealContext(ServerLevel originalLevel) { + this.originalLevel = originalLevel; + } + + public org.bukkit.entity.@Nullable Player getBukkitPlayer() { + return (org.bukkit.entity.Player) Optionull.map(this.player, Entity::getBukkitEntity); + } - public @Nullable Player getBukkitPlayer() { - return this.player == null ? null : this.player.getBukkitEntity(); + public static TreeType getTreeType(Holder> feature) { + if (feature.is(TreeFeatures.HUGE_RED_MUSHROOM)) { + return TreeType.RED_MUSHROOM; + } else if (feature.is(TreeFeatures.HUGE_BROWN_MUSHROOM)) { + return TreeType.BROWN_MUSHROOM; + } else if (feature.is(TreeFeatures.WARPED_FUNGUS_PLANTED)) { + return TreeType.WARPED_FUNGUS; + } else if (feature.is(TreeFeatures.CRIMSON_FUNGUS_PLANTED)) { + return TreeType.CRIMSON_FUNGUS; + } else if (feature.is(TreeFeatures.OAK) || feature.is(TreeFeatures.OAK_BEES_005)) { + return TreeType.TREE; + } else if (feature.is(TreeFeatures.JUNGLE_TREE)) { + return TreeType.COCOA_TREE; + } else if (feature.is(TreeFeatures.JUNGLE_TREE_NO_VINE)) { + return TreeType.SMALL_JUNGLE; + } else if (feature.is(TreeFeatures.PINE)) { + return TreeType.TALL_REDWOOD; + } else if (feature.is(TreeFeatures.SPRUCE)) { + return TreeType.REDWOOD; + } else if (feature.is(TreeFeatures.ACACIA)) { + return TreeType.ACACIA; + } else if (feature.is(TreeFeatures.BIRCH) || feature.is(TreeFeatures.BIRCH_BEES_005)) { + return TreeType.BIRCH; + } else if (feature.is(TreeFeatures.SUPER_BIRCH_BEES_0002)) { + return TreeType.TALL_BIRCH; + } else if (feature.is(TreeFeatures.SWAMP_OAK)) { + return TreeType.SWAMP; + } else if (feature.is(TreeFeatures.FANCY_OAK) || feature.is(TreeFeatures.FANCY_OAK_BEES_005)) { + return TreeType.BIG_TREE; + } else if (feature.is(TreeFeatures.JUNGLE_BUSH)) { + return TreeType.JUNGLE_BUSH; + } else if (feature.is(TreeFeatures.DARK_OAK)) { + return TreeType.DARK_OAK; + } else if (feature.is(TreeFeatures.MEGA_SPRUCE)) { + return TreeType.MEGA_REDWOOD; + } else if (feature.is(TreeFeatures.MEGA_PINE)) { + return TreeType.MEGA_PINE; + } else if (feature.is(TreeFeatures.MEGA_JUNGLE_TREE)) { + return TreeType.JUNGLE; + } else if (feature.is(TreeFeatures.AZALEA_TREE)) { + return TreeType.AZALEA; + } else if (feature.is(TreeFeatures.MANGROVE)) { + return TreeType.MANGROVE; + } else if (feature.is(TreeFeatures.TALL_MANGROVE)) { + return TreeType.TALL_MANGROVE; + } else if (feature.is(TreeFeatures.CHERRY) || feature.is(TreeFeatures.CHERRY_BEES_005)) { + return TreeType.CHERRY; + } else if (feature.is(TreeFeatures.PALE_OAK) || feature.is(TreeFeatures.PALE_OAK_BONEMEAL)) { + return TreeType.PALE_OAK; + } else if (feature.is(TreeFeatures.PALE_OAK_CREAKING)) { + return TreeType.PALE_OAK_CREAKING; + } else { + throw new IllegalArgumentException("Unknown tree feature: " + feature); + } } } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java index 745efb402164..67704dedcc7f 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java @@ -1,16 +1,18 @@ package io.papermc.paper.util.capture; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Stream; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; -import org.bukkit.Location; +import org.bukkit.craftbukkit.block.CraftBlock; import org.bukkit.craftbukkit.block.CraftBlockStates; -import org.bukkit.craftbukkit.util.CraftLocation; import org.jspecify.annotations.Nullable; /* @@ -80,16 +82,20 @@ public void applyApiPatch(ServerLevel level) { } // TODO: Clean this up - public Map calculateLatestSnapshots(ServerLevel level) { - Map out = new HashMap<>(); + public List calculateLatestSnapshots(ServerLevel level) { + List out = new ArrayList<>(); for (Map.Entry entry : this.recordsByPos.entrySet()) { CaptureRecord captureRecord = entry.getValue(); - out.put(CraftLocation.toBukkit(entry.getKey()), CraftBlockStates.getBlockState(level.getWorld(), entry.getKey(), captureRecord.state, captureRecord.blockEntity)); + out.add(CraftBlockStates.getBlockState(level.getWorld(), entry.getKey(), captureRecord.state, captureRecord.blockEntity)); } return out; } + public Stream getAffectedBlocks(ServerLevel level) { + return this.recordsByPos.keySet().stream().map(pos -> CraftBlock.at(level, pos)); + } + public static class CaptureRecord { private final BlockPos pos; diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java index c6e32da29447..e0f0160d6a0b 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java @@ -4,11 +4,11 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Stream; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Holder; @@ -47,7 +47,6 @@ import net.minecraft.world.phys.Vec3; import net.minecraft.world.ticks.LevelTickAccess; import net.minecraft.world.ticks.ScheduledTick; -import org.bukkit.Location; import org.jspecify.annotations.Nullable; public class MinecraftCaptureBridge implements PaperCapturingWorldLevel { @@ -395,10 +394,14 @@ public void addTask(Consumer level) { return placement.map(BlockPlacementPredictor.BlockEntityPlacement::blockEntity); } - public Map calculateLatestSnapshots(ServerLevel level) { + public List calculateLatestSnapshots(ServerLevel level) { return this.writeLayer.getRecordMap().calculateLatestSnapshots(level); } + public Stream getAffectedBlocks(ServerLevel level) { + return this.writeLayer.getRecordMap().getAffectedBlocks(level); + } + public void applyTasks() { this.sink = Runnable::run; for (Runnable runnable : this.queuedTasks) { diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java b/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java index 5c1f86885256..ed7cf0ea6c20 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java @@ -51,7 +51,7 @@ default WorldConfiguration paperConfig() { @Override default ChunkGenerator getGenerator() { - return this.handle().getGenerator(); + return this.handle().getGenerator(); // todo StackOverflowError when bonemealing a moss block } @Override diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java index be1e12ed2a08..7ab5f21ccaec 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java @@ -1,11 +1,12 @@ package io.papermc.paper.util.capture; -import java.util.Map; +import java.util.List; import java.util.Optional; +import java.util.stream.Stream; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.entity.BlockEntity; -import org.bukkit.Location; +import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.jspecify.annotations.Nullable; @@ -31,10 +32,14 @@ public boolean isCapturing() { return true; } - public Map getCapturedSnapshots() { + public List getCapturedSnapshots() { return this.capturingWorldLevel.calculateLatestSnapshots(this.level); } + public Stream getAffectedBlocks() { + return this.capturingWorldLevel.getAffectedBlocks(this.level); + } + public net.minecraft.world.level.block.state.@Nullable BlockState getOverlayBlockState(BlockPos pos) { return this.capturingWorldLevel.getLatestBlockState(pos); } @@ -51,7 +56,7 @@ public void overlayCaptureOnLevel() { } public boolean isOverlayingCaptureOnLevel() { - return isOverlayingCaptureOnLevel; + return this.isOverlayingCaptureOnLevel; } public void finalizePlacement() { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index c2e0ec4d6e2e..ff8a8cdf7cd4 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -746,9 +746,7 @@ public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate del BlockPos pos = CraftLocation.toBlockPosition(loc); boolean res = this.generateTree(captureTreeGeneration, this.getHandle().getMinecraftWorld().getChunkSource().getGenerator(), pos, new RandomSourceWrapper(CraftWorld.rand), type); if (res) { - Map snapshots = captureTreeGeneration.calculateLatestSnapshots(this.world); - - List blocks = new ArrayList<>(snapshots.values()); + List blocks = captureTreeGeneration.calculateLatestSnapshots(this.world); for (BlockState state : blocks) { delegate.setBlockData(state.getX(), state.getY(), state.getZ(), state.getBlockData()); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 5046ce461db4..6c40533be6b7 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -14,6 +14,7 @@ import io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent; import io.papermc.paper.event.entity.ItemTransportingEntityValidateTargetEvent; import io.papermc.paper.event.player.PlayerBedFailEnterEvent; +import io.papermc.paper.util.capture.BoneMealContext; import io.papermc.paper.util.capture.MinecraftCaptureBridge; import io.papermc.paper.util.capture.PaperCapturingWorldLevel; import io.papermc.paper.util.capture.SimpleBlockCapture; @@ -26,6 +27,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; +import it.unimi.dsi.fastutil.objects.Object2BooleanFunction; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.network.Connection; @@ -497,20 +499,20 @@ public static Stream handleBellResonate return event.getResonatedEntities().stream().map((bukkitEntity) -> ((CraftLivingEntity) bukkitEntity).getHandle()); } - public static BlockMultiPlaceEvent callBlockMultiPlaceEvent(ServerLevel level, net.minecraft.world.entity.player.Player player, InteractionHand hand, List blockStates, BlockPos clickedPos) { + public static BlockMultiPlaceEvent callBlockMultiPlaceEvent(ServerLevel level, net.minecraft.world.entity.player.Player player, InteractionHand hand, List replacedSnapshots, BlockPos clickedPos) { Player cplayer = (Player) player.getBukkitEntity(); Block clickedBlock = CraftBlock.at(level, clickedPos); boolean canBuild = true; - for (BlockState blockState : blockStates) { - if (!CraftEventFactory.canBuild(level, cplayer, blockState.getX(), blockState.getZ())) { + for (BlockState snapshot : replacedSnapshots) { + if (!CraftEventFactory.canBuild(level, cplayer, snapshot.getX(), snapshot.getZ())) { canBuild = false; break; } } EquipmentSlot handSlot = CraftEquipmentSlot.getHand(hand); - BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, clickedBlock, cplayer.getInventory().getItem(handSlot), cplayer, canBuild, handSlot); + BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(replacedSnapshots, clickedBlock, cplayer.getInventory().getItem(handSlot), cplayer, canBuild, handSlot); event.callEvent(); return event; @@ -2387,41 +2389,50 @@ public static boolean sendChestLockedNotifications(Vec3 pos) { return false; } - public static boolean structureEvent(ServerLevel serverLevel, PaperCapturingWorldLevel level, Player player, BlockPos pos, Function worldGenCapture, TreeType type) { + public static boolean structureEvent(PaperCapturingWorldLevel level, BlockPos pos, Function worldGenCapture, BoneMealContext context) { try (SimpleBlockCapture capture = level.forkCaptureSession()) { MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); if (worldGenCapture.apply(captureTreeGeneration)) { - Map snapshots = captureTreeGeneration.calculateLatestSnapshots(serverLevel); - Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, serverLevel); - - List blocks = new ArrayList<>(snapshots.values()); - StructureGrowEvent structureEvent = new StructureGrowEvent(location, type, false, player, blocks); + Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, context.originalLevel); + List blocks = captureTreeGeneration.calculateLatestSnapshots(context.originalLevel); + StructureGrowEvent structureEvent = new StructureGrowEvent(location, context.treeHook, context.usedBoneMeal, context.getBukkitPlayer(), blocks); if (structureEvent.callEvent()) { - capture.finalizePlacement(); + capture.finalizePlacement(); // todo block list is mutable return true; + } else { + context.precancelStructureEvent = true; } + } else { + capture.finalizePlacement(); } } return false; } - public static boolean fertilizeBlock(ServerLevel level, Player player, BlockPos pos, Consumer worldGenCapture, boolean cancelled) { + // todo block list is sometimes empty for trees in both events + public static boolean fertilizedBlock(ServerLevel level, Player player, BlockPos pos, Consumer worldGenCapture, BoneMealContext context) { + return fertilizeBlock(level, player, pos, capture -> { + worldGenCapture.accept(capture); + return true; + }, context); + } + + public static boolean fertilizeBlock(ServerLevel level, Player player, BlockPos pos, Function worldGenCapture, BoneMealContext context) { try (SimpleBlockCapture capture = level.forkCaptureSession()) { MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); + if (worldGenCapture.apply(captureTreeGeneration)) { + List blocks = captureTreeGeneration.calculateLatestSnapshots(level); + BlockFertilizeEvent structureEvent = new BlockFertilizeEvent(CraftBlock.at(level, pos), player, blocks); + structureEvent.setCancelled(context.precancelStructureEvent); - worldGenCapture.accept(captureTreeGeneration); - Map snapshots = captureTreeGeneration.calculateLatestSnapshots(level); - Location location = CraftLocation.toBukkit(pos, level); - - List blocks = new ArrayList<>(snapshots.values()); - BlockFertilizeEvent structureEvent = new BlockFertilizeEvent(location.getBlock(), player, blocks); - structureEvent.setCancelled(cancelled); - - if (structureEvent.callEvent()) { + if (structureEvent.callEvent()) { + capture.finalizePlacement(); // todo block list is mutable + return true; + } + } else { capture.finalizePlacement(); - return true; } } From dbd87471735187ba4032366e8507fe64bc386cea Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Sat, 14 Feb 2026 19:43:00 +0100 Subject: [PATCH 16/17] allow to bonemeal bamboo and moss block --- .../features/0032-Block-Capture-System.patch | 358 ++++++++++-------- .../paper/util/capture/CaptureRecordMap.java | 18 +- ...oneMealContext.java => GrowthContext.java} | 16 +- .../util/capture/LiveBlockPlacementLayer.java | 14 +- .../util/capture/MinecraftCaptureBridge.java | 16 +- .../capture/PaperCapturingWorldLevel.java | 8 +- .../ServerLevelPaperCapturingWorldLevel.java | 30 +- .../util/capture/SimpleBlockCapture.java | 1 - .../SimpleBlockPlacementPredictor.java | 3 +- .../craftbukkit/event/CraftEventFactory.java | 25 +- 10 files changed, 257 insertions(+), 232 deletions(-) rename paper-server/src/main/java/io/papermc/paper/util/capture/{BoneMealContext.java => GrowthContext.java} (89%) diff --git a/paper-server/patches/features/0032-Block-Capture-System.patch b/paper-server/patches/features/0032-Block-Capture-System.patch index 4b042abdb10a..1a1e7f037784 100644 --- a/paper-server/patches/features/0032-Block-Capture-System.patch +++ b/paper-server/patches/features/0032-Block-Capture-System.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Block Capture System diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java -index bfefb5031544caa59230f0073e8880c2b39ebf4d..bab79efd6357c389fbde8425e7073abcd172e2f3 100644 +index bfefb5031544caa59230f0073e8880c2b39ebf4d..244d20e5080051a1e7ac64f4a584454124b06054 100644 --- a/net/minecraft/core/dispenser/DispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java @@ -400,46 +400,21 @@ public interface DispenseItemBehavior { @@ -22,14 +22,14 @@ index bfefb5031544caa59230f0073e8880c2b39ebf4d..bab79efd6357c389fbde8425e7073abc - level.captureTreeGeneration = true; // CraftBukkit - if (!BoneMealItem.growCrop(item, level, blockPos) && !BoneMealItem.growWaterPlant(item, level, blockPos, null)) { + // Paper start -+ io.papermc.paper.util.capture.BoneMealContext boneMealContext = new io.papermc.paper.util.capture.BoneMealContext(level.getMinecraftWorld()); -+ boneMealContext.usedBoneMeal = true; -+ if (!BoneMealItem.growCrop(item, level, blockPos, boneMealContext) && !org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( ++ io.papermc.paper.util.capture.GrowthContext growthContext = new io.papermc.paper.util.capture.GrowthContext(level.getMinecraftWorld()); ++ growthContext.usedBoneMeal = true; ++ if (!BoneMealItem.growCrop(item, level, blockPos, growthContext) && !org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( + (ServerLevel) level, -+ boneMealContext.getBukkitPlayer(), ++ growthContext.getBukkitPlayer(), + blockPos, -+ world -> BoneMealItem.growWaterPlant(item, world, blockPos, null, boneMealContext), -+ boneMealContext ++ world -> BoneMealItem.growWaterPlant(item, world, blockPos, null, growthContext), ++ growthContext + )) { + // Paper end this.setSuccess(false); @@ -67,7 +67,7 @@ index bfefb5031544caa59230f0073e8880c2b39ebf4d..bab79efd6357c389fbde8425e7073abc return item; } diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index dc65503a2d785d64d37b76b0303f51cf66d9769a..1b4ccfbbbebe36656d65facaf87dd7530c415bd6 100644 +index dc65503a2d785d64d37b76b0303f51cf66d9769a..6035304ef0be21d297acc6ca21e3e14c9f61e3ed 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -181,7 +181,7 @@ import net.minecraft.world.waypoints.WaypointTransmitter; @@ -93,21 +93,7 @@ index dc65503a2d785d64d37b76b0303f51cf66d9769a..1b4ccfbbbebe36656d65facaf87dd753 this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, null, orientation); } -@@ -2669,6 +2667,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - return this.randomSequences; - } - -+ // Paper start - block placement capturing -+ @Override -+ public ServerLevel handle() { -+ return this; -+ } -+ // Paper end - block placement capturing -+ - public GameRules getGameRules() { - return this.serverLevelData.getGameRules(); - } -@@ -2683,17 +2688,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe +@@ -2683,17 +2681,6 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe return range <= 0 ? 64.0 * 64.0 : range * range; // 64 is taken from default in ServerLevel#levelEvent } // Paper end - respect global sound events gamerule @@ -311,7 +297,7 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..58ee47c73c4f6a21852cf459de90f532 return false; } else { diff --git a/net/minecraft/world/item/BoneMealItem.java b/net/minecraft/world/item/BoneMealItem.java -index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..168db7a6c623ccb2562eaea699dd21f8f4c7cd2d 100644 +index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..5ec98c831ffa71cf2704be317902a09351233720 100644 --- a/net/minecraft/world/item/BoneMealItem.java +++ b/net/minecraft/world/item/BoneMealItem.java @@ -44,7 +44,12 @@ public class BoneMealItem extends Item { @@ -320,11 +306,11 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..168db7a6c623ccb2562eaea699dd21f8 ItemStack itemInHand = context.getItemInHand(); - if (growCrop(itemInHand, level, clickedPos)) { + // Paper start -+ io.papermc.paper.util.capture.BoneMealContext boneMealContext = new io.papermc.paper.util.capture.BoneMealContext(level.getMinecraftWorld()); -+ boneMealContext.player = context.getPlayer(); -+ boneMealContext.usedBoneMeal = true; -+ // Paper end -+ if (growCrop(itemInHand, level, clickedPos, boneMealContext)) { // Paper ++ io.papermc.paper.util.capture.GrowthContext growthContext = new io.papermc.paper.util.capture.GrowthContext(level.getMinecraftWorld()); ++ growthContext.player = context.getPlayer(); ++ growthContext.usedBoneMeal = true; ++ if (growCrop(itemInHand, level, clickedPos, growthContext)) { ++ // Paper end if (!level.isClientSide()) { if (context.getPlayer() != null) itemInHand.causeUseVibration(context.getPlayer(), GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518 level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, clickedPos, 15); @@ -336,10 +322,10 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..168db7a6c623ccb2562eaea699dd21f8 + // Paper start + boolean result = org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( + (ServerLevel) level, -+ boneMealContext.getBukkitPlayer(), ++ growthContext.getBukkitPlayer(), + blockPos, -+ world -> growWaterPlant(itemInHand, world, blockPos, context.getClickedFace(), boneMealContext), -+ boneMealContext ++ world -> growWaterPlant(itemInHand, world, blockPos, context.getClickedFace(), growthContext), ++ growthContext + ); + if (isFaceSturdy && result) { + // Paper end @@ -351,7 +337,7 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..168db7a6c623ccb2562eaea699dd21f8 } - public static boolean growCrop(ItemStack stack, Level level, BlockPos pos) { -+ public static boolean growCrop(ItemStack stack, Level level, BlockPos pos, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public static boolean growCrop(ItemStack stack, Level level, BlockPos pos, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper BlockState blockState = level.getBlockState(pos); if (blockState.getBlock() instanceof BonemealableBlock bonemealableBlock && bonemealableBlock.isValidBonemealTarget(level, pos, blockState)) { if (level instanceof ServerLevel) { @@ -360,10 +346,10 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..168db7a6c623ccb2562eaea699dd21f8 + // Paper start + org.bukkit.craftbukkit.event.CraftEventFactory.fertilizedBlock( + (ServerLevel) level, -+ mealContext.getBukkitPlayer(), ++ growthContext.getBukkitPlayer(), + pos, -+ world -> bonemealableBlock.performBonemeal(world, level.random, pos, blockState, mealContext), -+ mealContext ++ world -> bonemealableBlock.performBonemeal(world, level.random, pos, blockState, growthContext), ++ growthContext + ); + // Paper end } @@ -374,7 +360,7 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..168db7a6c623ccb2562eaea699dd21f8 } - public static boolean growWaterPlant(ItemStack stack, Level level, BlockPos pos, @Nullable Direction clickedSide) { -+ public static boolean growWaterPlant(ItemStack stack, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, @Nullable Direction clickedSide, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public static boolean growWaterPlant(ItemStack stack, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, @Nullable Direction clickedSide, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper if (level.getBlockState(pos).is(Blocks.WATER) && level.getFluidState(pos).getAmount() == 8) { - if (!(level instanceof ServerLevel)) { + if (false) { // Paper @@ -404,7 +390,7 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..168db7a6c623ccb2562eaea699dd21f8 && ((BonemealableBlock)Blocks.SEAGRASS).isValidBonemealTarget(level, blockPos, blockState1) && random.nextInt(10) == 0) { - ((BonemealableBlock)Blocks.SEAGRASS).performBonemeal((ServerLevel)level, random, blockPos, blockState1); -+ ((BonemealableBlock)Blocks.SEAGRASS).performBonemeal(level, random, blockPos, blockState1, mealContext); // Paper ++ ((BonemealableBlock)Blocks.SEAGRASS).performBonemeal(level, random, blockPos, blockState1, growthContext); // Paper } } } @@ -885,6 +871,28 @@ index 579bbba4e823d4d0318e58759ca732b7c8e4d865..cdebff0efb78b98b941a8aac2cd1115b this.getChunkAt(blockPos).addAndRegisterBlockEntity(blockEntity); } } +diff --git a/net/minecraft/world/level/LevelAccessor.java b/net/minecraft/world/level/LevelAccessor.java +index 21d5042282a4eeaadfbb1057f5995e90acc7388e..bb5fa7b510efe46adfc5b7ffe55c446aaabff11a 100644 +--- a/net/minecraft/world/level/LevelAccessor.java ++++ b/net/minecraft/world/level/LevelAccessor.java +@@ -97,6 +97,4 @@ public interface LevelAccessor extends CommonLevelAccessor, LevelReader, Schedul + default void gameEvent(ResourceKey gameEvent, BlockPos pos, GameEvent.Context context) { + this.gameEvent(this.registryAccess().lookupOrThrow(Registries.GAME_EVENT).getOrThrow(gameEvent), pos, context); + } +- +- net.minecraft.server.level.ServerLevel getMinecraftWorld(); // CraftBukkit + } +diff --git a/net/minecraft/world/level/LevelReader.java b/net/minecraft/world/level/LevelReader.java +index fd5a38a9f24c26f8eca738f78180446d354ff3ac..c21dbae3acabfe541711ea5451c8487c511b0f40 100644 +--- a/net/minecraft/world/level/LevelReader.java ++++ b/net/minecraft/world/level/LevelReader.java +@@ -233,4 +233,6 @@ public interface LevelReader extends ca.spottedleaf.moonrise.patches.chunk_syste + } + + EnvironmentAttributeReader environmentAttributes(); ++ ++ net.minecraft.server.level.ServerLevel getMinecraftWorld(); // CraftBukkit + } diff --git a/net/minecraft/world/level/WorldGenLevel.java b/net/minecraft/world/level/WorldGenLevel.java index d8f3ed6f5e5cbc045399e38664cd062737481f7b..83336865ad6ab30c3494361c5fa6088607126b6b 100644 --- a/net/minecraft/world/level/WorldGenLevel.java @@ -896,7 +904,7 @@ index d8f3ed6f5e5cbc045399e38664cd062737481f7b..83336865ad6ab30c3494361c5fa60886 + } diff --git a/net/minecraft/world/level/block/AzaleaBlock.java b/net/minecraft/world/level/block/AzaleaBlock.java -index 435a455ad2ec3dfb142d570a51a720bc6c49dac3..10363f11fb1b113ba6e6d48ca4d432dfc01f9bff 100644 +index 435a455ad2ec3dfb142d570a51a720bc6c49dac3..b10c72f649a3ba63102afd789d774d5448268f65 100644 --- a/net/minecraft/world/level/block/AzaleaBlock.java +++ b/net/minecraft/world/level/block/AzaleaBlock.java @@ -49,8 +49,8 @@ public class AzaleaBlock extends VegetationBlock implements BonemealableBlock { @@ -905,13 +913,13 @@ index 435a455ad2ec3dfb142d570a51a720bc6c49dac3..10363f11fb1b113ba6e6d48ca4d432df @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { - TreeGrower.AZALEA.growTree(level, level.getChunkSource().getGenerator(), pos, state, random); -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper -+ TreeGrower.AZALEA.growTree(level, level.getChunkSource().getGenerator(), pos, state, random, mealContext); // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper ++ TreeGrower.AZALEA.growTree(level, level.getChunkSource().getGenerator(), pos, state, random, growthContext); // Paper } @Override diff --git a/net/minecraft/world/level/block/BambooSaplingBlock.java b/net/minecraft/world/level/block/BambooSaplingBlock.java -index 88c204ad9d4ead792eb618dbb8611cca863e798c..baf1f1b9e445c5caf2784f73db49ae83cdc7156b 100644 +index 88c204ad9d4ead792eb618dbb8611cca863e798c..c0ce6404a8d31669b0e279eafbe5e434b36b824b 100644 --- a/net/minecraft/world/level/block/BambooSaplingBlock.java +++ b/net/minecraft/world/level/block/BambooSaplingBlock.java @@ -84,11 +84,11 @@ public class BambooSaplingBlock extends Block implements BonemealableBlock { @@ -919,7 +927,7 @@ index 88c204ad9d4ead792eb618dbb8611cca863e798c..baf1f1b9e445c5caf2784f73db49ae83 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper this.growBamboo(level, pos); } @@ -929,18 +937,36 @@ index 88c204ad9d4ead792eb618dbb8611cca863e798c..baf1f1b9e445c5caf2784f73db49ae83 } } diff --git a/net/minecraft/world/level/block/BambooStalkBlock.java b/net/minecraft/world/level/block/BambooStalkBlock.java -index 81e2a279d4c29f5fe52387875489239515a8c82b..6955c72fd5d1af64769a13b69216ef016d349f03 100644 +index 81e2a279d4c29f5fe52387875489239515a8c82b..469f464fe4668e6b65be73a6762a042709e78018 100644 --- a/net/minecraft/world/level/block/BambooStalkBlock.java +++ b/net/minecraft/world/level/block/BambooStalkBlock.java +@@ -157,7 +157,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { + public boolean isValidBonemealTarget(LevelReader level, BlockPos pos, BlockState state) { + int heightAboveUpToMax = this.getHeightAboveUpToMax(level, pos); + int heightBelowUpToMax = this.getHeightBelowUpToMax(level, pos); +- return heightAboveUpToMax + heightBelowUpToMax + 1 < ((Level) level).paperConfig().maxGrowthHeight.bamboo.max && level.getBlockState(pos.above(heightAboveUpToMax)).getValue(STAGE) != 1; // Paper - Configurable cactus/bamboo/reed growth height ++ return heightAboveUpToMax + heightBelowUpToMax + 1 < level.getMinecraftWorld().paperConfig().maxGrowthHeight.bamboo.max && level.getBlockState(pos.above(heightAboveUpToMax)).getValue(STAGE) != 1; // Paper - Configurable cactus/bamboo/reed growth height + } + + @Override @@ -166,7 +166,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { } @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper int heightAboveUpToMax = this.getHeightAboveUpToMax(level, pos); int heightBelowUpToMax = this.getHeightBelowUpToMax(level, pos); int i = heightAboveUpToMax + heightBelowUpToMax + 1; +@@ -175,7 +175,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { + for (int i2 = 0; i2 < i1; i2++) { + BlockPos blockPos = pos.above(heightAboveUpToMax); + BlockState blockState = level.getBlockState(blockPos); +- if (i >= level.paperConfig().maxGrowthHeight.bamboo.max || !blockState.is(Blocks.BAMBOO) || blockState.getValue(BambooStalkBlock.STAGE) == 1 || !level.isEmptyBlock(blockPos.above())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here // Paper - Configurable cactus/bamboo/reed growth height ++ if (i >= level.getMinecraftWorld().paperConfig().maxGrowthHeight.bamboo.max || !blockState.is(Blocks.BAMBOO) || blockState.getValue(BambooStalkBlock.STAGE) == 1 || !level.isEmptyBlock(blockPos.above())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here // Paper - Configurable cactus/bamboo/reed growth height + return; + } + @@ -185,7 +185,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { } } @@ -950,12 +976,37 @@ index 81e2a279d4c29f5fe52387875489239515a8c82b..6955c72fd5d1af64769a13b69216ef01 BlockState blockState = level.getBlockState(pos.below()); BlockPos blockPos = pos.below(2); BlockState blockState1 = level.getBlockState(blockPos); -@@ -221,7 +221,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { - protected int getHeightAboveUpToMax(BlockGetter level, BlockPos pos) { +@@ -207,7 +207,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { + } + + int i = state.getValue(AGE) != 1 && !blockState1.is(Blocks.BAMBOO) ? 0 : 1; +- int i1 = (age < level.paperConfig().maxGrowthHeight.bamboo.min || random.nextFloat() >= 0.25F) && age != (level.paperConfig().maxGrowthHeight.bamboo.max - 1) ? 0 : 1; // Paper - Configurable cactus/bamboo/reed growth height ++ int i1 = (age < level.getMinecraftWorld().paperConfig().maxGrowthHeight.bamboo.min || random.nextFloat() >= 0.25F) && age != (level.getMinecraftWorld().paperConfig().maxGrowthHeight.bamboo.max - 1) ? 0 : 1; // Paper - Configurable cactus/bamboo/reed growth height + // CraftBukkit start + if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, pos.above(), this.defaultBlockState().setValue(AGE, i).setValue(LEAVES, bambooLeaves).setValue(STAGE, i1), Block.UPDATE_ALL)) { + if (shouldUpdateOthers) { +@@ -218,20 +218,20 @@ public class BambooStalkBlock extends Block implements BonemealableBlock { + // CraftBukkit end + } + +- protected int getHeightAboveUpToMax(BlockGetter level, BlockPos pos) { ++ protected int getHeightAboveUpToMax(LevelReader level, BlockPos pos) { // Paper int i = 0; - while (i < ((Level) level).paperConfig().maxGrowthHeight.bamboo.max && level.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO)) { // Paper - Configurable cactus/bamboo/reed growth height -+ while (i < ((Level) level).paperConfig().maxGrowthHeight.bamboo.max && level.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO)) { // Paper - Configurable cactus/bamboo/reed growth height // todo ClassCastException when bonemealing a bamboo ++ while (i < level.getMinecraftWorld().paperConfig().maxGrowthHeight.bamboo.max && level.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO)) { // Paper - Configurable cactus/bamboo/reed growth height + i++; + } + + return i; + } + +- protected int getHeightBelowUpToMax(BlockGetter level, BlockPos pos) { ++ protected int getHeightBelowUpToMax(LevelReader level, BlockPos pos) { // Paper + int i = 0; + +- while (i < ((Level) level).paperConfig().maxGrowthHeight.bamboo.max && level.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO)) { // Paper - Configurable cactus/bamboo/reed growth height ++ while (i < level.getMinecraftWorld().paperConfig().maxGrowthHeight.bamboo.max && level.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO)) { // Paper - Configurable cactus/bamboo/reed growth height i++; } @@ -996,7 +1047,7 @@ index dbc912d514120a33f22959d6dc36ccf6ebc6be80..f27840bd23d51b86f00cac7eede270cc } diff --git a/net/minecraft/world/level/block/BigDripleafBlock.java b/net/minecraft/world/level/block/BigDripleafBlock.java -index c2ef71dd9ee070f80d32b8829b80240ff22f0f7f..70d4c9359c9e3c7c28ad8ae6f2f4a6a180a70299 100644 +index c2ef71dd9ee070f80d32b8829b80240ff22f0f7f..d2324808a9db1ca701becc9a27577ab6f111f987 100644 --- a/net/minecraft/world/level/block/BigDripleafBlock.java +++ b/net/minecraft/world/level/block/BigDripleafBlock.java @@ -177,7 +177,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone @@ -1004,12 +1055,12 @@ index c2ef71dd9ee070f80d32b8829b80240ff22f0f7f..70d4c9359c9e3c7c28ad8ae6f2f4a6a1 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper BlockPos blockPos = pos.above(); BlockState blockState = level.getBlockState(blockPos); if (canPlaceAt(level, blockPos, blockState)) { diff --git a/net/minecraft/world/level/block/BigDripleafStemBlock.java b/net/minecraft/world/level/block/BigDripleafStemBlock.java -index 0ce3e60a94dff03874cad51a936f247d5c3d65ce..40c56db22734c0caf51d53666163b8625368f341 100644 +index 0ce3e60a94dff03874cad51a936f247d5c3d65ce..d5777bfb65d999cb2706c14a26c57f7dfe66a565 100644 --- a/net/minecraft/world/level/block/BigDripleafStemBlock.java +++ b/net/minecraft/world/level/block/BigDripleafStemBlock.java @@ -119,7 +119,7 @@ public class BigDripleafStemBlock extends HorizontalDirectionalBlock implements @@ -1017,7 +1068,7 @@ index 0ce3e60a94dff03874cad51a936f247d5c3d65ce..40c56db22734c0caf51d53666163b862 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper Optional topConnectedBlock = BlockUtil.getTopConnectedBlock(level, pos, state.getBlock(), Direction.UP, Blocks.BIG_DRIPLEAF); if (!topConnectedBlock.isEmpty()) { BlockPos blockPos = topConnectedBlock.get(); @@ -1035,7 +1086,7 @@ index 94b4143449c99ee35db44ab8e2a766d924aa6410..9b9cc245b1fc42d9747de68049189935 public boolean isPossibleToRespawnInThis(BlockState state) { diff --git a/net/minecraft/world/level/block/BonemealableBlock.java b/net/minecraft/world/level/block/BonemealableBlock.java -index 676cc6cb2ea0ac25c4e4bd6a32856f07784c7b49..5eba300c458f2b897b89409eb7d2873efd5974d2 100644 +index 676cc6cb2ea0ac25c4e4bd6a32856f07784c7b49..965e966e44c8091df61b703f4887963181ea2882 100644 --- a/net/minecraft/world/level/block/BonemealableBlock.java +++ b/net/minecraft/world/level/block/BonemealableBlock.java @@ -15,14 +15,20 @@ public interface BonemealableBlock { @@ -1048,7 +1099,7 @@ index 676cc6cb2ea0ac25c4e4bd6a32856f07784c7b49..5eba300c458f2b897b89409eb7d2873e + performBonemeal(level, random, pos, state, null); + } + -+ void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext); ++ void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext); + // Paper end static boolean hasSpreadableNeighbourPos(LevelReader level, BlockPos pos, BlockState state) { @@ -1063,25 +1114,20 @@ index 676cc6cb2ea0ac25c4e4bd6a32856f07784c7b49..5eba300c458f2b897b89409eb7d2873e private static Optional getSpreadableNeighbourPos(List directions, LevelReader level, BlockPos pos, BlockState state) { diff --git a/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java b/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java -index ee701a4c5042aec359271533680d292a6169d4db..a272f5d9f5fe36909e97d7ad10636c6ef98f43bf 100644 +index ee701a4c5042aec359271533680d292a6169d4db..d856dfbd89df890eeabce86618ab2586900206ae 100644 --- a/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java +++ b/net/minecraft/world/level/block/BonemealableFeaturePlacerBlock.java -@@ -41,11 +41,11 @@ public class BonemealableFeaturePlacerBlock extends Block implements Bonemealabl +@@ -41,7 +41,7 @@ public class BonemealableFeaturePlacerBlock extends Block implements Bonemealabl } @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper level.registryAccess() .lookup(Registries.CONFIGURED_FEATURE) .flatMap(registry -> registry.get(this.feature)) -- .ifPresent(reference -> reference.value().place(level, level.getChunkSource().getGenerator(), random, pos.above())); -+ .ifPresent(reference -> reference.value().place(level, level.getGenerator(), random, pos.above())); // Paper - } - - @Override diff --git a/net/minecraft/world/level/block/BushBlock.java b/net/minecraft/world/level/block/BushBlock.java -index 532d2982b520f6f9f7021ec85ef3bcf9bd31e141..52663da1b1413a104c40b44b341fd5e4f1b2a4eb 100644 +index 532d2982b520f6f9f7021ec85ef3bcf9bd31e141..4691a3a75935f1109fca36a85f6d62f3b4a05cab 100644 --- a/net/minecraft/world/level/block/BushBlock.java +++ b/net/minecraft/world/level/block/BushBlock.java @@ -41,7 +41,7 @@ public class BushBlock extends VegetationBlock implements BonemealableBlock { @@ -1089,12 +1135,12 @@ index 532d2982b520f6f9f7021ec85ef3bcf9bd31e141..52663da1b1413a104c40b44b341fd5e4 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper BonemealableBlock.findSpreadableNeighbourPos(level, pos, state).ifPresent(blockPos -> level.setBlockAndUpdate(blockPos, this.defaultBlockState())); } } diff --git a/net/minecraft/world/level/block/CaveVinesBlock.java b/net/minecraft/world/level/block/CaveVinesBlock.java -index f4a4dc14012c110e58b1c9272d80d4b89394d090..053090bcec4a53b707238e634db4683002f10c93 100644 +index f4a4dc14012c110e58b1c9272d80d4b89394d090..84c5e9195e3d1e026691ecf0b0e2dd6fae3a9e91 100644 --- a/net/minecraft/world/level/block/CaveVinesBlock.java +++ b/net/minecraft/world/level/block/CaveVinesBlock.java @@ -89,7 +89,7 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements CaveVines { @@ -1102,12 +1148,12 @@ index f4a4dc14012c110e58b1c9272d80d4b89394d090..053090bcec4a53b707238e634db46830 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper level.setBlock(pos, state.setValue(BERRIES, true), Block.UPDATE_CLIENTS); } } diff --git a/net/minecraft/world/level/block/CaveVinesPlantBlock.java b/net/minecraft/world/level/block/CaveVinesPlantBlock.java -index aba65098fb3202e31b07aa2cee52acc5b671fa95..f2d6f31e007f49d50052fb24efa7fc2eaa92a799 100644 +index aba65098fb3202e31b07aa2cee52acc5b671fa95..9e3620313456a1ef62c5ce922d4ca21b34f880aa 100644 --- a/net/minecraft/world/level/block/CaveVinesPlantBlock.java +++ b/net/minecraft/world/level/block/CaveVinesPlantBlock.java @@ -65,7 +65,7 @@ public class CaveVinesPlantBlock extends GrowingPlantBodyBlock implements CaveVi @@ -1115,12 +1161,12 @@ index aba65098fb3202e31b07aa2cee52acc5b671fa95..f2d6f31e007f49d50052fb24efa7fc2e @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper level.setBlock(pos, state.setValue(BERRIES, true), Block.UPDATE_CLIENTS); } } diff --git a/net/minecraft/world/level/block/CocoaBlock.java b/net/minecraft/world/level/block/CocoaBlock.java -index d4f82e39bb688e43decfb5c8e72fd6db2bfeb571..0b7e4732bc65c8c75ec876eebb238eafed1bb597 100644 +index d4f82e39bb688e43decfb5c8e72fd6db2bfeb571..8f11658f55980ea9f7f15d4892a027b7db1e03f1 100644 --- a/net/minecraft/world/level/block/CocoaBlock.java +++ b/net/minecraft/world/level/block/CocoaBlock.java @@ -114,7 +114,7 @@ public class CocoaBlock extends HorizontalDirectionalBlock implements Bonemealab @@ -1128,7 +1174,7 @@ index d4f82e39bb688e43decfb5c8e72fd6db2bfeb571..0b7e4732bc65c8c75ec876eebb238eaf @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, state.setValue(AGE, state.getValue(AGE) + 1), Block.UPDATE_CLIENTS); // CraftBukkit } @@ -1190,7 +1236,7 @@ index c5fe15844d405a27cdae18c903dd481c25b437de..abb7d98c6608ec1bbed169327114245d level.scheduleTick(pos, this, 4); } diff --git a/net/minecraft/world/level/block/CropBlock.java b/net/minecraft/world/level/block/CropBlock.java -index 1cf40fafd822d976ef4822335c60d8017659916f..171b7f79244bae8aab49cf8b2db6f03662b3d990 100644 +index 1cf40fafd822d976ef4822335c60d8017659916f..f502b8b5d9fd9e0404f1b5d37e0e3ea74de6900a 100644 --- a/net/minecraft/world/level/block/CropBlock.java +++ b/net/minecraft/world/level/block/CropBlock.java @@ -104,13 +104,13 @@ public class CropBlock extends VegetationBlock implements BonemealableBlock { @@ -1215,7 +1261,7 @@ index 1cf40fafd822d976ef4822335c60d8017659916f..171b7f79244bae8aab49cf8b2db6f036 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper this.growCrops(level, pos, state); } @@ -1282,7 +1328,7 @@ index e7dbc14cce1d0ec3f410ae7ebbb4dc47301b5176..449083791b18eb4df10a3117f9802594 level.playSound( null, pos, state.getValue(WATERLOGGED) ? SoundEvents.DRIED_GHAST_PLACE_IN_WATER : SoundEvents.DRIED_GHAST_PLACE, SoundSource.BLOCKS, 1.0F, 1.0F diff --git a/net/minecraft/world/level/block/FireflyBushBlock.java b/net/minecraft/world/level/block/FireflyBushBlock.java -index 635ce3fb2583f6b14355f6137fb6f79dfd959400..c1927d4655d91d556ec9fb734c197a3a33a8ff56 100644 +index 635ce3fb2583f6b14355f6137fb6f79dfd959400..138ae70e1dc348f5b804d90ecb10d04febc5ee10 100644 --- a/net/minecraft/world/level/block/FireflyBushBlock.java +++ b/net/minecraft/world/level/block/FireflyBushBlock.java @@ -58,7 +58,7 @@ public class FireflyBushBlock extends VegetationBlock implements BonemealableBlo @@ -1290,12 +1336,12 @@ index 635ce3fb2583f6b14355f6137fb6f79dfd959400..c1927d4655d91d556ec9fb734c197a3a @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper BonemealableBlock.findSpreadableNeighbourPos(level, pos, state).ifPresent(blockPos -> level.setBlockAndUpdate(blockPos, this.defaultBlockState())); } } diff --git a/net/minecraft/world/level/block/FlowerBedBlock.java b/net/minecraft/world/level/block/FlowerBedBlock.java -index 2a24e71d2b598792ebaa867a2f74fd79e97cc6a2..5f39cc10064c222f9009bdee5ce59bfe3bbae704 100644 +index 2a24e71d2b598792ebaa867a2f74fd79e97cc6a2..291a8a5ceb8f907db6fffa038e02f3959719a66d 100644 --- a/net/minecraft/world/level/block/FlowerBedBlock.java +++ b/net/minecraft/world/level/block/FlowerBedBlock.java @@ -92,12 +92,12 @@ public class FlowerBedBlock extends VegetationBlock implements BonemealableBlock @@ -1303,7 +1349,7 @@ index 2a24e71d2b598792ebaa867a2f74fd79e97cc6a2..5f39cc10064c222f9009bdee5ce59bfe @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper int amountValue = state.getValue(AMOUNT); if (amountValue < 4) { level.setBlock(pos, state.setValue(AMOUNT, amountValue + 1), Block.UPDATE_CLIENTS); @@ -1314,7 +1360,7 @@ index 2a24e71d2b598792ebaa867a2f74fd79e97cc6a2..5f39cc10064c222f9009bdee5ce59bfe } } diff --git a/net/minecraft/world/level/block/FungusBlock.java b/net/minecraft/world/level/block/FungusBlock.java -index 9711efb088bd0da9168e9bcd0496bd7caddd2974..8dbaabce9509b7daec8bbdff5e37cf963f2ae04b 100644 +index 9711efb088bd0da9168e9bcd0496bd7caddd2974..4d09148cdbd81a0a83d0bfcc874c7957fa701329 100644 --- a/net/minecraft/world/level/block/FungusBlock.java +++ b/net/minecraft/world/level/block/FungusBlock.java @@ -71,18 +71,14 @@ public class FungusBlock extends VegetationBlock implements BonemealableBlock { @@ -1335,18 +1381,18 @@ index 9711efb088bd0da9168e9bcd0496bd7caddd2974..8dbaabce9509b7daec8bbdff5e37cf96 - .ifPresent(holder -> holder.value().place(level, level.getChunkSource().getGenerator(), random, pos)); - // CraftBukkit end + // Paper start -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { + this.getFeature(level).ifPresent(holder -> { -+ mealContext.treeHook = io.papermc.paper.util.capture.BoneMealContext.getTreeType(holder); ++ growthContext.feature = holder; + org.bukkit.craftbukkit.event.CraftEventFactory.structureEvent(level, pos, $ -> { + return holder.value().place(level, level.getChunkSource().getGenerator(), random, pos); -+ }, mealContext); ++ }, growthContext); + }); + // Paper end } } diff --git a/net/minecraft/world/level/block/GlowLichenBlock.java b/net/minecraft/world/level/block/GlowLichenBlock.java -index ba39497a6d7160cde961e339be8028ec131b8019..f38447dbc1880878d29e08a18a2db32319291992 100644 +index ba39497a6d7160cde961e339be8028ec131b8019..f6d0940d22f52ff4f465bd9a0978f4626ca2fb4c 100644 --- a/net/minecraft/world/level/block/GlowLichenBlock.java +++ b/net/minecraft/world/level/block/GlowLichenBlock.java @@ -39,7 +39,7 @@ public class GlowLichenBlock extends MultifaceSpreadeableBlock implements Boneme @@ -1354,12 +1400,12 @@ index ba39497a6d7160cde961e339be8028ec131b8019..f38447dbc1880878d29e08a18a2db323 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper this.spreader.spreadFromRandomFaceTowardRandomDirection(state, level, pos, random); } diff --git a/net/minecraft/world/level/block/GrassBlock.java b/net/minecraft/world/level/block/GrassBlock.java -index 368f60ecce691ea161120743150e87b32efc3ca4..8a74e7b53ec7d3c3ad7b71856ef01abef9b98dd7 100644 +index 368f60ecce691ea161120743150e87b32efc3ca4..bfd07348e6f93c6d7acad44b708900628f9471cf 100644 --- a/net/minecraft/world/level/block/GrassBlock.java +++ b/net/minecraft/world/level/block/GrassBlock.java @@ -40,7 +40,7 @@ public class GrassBlock extends SpreadingSnowyDirtBlock implements BonemealableB @@ -1367,7 +1413,7 @@ index 368f60ecce691ea161120743150e87b32efc3ca4..8a74e7b53ec7d3c3ad7b71856ef01abe @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper BlockPos blockPos = pos.above(); BlockState blockState = Blocks.SHORT_GRASS.defaultBlockState(); Optional> optional = level.registryAccess() @@ -1376,12 +1422,12 @@ index 368f60ecce691ea161120743150e87b32efc3ca4..8a74e7b53ec7d3c3ad7b71856ef01abe BonemealableBlock bonemealableBlock = (BonemealableBlock)blockState.getBlock(); if (bonemealableBlock.isValidBonemealTarget(level, blockPos1, blockState1)) { - bonemealableBlock.performBonemeal(level, random, blockPos1, blockState1); -+ bonemealableBlock.performBonemeal(level, random, blockPos1, blockState1, mealContext); // Paper ++ bonemealableBlock.performBonemeal(level, random, blockPos1, blockState1, growthContext); // Paper } } diff --git a/net/minecraft/world/level/block/GrowingPlantBodyBlock.java b/net/minecraft/world/level/block/GrowingPlantBodyBlock.java -index 314d198617e34f91f72a2952a2a62ce0a3b9147d..3ccaf85f148ce738fb122842b2aef5e94aa49cdc 100644 +index 314d198617e34f91f72a2952a2a62ce0a3b9147d..817bd9dafb17817174af118fafae57962e915ac3 100644 --- a/net/minecraft/world/level/block/GrowingPlantBodyBlock.java +++ b/net/minecraft/world/level/block/GrowingPlantBodyBlock.java @@ -74,11 +74,11 @@ public abstract class GrowingPlantBodyBlock extends GrowingPlantBlock implements @@ -1389,17 +1435,17 @@ index 314d198617e34f91f72a2952a2a62ce0a3b9147d..3ccaf85f148ce738fb122842b2aef5e9 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper Optional headPos = this.getHeadPos(level, pos, state.getBlock()); if (headPos.isPresent()) { BlockState blockState = level.getBlockState(headPos.get()); - ((GrowingPlantHeadBlock)blockState.getBlock()).performBonemeal(level, random, headPos.get(), blockState); -+ ((GrowingPlantHeadBlock)blockState.getBlock()).performBonemeal(level, random, headPos.get(), blockState, mealContext); // Paper ++ ((GrowingPlantHeadBlock)blockState.getBlock()).performBonemeal(level, random, headPos.get(), blockState, growthContext); // Paper } } diff --git a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java -index bac7f990282fd7c676c2f8c40d7fd87badb1e284..1a1dcfe8abff794663f3aaeefff693686cc54bce 100644 +index bac7f990282fd7c676c2f8c40d7fd87badb1e284..bd30c47bab6dd4db3143fde72867721f3634fa8a 100644 --- a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java +++ b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java @@ -135,7 +135,7 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements @@ -1407,12 +1453,12 @@ index bac7f990282fd7c676c2f8c40d7fd87badb1e284..1a1dcfe8abff794663f3aaeefff69368 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper BlockPos blockPos = pos.relative(this.growthDirection); int min = Math.min(state.getValue(AGE) + 1, 25); int blocksToGrowWhenBonemealed = this.getBlocksToGrowWhenBonemealed(random); diff --git a/net/minecraft/world/level/block/HangingMossBlock.java b/net/minecraft/world/level/block/HangingMossBlock.java -index 9675e0ad00a4a26c38a7388dc3615e0c33b4e802..a319d8e0a2737723c059a4400a3e62d81c4f0717 100644 +index 9675e0ad00a4a26c38a7388dc3615e0c33b4e802..391fef088a53a2f5433687e164f04d61f5a0b566 100644 --- a/net/minecraft/world/level/block/HangingMossBlock.java +++ b/net/minecraft/world/level/block/HangingMossBlock.java @@ -124,7 +124,7 @@ public class HangingMossBlock extends Block implements BonemealableBlock { @@ -1420,7 +1466,7 @@ index 9675e0ad00a4a26c38a7388dc3615e0c33b4e802..a319d8e0a2737723c059a4400a3e62d8 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper BlockPos blockPos = this.getTip(level, pos).below(); if (this.canGrowInto(level.getBlockState(blockPos))) { level.setBlockAndUpdate(blockPos, state.setValue(TIP, true)); @@ -1438,7 +1484,7 @@ index a4b824dab307705559a76b954a3e47d30dd65889..4fe64b4b445ddceedff0909c917e85d6 TypedEntityData> typedEntityData = stack.get(DataComponents.BLOCK_ENTITY_DATA); if (typedEntityData != null && typedEntityData.contains("RecordItem")) { diff --git a/net/minecraft/world/level/block/MangroveLeavesBlock.java b/net/minecraft/world/level/block/MangroveLeavesBlock.java -index 6d8154821cd17f7529a44eeb16a78caa07529436..5ba0ec6900bd8c0fa9b38f83d8af6ba07d9bada6 100644 +index 6d8154821cd17f7529a44eeb16a78caa07529436..c5427b5de39340f9d3c53ce47c5bffda6d4e9731 100644 --- a/net/minecraft/world/level/block/MangroveLeavesBlock.java +++ b/net/minecraft/world/level/block/MangroveLeavesBlock.java @@ -40,7 +40,7 @@ public class MangroveLeavesBlock extends TintedParticleLeavesBlock implements Bo @@ -1446,12 +1492,12 @@ index 6d8154821cd17f7529a44eeb16a78caa07529436..5ba0ec6900bd8c0fa9b38f83d8af6ba0 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper level.setBlock(pos.below(), MangrovePropaguleBlock.createNewHangingPropagule(), Block.UPDATE_CLIENTS); } diff --git a/net/minecraft/world/level/block/MangrovePropaguleBlock.java b/net/minecraft/world/level/block/MangrovePropaguleBlock.java -index 2fba8534a636491314c760bc338226f6506f0a9a..71320ae181ae72f0803e70190d73627972074e71 100644 +index 2fba8534a636491314c760bc338226f6506f0a9a..1652846681e9a3a906dc0ee34bc7fc85f2bfcb47 100644 --- a/net/minecraft/world/level/block/MangrovePropaguleBlock.java +++ b/net/minecraft/world/level/block/MangrovePropaguleBlock.java @@ -123,11 +123,11 @@ public class MangrovePropaguleBlock extends SaplingBlock implements SimpleWaterl @@ -1459,17 +1505,17 @@ index 2fba8534a636491314c760bc338226f6506f0a9a..71320ae181ae72f0803e70190d736279 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper if (isHanging(state) && !isFullyGrown(state)) { level.setBlock(pos, state.cycle(AGE), Block.UPDATE_CLIENTS); } else { - super.performBonemeal(level, random, pos, state); -+ super.performBonemeal(level, random, pos, state, mealContext); // Paper ++ super.performBonemeal(level, random, pos, state, growthContext); // Paper } } diff --git a/net/minecraft/world/level/block/MossyCarpetBlock.java b/net/minecraft/world/level/block/MossyCarpetBlock.java -index 3762d833f17d596e04845a3f391423f9c14a878b..f5d03f2a3a3866857ddaece77ac4e1273d71c83b 100644 +index 3762d833f17d596e04845a3f391423f9c14a878b..7a10673d12eaddca1caef25f11c0f301a0389c29 100644 --- a/net/minecraft/world/level/block/MossyCarpetBlock.java +++ b/net/minecraft/world/level/block/MossyCarpetBlock.java @@ -176,7 +176,7 @@ public class MossyCarpetBlock extends Block implements BonemealableBlock { @@ -1486,12 +1532,12 @@ index 3762d833f17d596e04845a3f391423f9c14a878b..f5d03f2a3a3866857ddaece77ac4e127 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper BlockState blockState = createTopperWithSideChance(level, pos, () -> true); if (!blockState.isAir()) { level.setBlock(pos.above(), blockState, Block.UPDATE_ALL); diff --git a/net/minecraft/world/level/block/MushroomBlock.java b/net/minecraft/world/level/block/MushroomBlock.java -index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..b2c8c755af383aa5e15cde5207282f7481a60012 100644 +index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..42de0f8830cdb41e94ad38564a85a1b9cd4d78ab 100644 --- a/net/minecraft/world/level/block/MushroomBlock.java +++ b/net/minecraft/world/level/block/MushroomBlock.java @@ -87,14 +87,18 @@ public class MushroomBlock extends VegetationBlock implements BonemealableBlock @@ -1499,7 +1545,7 @@ index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..b2c8c755af383aa5e15cde5207282f74 } - public boolean growMushroom(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) { -+ public boolean growMushroom(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public boolean growMushroom(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper Optional>> optional = level.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).get(this.feature); if (optional.isEmpty()) { return false; @@ -1508,10 +1554,10 @@ index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..b2c8c755af383aa5e15cde5207282f74 - SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? org.bukkit.TreeType.BROWN_MUSHROOM : org.bukkit.TreeType.RED_MUSHROOM; // CraftBukkit - if (optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) { + // Paper start -+ mealContext.treeHook = io.papermc.paper.util.capture.BoneMealContext.getTreeType(optional.get()); ++ growthContext.feature = optional.get(); + if (org.bukkit.craftbukkit.event.CraftEventFactory.structureEvent(level, pos, $ -> { + return optional.get().value().place(level, level.getChunkSource().getGenerator(), random, pos); -+ }, mealContext)) { ++ }, growthContext)) { + // Paper end return true; } else { @@ -1522,12 +1568,12 @@ index b11ff87df7b75f2a3065bbed7b13cc52f7df83fc..b2c8c755af383aa5e15cde5207282f74 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { - this.growMushroom(level, pos, state, random); -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper -+ this.growMushroom(level, pos, state, random, mealContext); // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper ++ this.growMushroom(level, pos, state, random, growthContext); // Paper } } diff --git a/net/minecraft/world/level/block/NetherrackBlock.java b/net/minecraft/world/level/block/NetherrackBlock.java -index 24e166e399bc542522fc832064401ebb6ba2568e..c217cc3fe5fb64c18a7a6df1b72ce7b232b2e65d 100644 +index 24e166e399bc542522fc832064401ebb6ba2568e..f1f6bad412baf259e219eef269baec76b498bb18 100644 --- a/net/minecraft/world/level/block/NetherrackBlock.java +++ b/net/minecraft/world/level/block/NetherrackBlock.java @@ -43,7 +43,7 @@ public class NetherrackBlock extends Block implements BonemealableBlock { @@ -1535,12 +1581,12 @@ index 24e166e399bc542522fc832064401ebb6ba2568e..c217cc3fe5fb64c18a7a6df1b72ce7b2 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper boolean flag = false; boolean flag1 = false; diff --git a/net/minecraft/world/level/block/NyliumBlock.java b/net/minecraft/world/level/block/NyliumBlock.java -index 7357bf27bb0890cefd8f8188a7a326a631cd0ff6..45dd1e6c7baaf3b75340520478ec7b7fd091f604 100644 +index 7357bf27bb0890cefd8f8188a7a326a631cd0ff6..c892e251acf6c2744a4712bcc488ffc5298e2bc4 100644 --- a/net/minecraft/world/level/block/NyliumBlock.java +++ b/net/minecraft/world/level/block/NyliumBlock.java @@ -59,7 +59,7 @@ public class NyliumBlock extends Block implements BonemealableBlock { @@ -1548,7 +1594,7 @@ index 7357bf27bb0890cefd8f8188a7a326a631cd0ff6..45dd1e6c7baaf3b75340520478ec7b7f @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper BlockState blockState = level.getBlockState(pos); BlockPos blockPos = pos.above(); ChunkGenerator generator = level.getChunkSource().getGenerator(); @@ -1562,7 +1608,7 @@ index 7357bf27bb0890cefd8f8188a7a326a631cd0ff6..45dd1e6c7baaf3b75340520478ec7b7f RandomSource random, BlockPos pos diff --git a/net/minecraft/world/level/block/PitcherCropBlock.java b/net/minecraft/world/level/block/PitcherCropBlock.java -index cbaf7ea236e8689793af65f29af3f1860644d152..0f4e092dd8ddc273234fbe87f002ab11bb868283 100644 +index cbaf7ea236e8689793af65f29af3f1860644d152..b21ffb0aaa27c7443437f3b7cdd64a44e0624f6b 100644 --- a/net/minecraft/world/level/block/PitcherCropBlock.java +++ b/net/minecraft/world/level/block/PitcherCropBlock.java @@ -129,7 +129,7 @@ public class PitcherCropBlock extends DoublePlantBlock implements BonemealableBl @@ -1588,12 +1634,12 @@ index cbaf7ea236e8689793af65f29af3f1860644d152..0f4e092dd8ddc273234fbe87f002ab11 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper PitcherCropBlock.PosAndState lowerHalf = this.getLowerHalf(level, pos, state); if (lowerHalf != null) { this.grow(level, lowerHalf.state, lowerHalf.pos, 1); diff --git a/net/minecraft/world/level/block/RootedDirtBlock.java b/net/minecraft/world/level/block/RootedDirtBlock.java -index db6b32016a1ad4264da9d8812e5ea9356d0601df..19000f09efadb6a2c616badfb837487a71168f14 100644 +index db6b32016a1ad4264da9d8812e5ea9356d0601df..a9a2e451e54a616ca5d3e0d47381caba523e07e2 100644 --- a/net/minecraft/world/level/block/RootedDirtBlock.java +++ b/net/minecraft/world/level/block/RootedDirtBlock.java @@ -32,7 +32,7 @@ public class RootedDirtBlock extends Block implements BonemealableBlock { @@ -1601,12 +1647,12 @@ index db6b32016a1ad4264da9d8812e5ea9356d0601df..19000f09efadb6a2c616badfb837487a @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, pos.below(), Blocks.HANGING_ROOTS.defaultBlockState(), Block.UPDATE_ALL); // CraftBukkit } diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java -index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..0c20853ebcad4b0d55577ef361c551eeee4c93d3 100644 +index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..3f2e275e95737eddbe116041620299a84abdd284 100644 --- a/net/minecraft/world/level/block/SaplingBlock.java +++ b/net/minecraft/world/level/block/SaplingBlock.java @@ -25,7 +25,6 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { @@ -1621,13 +1667,12 @@ index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..0c20853ebcad4b0d55577ef361c551ee } } -- public void advanceTree(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) { + // Paper start -+ public void advanceTree(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) { // Paper -+ this.advanceTree(level, pos, state, random, new io.papermc.paper.util.capture.BoneMealContext(level)); + public void advanceTree(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) { ++ this.advanceTree(level, pos, state, random, new io.papermc.paper.util.capture.GrowthContext(level)); + } + -+ public void advanceTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.BoneMealContext mealContext) { ++ public void advanceTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.GrowthContext growthContext) { + // Paper end if (state.getValue(STAGE) == 0) { level.setBlock(pos, state.cycle(STAGE), Block.UPDATE_NONE); @@ -1660,7 +1705,7 @@ index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..0c20853ebcad4b0d55577ef361c551ee - } - } - // CraftBukkit end -+ this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random, mealContext); // Paper ++ this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random, growthContext); // Paper } } @@ -1670,13 +1715,13 @@ index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..0c20853ebcad4b0d55577ef361c551ee @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { - this.advanceTree(level, pos, state, random); -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper -+ this.advanceTree(level, pos, state, random, mealContext); // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper ++ this.advanceTree(level, pos, state, random, growthContext); // Paper } @Override diff --git a/net/minecraft/world/level/block/SeaPickleBlock.java b/net/minecraft/world/level/block/SeaPickleBlock.java -index f59fb2923bbcdf9bc73747b6aa9199fef4c2319f..a69f21db840abd71041d6d1c32dced0c48646fa6 100644 +index f59fb2923bbcdf9bc73747b6aa9199fef4c2319f..bcad45255d3e0b117f62e11ec2f79153306be816 100644 --- a/net/minecraft/world/level/block/SeaPickleBlock.java +++ b/net/minecraft/world/level/block/SeaPickleBlock.java @@ -130,7 +130,7 @@ public class SeaPickleBlock extends VegetationBlock implements BonemealableBlock @@ -1684,12 +1729,12 @@ index f59fb2923bbcdf9bc73747b6aa9199fef4c2319f..a69f21db840abd71041d6d1c32dced0c @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper int i = 5; int i1 = 1; int i2 = 2; diff --git a/net/minecraft/world/level/block/SeagrassBlock.java b/net/minecraft/world/level/block/SeagrassBlock.java -index a41270e041ab408dc6ac1207f7b6ec2eaff161d4..ddef3caad5ee406274734ee422c1f924627222c3 100644 +index a41270e041ab408dc6ac1207f7b6ec2eaff161d4..7694b7900996bd89fbe4ede78f25be737b4fd417 100644 --- a/net/minecraft/world/level/block/SeagrassBlock.java +++ b/net/minecraft/world/level/block/SeagrassBlock.java @@ -87,7 +87,7 @@ public class SeagrassBlock extends VegetationBlock implements BonemealableBlock, @@ -1697,12 +1742,12 @@ index a41270e041ab408dc6ac1207f7b6ec2eaff161d4..ddef3caad5ee406274734ee422c1f924 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper BlockState blockState = Blocks.TALL_SEAGRASS.defaultBlockState(); BlockState blockState1 = blockState.setValue(TallSeagrassBlock.HALF, DoubleBlockHalf.UPPER); BlockPos blockPos = pos.above(); diff --git a/net/minecraft/world/level/block/ShortDryGrassBlock.java b/net/minecraft/world/level/block/ShortDryGrassBlock.java -index 1df47e0ea401267027721342aaf26639b2622e13..a0188c77806f4fe21cffcb43aa83cde6c14442e3 100644 +index 1df47e0ea401267027721342aaf26639b2622e13..910e5424b8f5aa15c665d205afbd38b414d8d346 100644 --- a/net/minecraft/world/level/block/ShortDryGrassBlock.java +++ b/net/minecraft/world/level/block/ShortDryGrassBlock.java @@ -47,7 +47,7 @@ public class ShortDryGrassBlock extends DryVegetationBlock implements Bonemealab @@ -1710,12 +1755,12 @@ index 1df47e0ea401267027721342aaf26639b2622e13..a0188c77806f4fe21cffcb43aa83cde6 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper level.setBlockAndUpdate(pos, Blocks.TALL_DRY_GRASS.defaultBlockState()); } } diff --git a/net/minecraft/world/level/block/SmallDripleafBlock.java b/net/minecraft/world/level/block/SmallDripleafBlock.java -index d5821239d33aad9213b9a87d225e942934623857..a3c8baf014ae2e26f022bff78d4e0e7d6808fa80 100644 +index d5821239d33aad9213b9a87d225e942934623857..7a0ceb68960101f8fb6c2cd34e32372a6bac43af 100644 --- a/net/minecraft/world/level/block/SmallDripleafBlock.java +++ b/net/minecraft/world/level/block/SmallDripleafBlock.java @@ -64,7 +64,7 @@ public class SmallDripleafBlock extends DoublePlantBlock implements Bonemealable @@ -1732,7 +1777,7 @@ index d5821239d33aad9213b9a87d225e942934623857..a3c8baf014ae2e26f022bff78d4e0e7d @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper if (state.getValue(DoublePlantBlock.HALF) == DoubleBlockHalf.LOWER) { BlockPos blockPos = pos.above(); level.setBlock(blockPos, level.getFluidState(blockPos).createLegacyBlock(), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE); @@ -1740,7 +1785,7 @@ index d5821239d33aad9213b9a87d225e942934623857..a3c8baf014ae2e26f022bff78d4e0e7d } else { BlockPos blockPos = pos.below(); - this.performBonemeal(level, random, blockPos, level.getBlockState(blockPos)); -+ this.performBonemeal(level, random, blockPos, level.getBlockState(blockPos), mealContext); // Paper ++ this.performBonemeal(level, random, blockPos, level.getBlockState(blockPos), growthContext); // Paper } } @@ -1759,7 +1804,7 @@ index 298e51da2da64fdaa860108cc34e59d214b5f9a7..86d6a41750e15b0c4c49706ffd827e4f return true; diff --git a/net/minecraft/world/level/block/StemBlock.java b/net/minecraft/world/level/block/StemBlock.java -index 82ea0ce3895108d477d10e901e756a27a3a9cedc..14694eaea6d2f27401c3e656f7e9e9133f215494 100644 +index 82ea0ce3895108d477d10e901e756a27a3a9cedc..dc963acafd12db0784721b3c6545c191c9aac129 100644 --- a/net/minecraft/world/level/block/StemBlock.java +++ b/net/minecraft/world/level/block/StemBlock.java @@ -113,12 +113,12 @@ public class StemBlock extends VegetationBlock implements BonemealableBlock { @@ -1768,7 +1813,7 @@ index 82ea0ce3895108d477d10e901e756a27a3a9cedc..14694eaea6d2f27401c3e656f7e9e913 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { - int min = Math.min(7, state.getValue(AGE) + Mth.nextInt(level.random, 2, 5)); -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper + int min = Math.min(7, state.getValue(AGE) + Mth.nextInt(level.getRandom(), 2, 5)); // Paper BlockState blockState = state.setValue(AGE, min); org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, blockState, Block.UPDATE_CLIENTS); // CraftBukkit @@ -1792,7 +1837,7 @@ index 5b19c117d7935c6b0c3887083f63cc293bc495e3..9fcbe1a3d741852674ed8942e10bc0ca if (placer != null) { BlockEntity blockEntity = level.getBlockEntity(pos); diff --git a/net/minecraft/world/level/block/SweetBerryBushBlock.java b/net/minecraft/world/level/block/SweetBerryBushBlock.java -index 3d2bd187cb9f83f9369d11b21c484c21ceba0569..b6aadb943a4b5c04a39f1700a508c22df0464770 100644 +index 3d2bd187cb9f83f9369d11b21c484c21ceba0569..4712b7faac0612ee85a2c0e88a2c84ba289639c7 100644 --- a/net/minecraft/world/level/block/SweetBerryBushBlock.java +++ b/net/minecraft/world/level/block/SweetBerryBushBlock.java @@ -160,7 +160,7 @@ public class SweetBerryBushBlock extends VegetationBlock implements Bonemealable @@ -1800,12 +1845,12 @@ index 3d2bd187cb9f83f9369d11b21c484c21ceba0569..b6aadb943a4b5c04a39f1700a508c22d @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper int min = Math.min(3, state.getValue(AGE) + 1); level.setBlock(pos, state.setValue(AGE, min), Block.UPDATE_CLIENTS); } diff --git a/net/minecraft/world/level/block/TallDryGrassBlock.java b/net/minecraft/world/level/block/TallDryGrassBlock.java -index f0514cd9df52b3a459ff92812ae5ad9b0df85763..53a8c2130037a1de75f8cc9b63f035f7a62b4bea 100644 +index f0514cd9df52b3a459ff92812ae5ad9b0df85763..7dca920d5702292b01c495a167956616b867cf7f 100644 --- a/net/minecraft/world/level/block/TallDryGrassBlock.java +++ b/net/minecraft/world/level/block/TallDryGrassBlock.java @@ -47,7 +47,7 @@ public class TallDryGrassBlock extends DryVegetationBlock implements Bonemealabl @@ -1813,12 +1858,12 @@ index f0514cd9df52b3a459ff92812ae5ad9b0df85763..53a8c2130037a1de75f8cc9b63f035f7 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper BonemealableBlock.findSpreadableNeighbourPos(level, pos, Blocks.SHORT_DRY_GRASS.defaultBlockState()) .ifPresent(blockPos -> level.setBlockAndUpdate(blockPos, Blocks.SHORT_DRY_GRASS.defaultBlockState())); } diff --git a/net/minecraft/world/level/block/TallFlowerBlock.java b/net/minecraft/world/level/block/TallFlowerBlock.java -index 4be72e53848b13eb68a2149749a88a83a4cb1e5c..6feb3bb080f51904bb119f2a69f9fe3ce511a582 100644 +index 4be72e53848b13eb68a2149749a88a83a4cb1e5c..c3ab67eb1da188b94fc1e2a4072bd08616391865 100644 --- a/net/minecraft/world/level/block/TallFlowerBlock.java +++ b/net/minecraft/world/level/block/TallFlowerBlock.java @@ -33,7 +33,7 @@ public class TallFlowerBlock extends DoublePlantBlock implements BonemealableBlo @@ -1827,12 +1872,12 @@ index 4be72e53848b13eb68a2149749a88a83a4cb1e5c..6feb3bb080f51904bb119f2a69f9fe3c @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { - popResource(level, pos, new ItemStack(this)); -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper + level.addTask((serverLevel) -> popResource(serverLevel, pos, new ItemStack(this))); // Paper } } diff --git a/net/minecraft/world/level/block/TallGrassBlock.java b/net/minecraft/world/level/block/TallGrassBlock.java -index 6c5259371fb8e2e131b2c69f354521b1e902ac4e..195bf95f843f459fab58382e5ceba2077c465dd7 100644 +index 6c5259371fb8e2e131b2c69f354521b1e902ac4e..e7978ffe8702b12e3e1d0370237af76b43aeb89f 100644 --- a/net/minecraft/world/level/block/TallGrassBlock.java +++ b/net/minecraft/world/level/block/TallGrassBlock.java @@ -41,7 +41,7 @@ public class TallGrassBlock extends VegetationBlock implements BonemealableBlock @@ -1840,7 +1885,7 @@ index 6c5259371fb8e2e131b2c69f354521b1e902ac4e..195bf95f843f459fab58382e5ceba207 @Override - public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) { -+ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.BoneMealContext mealContext) { // Paper ++ public void performBonemeal(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, RandomSource random, BlockPos pos, BlockState state, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper DoublePlantBlock.placeAt(level, getGrownBlock(state).defaultBlockState(), pos, Block.UPDATE_CLIENTS); } @@ -1910,7 +1955,7 @@ index c57cb7b47a01e313a1be5de769c6766ac2787d95..f34731580bc880dcc5d3e96923a7f2b4 } } diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java -index 5471619a0484ece08640e2b3fd26746c351dc3e0..6dd4619f3c5d620150f8039282fc8e968b8fa876 100644 +index 5471619a0484ece08640e2b3fd26746c351dc3e0..2ed060c44386121f18d642251d30d9a045962aee 100644 --- a/net/minecraft/world/level/block/grower/TreeGrower.java +++ b/net/minecraft/world/level/block/grower/TreeGrower.java @@ -122,7 +122,34 @@ public final class TreeGrower { @@ -1924,19 +1969,19 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..6dd4619f3c5d620150f8039282fc8e96 + return this.growTree0(level, chunkGenerator, pos, state, random, null); + } + -+ public boolean growTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.BoneMealContext mealContext) { ++ public boolean growTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.GrowthContext growthContext) { + try (io.papermc.paper.util.capture.SimpleBlockCapture capture = level.forkCaptureSession()) { + io.papermc.paper.util.capture.MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); -+ if (this.growTree0(captureTreeGeneration, chunkGenerator, pos, state, random, mealContext)) { -+ org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, mealContext.originalLevel); -+ java.util.List blocks = captureTreeGeneration.calculateLatestSnapshots(mealContext.originalLevel); -+ org.bukkit.event.world.StructureGrowEvent event = new org.bukkit.event.world.StructureGrowEvent(location, mealContext.treeHook, mealContext.usedBoneMeal, mealContext.getBukkitPlayer(), blocks); ++ if (this.growTree0(captureTreeGeneration, chunkGenerator, pos, state, random, growthContext)) { ++ org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, growthContext.originalLevel); ++ java.util.List blocks = captureTreeGeneration.calculateLatestSnapshots(growthContext.originalLevel); ++ org.bukkit.event.world.StructureGrowEvent event = new org.bukkit.event.world.StructureGrowEvent(location, growthContext.getTreeSpecies(), growthContext.usedBoneMeal, growthContext.getBukkitPlayer(), blocks); + + if (event.callEvent()) { + captureTreeGeneration.applyTasks(); // todo block list is mutable + return true; + } else { -+ mealContext.precancelStructureEvent = true; ++ growthContext.cancelledStructureEvent = true; + } + } + } @@ -1944,7 +1989,7 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..6dd4619f3c5d620150f8039282fc8e96 + return false; + } + -+ private boolean growTree0(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.BoneMealContext mealContext) { ++ private boolean growTree0(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.GrowthContext growthContext) { + // Paper end ResourceKey> configuredMegaFeature = this.getConfiguredMegaFeature(random); if (configuredMegaFeature != null) { @@ -1954,7 +1999,7 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..6dd4619f3c5d620150f8039282fc8e96 .orElse(null); if (holder != null) { - this.setTreeType(holder); // CraftBukkit -+ mealContext.treeHook = io.papermc.paper.util.capture.BoneMealContext.getTreeType(holder); // Paper ++ growthContext.feature = holder; // Paper for (int i = 0; i >= -1; i--) { for (int i1 = 0; i1 >= -1; i1--) { if (isTwoByTwoSapling(state, level, pos, i, i1)) { @@ -1963,7 +2008,7 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..6dd4619f3c5d620150f8039282fc8e96 return false; } else { - this.setTreeType(holder1); // CraftBukkit -+ mealContext.treeHook = io.papermc.paper.util.capture.BoneMealContext.getTreeType(holder1); // Paper ++ growthContext.feature = holder1; // Paper ConfiguredFeature configuredFeature2 = holder1.value(); BlockState blockState1 = level.getFluidState(pos).createLegacyBlock(); level.setBlock(pos, blockState1, Block.UPDATE_NONE); @@ -2082,3 +2127,16 @@ index aca28c507cb642ae10c70f8e8393db10c7bf6165..cf5e7cbc7ccb4e749ee74168946dd9fa } @Override +diff --git a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +index 1af585f9554278983148096c73c86e18166f5471..5852c8cc5cf68be92bcd6d9588007c0911743ce4 100644 +--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java ++++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +@@ -432,7 +432,7 @@ public class MapItemSavedData extends SavedData { + return true; + } + +- if (!this.isTrackedCountOverLimit(((Level) level).paperConfig().maps.itemFrameCursorLimit)) { // Paper - Limit item frame cursors on maps ++ if (!this.isTrackedCountOverLimit(level.getMinecraftWorld().paperConfig().maps.itemFrameCursorLimit)) { // Paper - Limit item frame cursors on maps + this.bannerMarkers.put(mapBanner.getId(), mapBanner); + this.addDecoration(mapBanner.getDecoration(), level, mapBanner.getId(), d, d1, 180.0, mapBanner.name().orElse(null)); + this.setDirty(); diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java index 67704dedcc7f..2d7cdd9fb9bf 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/CaptureRecordMap.java @@ -28,13 +28,13 @@ public final class CaptureRecordMap { private final Map recordsByPos = new HashMap<>(); public void setLatestBlockStateAt(BlockPos pos, BlockState state, @Block.UpdateFlags int flags) { - this.add(new CaptureRecord(state, pos, flags)); + this.add(new CaptureRecord(pos, state, flags)); } - public void setLatestBlockEntityAt(BlockPos pos, boolean remove, @Nullable BlockEntity add) { + public void setLatestBlockEntityAt(BlockPos pos, boolean clearPreviousBlockEntity, @Nullable BlockEntity add) { CaptureRecord oldRecord = this.recordsByPos.get(pos); if (oldRecord != null) { - oldRecord.setBlockEntity(remove, add); + oldRecord.setBlockEntity(clearPreviousBlockEntity, add); } } @@ -102,7 +102,7 @@ public static class CaptureRecord { private BlockState state; private @Nullable BlockEntity blockEntity; - private boolean removeBe; + private boolean clearPreviousBlockEntity; private @Block.UpdateFlags int flags; public CaptureRecord(BlockPos pos, BlockState state, BlockEntity blockEntity) { @@ -111,20 +111,20 @@ public CaptureRecord(BlockPos pos, BlockState state, BlockEntity blockEntity) { this.blockEntity = blockEntity; } - public CaptureRecord(BlockState state, BlockPos pos, @Block.UpdateFlags int flags) { + public CaptureRecord(BlockPos pos, BlockState state, @Block.UpdateFlags int flags) { this.pos = pos; this.state = state; this.flags = flags; } public CaptureRecord(boolean remove, @Nullable BlockEntity add, BlockPos pos) { - this.removeBe = remove; + this.clearPreviousBlockEntity = remove; this.blockEntity = add; this.pos = pos; } public void applyApiPatch(ServerLevel level) { - if (this.removeBe) { + if (this.clearPreviousBlockEntity) { level.removeBlockEntity(this.pos); } @@ -134,8 +134,8 @@ public void applyApiPatch(ServerLevel level) { } } - public void setBlockEntity(boolean remove, @Nullable BlockEntity add) { - this.removeBe = remove; + public void setBlockEntity(boolean clearPreviousBlockEntity, @Nullable BlockEntity add) { + this.clearPreviousBlockEntity = clearPreviousBlockEntity; this.blockEntity = add; } } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java b/paper-server/src/main/java/io/papermc/paper/util/capture/GrowthContext.java similarity index 89% rename from paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java rename to paper-server/src/main/java/io/papermc/paper/util/capture/GrowthContext.java index 1e2a1c31009c..10aade5ce0bc 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/BoneMealContext.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/GrowthContext.java @@ -10,16 +10,16 @@ import org.bukkit.TreeType; import org.jspecify.annotations.Nullable; -public class BoneMealContext { +public class GrowthContext { public final ServerLevel originalLevel; public @Nullable Player player; - public boolean precancelStructureEvent = false; - public TreeType treeHook; // just make this a field - public boolean usedBoneMeal; // names are kinda messed + public boolean cancelledStructureEvent = false; + public Holder> feature; // just make this a field + public boolean usedBoneMeal; - public BoneMealContext(ServerLevel originalLevel) { + public GrowthContext(ServerLevel originalLevel) { this.originalLevel = originalLevel; } @@ -27,7 +27,11 @@ public BoneMealContext(ServerLevel originalLevel) { return (org.bukkit.entity.Player) Optionull.map(this.player, Entity::getBukkitEntity); } - public static TreeType getTreeType(Holder> feature) { + public TreeType getTreeSpecies() { + return getTreeSpecies(this.feature); + } + + private static TreeType getTreeSpecies(Holder> feature) { if (feature.is(TreeFeatures.HUGE_RED_MUSHROOM)) { return TreeType.RED_MUSHROOM; } else if (feature.is(TreeFeatures.HUGE_BROWN_MUSHROOM)) { diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java b/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java index 600679921300..9ce7bdcf4b8f 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/LiveBlockPlacementLayer.java @@ -8,16 +8,16 @@ import net.minecraft.world.level.block.state.BlockState; import org.jspecify.annotations.Nullable; -public record LiveBlockPlacementLayer(WorldCapturer level, ServerLevel serverLevel) implements BlockPlacementPredictor { +public record LiveBlockPlacementLayer(WorldCapturer capturer, ServerLevel level) implements BlockPlacementPredictor { @Override public Optional getLatestBlockAt(BlockPos pos) { - return Optional.of(provideLive(() -> this.serverLevel.getBlockState(pos))); + return Optional.of(provideLive(() -> this.level.getBlockState(pos))); } @Override public Optional getLatestBlockAtIfLoaded(BlockPos pos) { - BlockState state = provideLive(() -> this.serverLevel.getBlockStateIfLoaded(pos)); + BlockState state = provideLive(() -> this.level.getBlockStateIfLoaded(pos)); if (state == null) { return LoadedBlockState.UNLOADED; } @@ -27,7 +27,7 @@ public Optional getLatestBlockAtIfLoaded(BlockPos pos) { @Override public Optional getLatestBlockEntityAt(BlockPos pos) { - BlockEntity blockEntity = provideLive(() -> this.serverLevel.getBlockEntity(pos)); + BlockEntity blockEntity = provideLive(() -> this.level.getBlockEntity(pos)); if (blockEntity == null) { return BlockEntityPlacement.ABSENT; } @@ -36,10 +36,10 @@ public Optional getLatestBlockEntityAt(BlockPos pos) { } public @Nullable T provideLive(Supplier<@Nullable T> valueProvider) { - SimpleBlockCapture blockCapture = this.level.getCapture(); - this.level.releaseCapture(null); + SimpleBlockCapture blockCapture = this.capturer.getCapture(); + this.capturer.releaseCapture(null); T value = valueProvider.get(); - this.level.releaseCapture(blockCapture); + this.capturer.releaseCapture(blockCapture); return value; } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java index e0f0160d6a0b..0057e91879a7 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/MinecraftCaptureBridge.java @@ -1,6 +1,5 @@ package io.papermc.paper.util.capture; -import io.papermc.paper.configuration.WorldConfiguration; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -32,7 +31,6 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.border.WorldBorder; import net.minecraft.world.level.chunk.ChunkAccess; -import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.entity.EntityTypeTest; @@ -125,16 +123,6 @@ public boolean setBlockAndUpdate(BlockPos pos, BlockState state) { return this.setBlock(pos, state, Block.UPDATE_ALL); } - @Override - public WorldConfiguration paperConfig() { - return this.parent.paperConfig(); - } - - @Override - public ChunkGenerator getGenerator() { - return this.parent.getGenerator(); - } - @Override public SimpleBlockCapture forkCaptureSession() { return this.parent.capturer.createCaptureSession(new BlockPlacementPredictor() { @@ -178,7 +166,7 @@ public void levelEvent(@Nullable Entity entity, int type, BlockPos pos, int data @Override public void gameEvent(Holder gameEvent, Vec3 pos, GameEvent.Context context) { - this.sink.accept(() -> this.parent.gameEvent(gameEvent, pos, context)); + this.addTask((level) -> level.gameEvent(gameEvent, pos, context)); } @Override @@ -387,7 +375,7 @@ public void addTask(Consumer level) { public @Nullable Optional getLatestBlockEntity(BlockPos pos) { Optional placement = this.effectiveReadLayer.getLatestBlockEntityAt(pos); - if (placement.isEmpty() || placement.get().blockEntity() == null && !placement.get().removed()) { + if (placement.isEmpty() || (placement.get().blockEntity() == null && !placement.get().removed())) { return null; } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java b/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java index a2ca65367a63..71c37888b0a7 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/PaperCapturingWorldLevel.java @@ -1,6 +1,5 @@ package io.papermc.paper.util.capture; -import io.papermc.paper.configuration.WorldConfiguration; import java.util.function.Consumer; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerChunkCache; @@ -9,15 +8,12 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.gamerules.GameRules; public interface PaperCapturingWorldLevel extends WorldGenLevel { GameRules getGameRules(); - void addTask(Consumer level); - void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, @Block.UpdateFlags int flags); void setBlockEntity(BlockEntity blockEntity); @@ -28,9 +24,7 @@ public interface PaperCapturingWorldLevel extends WorldGenLevel { boolean setBlockAndUpdate(BlockPos pos, BlockState state); - WorldConfiguration paperConfig(); - - ChunkGenerator getGenerator(); + void addTask(Consumer level); SimpleBlockCapture forkCaptureSession(); } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java b/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java index ed7cf0ea6c20..954cdff63d2b 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/ServerLevelPaperCapturingWorldLevel.java @@ -1,61 +1,47 @@ package io.papermc.paper.util.capture; -import io.papermc.paper.configuration.WorldConfiguration; import java.util.function.Consumer; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.gamerules.GameRules; public interface ServerLevelPaperCapturingWorldLevel extends PaperCapturingWorldLevel { - ServerLevel handle(); - @Override default GameRules getGameRules() { - return this.handle().getGameRules(); - } - - @Override - default void addTask(Consumer level) { - level.accept(this.handle()); + return this.getLevel().getGameRules(); } @Override default void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, @Block.UpdateFlags int flags) { - this.handle().sendBlockUpdated(pos, oldState, newState, flags); + this.getLevel().sendBlockUpdated(pos, oldState, newState, flags); } @Override default void setBlockEntity(BlockEntity blockEntity) { - this.handle().setBlockEntity(blockEntity); + this.getLevel().setBlockEntity(blockEntity); } @Override default boolean setBlockSilent(BlockPos pos, BlockState state, @Block.UpdateFlags int flags, int recursionLeft) { - return this.handle().setBlock(pos, state, flags, recursionLeft); + return this.getLevel().setBlock(pos, state, flags, recursionLeft); } @Override default boolean setBlockAndUpdate(BlockPos pos, BlockState state) { - return this.handle().setBlockAndUpdate(pos, state); + return this.getLevel().setBlockAndUpdate(pos, state); } @Override - default WorldConfiguration paperConfig() { - return this.handle().paperConfig(); - } - - @Override - default ChunkGenerator getGenerator() { - return this.handle().getGenerator(); // todo StackOverflowError when bonemealing a moss block + default void addTask(Consumer level) { + level.accept(this.getLevel()); } @Override default SimpleBlockCapture forkCaptureSession() { - return this.handle().capturer.createCaptureSession(); + return this.getLevel().capturer.createCaptureSession(); } } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java index 7ab5f21ccaec..a86ff96620f7 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockCapture.java @@ -48,7 +48,6 @@ public Stream getAffectedBlocks() { return this.capturingWorldLevel.getLatestBlockEntity(pos); } - // This is done so that the captured blocks appear ontop of the world. public void overlayCaptureOnLevel() { this.isOverlayingCaptureOnLevel = true; diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java index 7b1f59171ce5..a7575cd94243 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/SimpleBlockPlacementPredictor.java @@ -15,7 +15,7 @@ class SimpleBlockPlacementPredictor implements BlockPlacementPredictor { public boolean setBlockState(BlockPlacementPredictor layer, BlockPos pos, BlockState state, @Block.UpdateFlags int flags) { BlockState blockState = layer.getLatestBlockAt(pos).orElse(Blocks.AIR.defaultBlockState()); - // Dont do any processing if the same + // Don't do any processing if the same if (blockState == state) { return false; } else { @@ -36,7 +36,6 @@ public boolean setBlockState(BlockPlacementPredictor layer, BlockPos pos, BlockS this.removeBlockEntity(pos); } - // if ((differentState || block instanceof BaseRailBlock) && ((flags & Block.UPDATE_NEIGHBORS) != 0 || updateMoveByPiston)) { // BlockState finalBlockState = blockState; // this.capturingWorldLevel.addTask((level) -> { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 6c40533be6b7..504204cb018a 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -14,7 +14,7 @@ import io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent; import io.papermc.paper.event.entity.ItemTransportingEntityValidateTargetEvent; import io.papermc.paper.event.player.PlayerBedFailEnterEvent; -import io.papermc.paper.util.capture.BoneMealContext; +import io.papermc.paper.util.capture.GrowthContext; import io.papermc.paper.util.capture.MinecraftCaptureBridge; import io.papermc.paper.util.capture.PaperCapturingWorldLevel; import io.papermc.paper.util.capture.SimpleBlockCapture; @@ -27,7 +27,6 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; -import it.unimi.dsi.fastutil.objects.Object2BooleanFunction; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.network.Connection; @@ -68,7 +67,6 @@ import net.minecraft.world.level.Explosion; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.WorldGenLevel; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.SignBlockEntity; import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; @@ -86,7 +84,6 @@ import org.bukkit.Material; import org.bukkit.PortalType; import org.bukkit.Statistic.Type; -import org.bukkit.TreeType; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; @@ -2389,19 +2386,19 @@ public static boolean sendChestLockedNotifications(Vec3 pos) { return false; } - public static boolean structureEvent(PaperCapturingWorldLevel level, BlockPos pos, Function worldGenCapture, BoneMealContext context) { + public static boolean structureEvent(PaperCapturingWorldLevel level, BlockPos pos, Function worldGenCapture, GrowthContext context) { try (SimpleBlockCapture capture = level.forkCaptureSession()) { MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); if (worldGenCapture.apply(captureTreeGeneration)) { - Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, context.originalLevel); + Location location = CraftLocation.toBukkit(pos, context.originalLevel); List blocks = captureTreeGeneration.calculateLatestSnapshots(context.originalLevel); - StructureGrowEvent structureEvent = new StructureGrowEvent(location, context.treeHook, context.usedBoneMeal, context.getBukkitPlayer(), blocks); + StructureGrowEvent event = new StructureGrowEvent(location, context.getTreeSpecies(), context.usedBoneMeal, context.getBukkitPlayer(), blocks); - if (structureEvent.callEvent()) { + if (event.callEvent()) { capture.finalizePlacement(); // todo block list is mutable return true; } else { - context.precancelStructureEvent = true; + context.cancelledStructureEvent = true; } } else { capture.finalizePlacement(); @@ -2412,22 +2409,22 @@ public static boolean structureEvent(PaperCapturingWorldLevel level, BlockPos po } // todo block list is sometimes empty for trees in both events - public static boolean fertilizedBlock(ServerLevel level, Player player, BlockPos pos, Consumer worldGenCapture, BoneMealContext context) { + public static boolean fertilizedBlock(ServerLevel level, Player player, BlockPos pos, Consumer worldGenCapture, GrowthContext context) { return fertilizeBlock(level, player, pos, capture -> { worldGenCapture.accept(capture); return true; }, context); } - public static boolean fertilizeBlock(ServerLevel level, Player player, BlockPos pos, Function worldGenCapture, BoneMealContext context) { + public static boolean fertilizeBlock(ServerLevel level, Player player, BlockPos pos, Function worldGenCapture, GrowthContext context) { try (SimpleBlockCapture capture = level.forkCaptureSession()) { MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); if (worldGenCapture.apply(captureTreeGeneration)) { List blocks = captureTreeGeneration.calculateLatestSnapshots(level); - BlockFertilizeEvent structureEvent = new BlockFertilizeEvent(CraftBlock.at(level, pos), player, blocks); - structureEvent.setCancelled(context.precancelStructureEvent); + BlockFertilizeEvent event = new BlockFertilizeEvent(CraftBlock.at(level, pos), player, blocks); + event.setCancelled(context.cancelledStructureEvent); - if (structureEvent.callEvent()) { + if (event.callEvent()) { capture.finalizePlacement(); // todo block list is mutable return true; } From 4731020c9d038bac2cea0dcefbbb66cf892d9139 Mon Sep 17 00:00:00 2001 From: Lulu13022002 <41980282+Lulu13022002@users.noreply.github.com> Date: Sun, 15 Feb 2026 19:51:24 +0100 Subject: [PATCH 17/17] only grow water plant on solid blocks, prevent item removal when event is cancelled --- .../features/0032-Block-Capture-System.patch | 83 +++++++++--------- .../paper/util/capture/GrowthContext.java | 27 ++++-- .../craftbukkit/block/CraftBlockState.java | 11 --- .../craftbukkit/event/CraftEventFactory.java | 28 +++--- .../io/papermc/testplugin/OwenPlayground.java | 86 +++++++++++++++++++ .../io/papermc/testplugin/TestPlugin.java | 86 +------------------ 6 files changed, 163 insertions(+), 158 deletions(-) create mode 100644 test-plugin/src/main/java/io/papermc/testplugin/OwenPlayground.java diff --git a/paper-server/patches/features/0032-Block-Capture-System.patch b/paper-server/patches/features/0032-Block-Capture-System.patch index 1a1e7f037784..2785e9de6995 100644 --- a/paper-server/patches/features/0032-Block-Capture-System.patch +++ b/paper-server/patches/features/0032-Block-Capture-System.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Block Capture System diff --git a/net/minecraft/core/dispenser/DispenseItemBehavior.java b/net/minecraft/core/dispenser/DispenseItemBehavior.java -index bfefb5031544caa59230f0073e8880c2b39ebf4d..244d20e5080051a1e7ac64f4a584454124b06054 100644 +index bfefb5031544caa59230f0073e8880c2b39ebf4d..d7ea3cfb013c503fe903f64df6d6cd61dd0cd4a7 100644 --- a/net/minecraft/core/dispenser/DispenseItemBehavior.java +++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -400,46 +400,21 @@ public interface DispenseItemBehavior { +@@ -400,46 +400,19 @@ public interface DispenseItemBehavior { this.setSuccess(true); Level level = blockSource.level(); BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING)); @@ -22,11 +22,9 @@ index bfefb5031544caa59230f0073e8880c2b39ebf4d..244d20e5080051a1e7ac64f4a5844541 - level.captureTreeGeneration = true; // CraftBukkit - if (!BoneMealItem.growCrop(item, level, blockPos) && !BoneMealItem.growWaterPlant(item, level, blockPos, null)) { + // Paper start -+ io.papermc.paper.util.capture.GrowthContext growthContext = new io.papermc.paper.util.capture.GrowthContext(level.getMinecraftWorld()); -+ growthContext.usedBoneMeal = true; ++ io.papermc.paper.util.capture.GrowthContext growthContext = io.papermc.paper.util.capture.GrowthContext.usingBoneMeal(null); + if (!BoneMealItem.growCrop(item, level, blockPos, growthContext) && !org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( + (ServerLevel) level, -+ growthContext.getBukkitPlayer(), + blockPos, + world -> BoneMealItem.growWaterPlant(item, world, blockPos, null, growthContext), + growthContext @@ -112,7 +110,7 @@ index dc65503a2d785d64d37b76b0303f51cf66d9769a..6035304ef0be21d297acc6ca21e3e14c @Override public CrashReportCategory fillReportDetails(CrashReport report) { diff --git a/net/minecraft/world/entity/ai/behavior/UseBonemeal.java b/net/minecraft/world/entity/ai/behavior/UseBonemeal.java -index d815149ba20d815ec11fd7d4c203aa407e7aa8b2..67118f27b9546c0e6afcf9e4e1dedacc90445774 100644 +index d815149ba20d815ec11fd7d4c203aa407e7aa8b2..01c50bddfe74d927bfbaa94d302fd21ca13cce5d 100644 --- a/net/minecraft/world/entity/ai/behavior/UseBonemeal.java +++ b/net/minecraft/world/entity/ai/behavior/UseBonemeal.java @@ -113,7 +113,7 @@ public class UseBonemeal extends Behavior { @@ -120,7 +118,7 @@ index d815149ba20d815ec11fd7d4c203aa407e7aa8b2..67118f27b9546c0e6afcf9e4e1dedacc } - if (!itemStack.isEmpty() && BoneMealItem.growCrop(itemStack, level, blockPos)) { -+ if (!itemStack.isEmpty() && BoneMealItem.growCrop(itemStack, level, blockPos, null)) { // Paper ++ if (!itemStack.isEmpty() && BoneMealItem.growCrop(itemStack, level, blockPos, io.papermc.paper.util.capture.GrowthContext.empty())) { // Paper level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, blockPos, 15); this.cropPos = this.pickNextTarget(level, owner); this.setCurrentCropAsTarget(owner); @@ -297,42 +295,38 @@ index 73ce7c82c0bd28c2e43ca40ba35c4603b21375ad..58ee47c73c4f6a21852cf459de90f532 return false; } else { diff --git a/net/minecraft/world/item/BoneMealItem.java b/net/minecraft/world/item/BoneMealItem.java -index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..5ec98c831ffa71cf2704be317902a09351233720 100644 +index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..f1706143f27a3ea8e24d2780954beb3ee7fc0785 100644 --- a/net/minecraft/world/item/BoneMealItem.java +++ b/net/minecraft/world/item/BoneMealItem.java -@@ -44,7 +44,12 @@ public class BoneMealItem extends Item { +@@ -44,7 +44,10 @@ public class BoneMealItem extends Item { BlockPos clickedPos = context.getClickedPos(); BlockPos blockPos = clickedPos.relative(context.getClickedFace()); ItemStack itemInHand = context.getItemInHand(); - if (growCrop(itemInHand, level, clickedPos)) { + // Paper start -+ io.papermc.paper.util.capture.GrowthContext growthContext = new io.papermc.paper.util.capture.GrowthContext(level.getMinecraftWorld()); -+ growthContext.player = context.getPlayer(); -+ growthContext.usedBoneMeal = true; ++ io.papermc.paper.util.capture.GrowthContext growthContext = io.papermc.paper.util.capture.GrowthContext.usingBoneMeal(context.getPlayer()); + if (growCrop(itemInHand, level, clickedPos, growthContext)) { + // Paper end if (!level.isClientSide()) { if (context.getPlayer() != null) itemInHand.causeUseVibration(context.getPlayer(), GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518 level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, clickedPos, 15); -@@ -54,7 +59,16 @@ public class BoneMealItem extends Item { +@@ -54,7 +57,14 @@ public class BoneMealItem extends Item { } else { BlockState blockState = level.getBlockState(clickedPos); boolean isFaceSturdy = blockState.isFaceSturdy(level, clickedPos, context.getClickedFace()); - if (isFaceSturdy && growWaterPlant(itemInHand, level, blockPos, context.getClickedFace())) { + // Paper start -+ boolean result = org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( ++ if (isFaceSturdy && org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( + (ServerLevel) level, -+ growthContext.getBukkitPlayer(), + blockPos, + world -> growWaterPlant(itemInHand, world, blockPos, context.getClickedFace(), growthContext), + growthContext -+ ); -+ if (isFaceSturdy && result) { ++ )) { + // Paper end if (!level.isClientSide()) { if (context.getPlayer() != null) itemInHand.causeUseVibration(context.getPlayer(), GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518 level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, blockPos, 15); -@@ -67,12 +81,20 @@ public class BoneMealItem extends Item { +@@ -67,12 +77,24 @@ public class BoneMealItem extends Item { } } @@ -344,13 +338,17 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..5ec98c831ffa71cf2704be317902a093 if (bonemealableBlock.isBonemealSuccess(level, level.random, pos, blockState)) { - bonemealableBlock.performBonemeal((ServerLevel)level, level.random, pos, blockState); + // Paper start -+ org.bukkit.craftbukkit.event.CraftEventFactory.fertilizedBlock( ++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.fertilizeBlock( + (ServerLevel) level, -+ growthContext.getBukkitPlayer(), + pos, -+ world -> bonemealableBlock.performBonemeal(world, level.random, pos, blockState, growthContext), ++ world -> { ++ bonemealableBlock.performBonemeal(world, level.random, pos, blockState, growthContext); ++ return true; ++ }, + growthContext -+ ); ++ )) { ++ return false; ++ } + // Paper end } @@ -363,7 +361,7 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..5ec98c831ffa71cf2704be317902a093 + public static boolean growWaterPlant(ItemStack stack, io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, @Nullable Direction clickedSide, io.papermc.paper.util.capture.GrowthContext growthContext) { // Paper if (level.getBlockState(pos).is(Blocks.WATER) && level.getFluidState(pos).getAmount() == 8) { - if (!(level instanceof ServerLevel)) { -+ if (false) { // Paper ++ if (false && !(level instanceof ServerLevel)) { // Paper return true; } else { RandomSource random = level.getRandom(); @@ -385,7 +383,7 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..5ec98c831ffa71cf2704be317902a093 .map(holder -> holder.value().defaultBlockState()) .orElse(blockState); } -@@ -134,7 +156,7 @@ public class BoneMealItem extends Item { +@@ -134,12 +156,12 @@ public class BoneMealItem extends Item { } else if (blockState1.is(Blocks.SEAGRASS) && ((BonemealableBlock)Blocks.SEAGRASS).isValidBonemealTarget(level, blockPos, blockState1) && random.nextInt(10) == 0) { @@ -394,6 +392,12 @@ index 4490e50f02d2c7383e86951cb98a113d5d4f36c2..5ec98c831ffa71cf2704be317902a093 } } } + +- stack.shrink(1); ++ level.addTask($ -> stack.shrink(1)); // Paper + return true; + } + } else { diff --git a/net/minecraft/world/item/DoubleHighBlockItem.java b/net/minecraft/world/item/DoubleHighBlockItem.java index e6406e4d7ed3c340e3e3137165e9a2fa7e4f3656..6b245cf238851900c409fa2f0c885c6ebee69a91 100644 --- a/net/minecraft/world/item/DoubleHighBlockItem.java @@ -586,7 +590,7 @@ index ed06cffe8a5eba2ca4a34ade81f8185e21d7b651..25a094bc33546bc71bbe4ad348bd954a return interactionResult; } diff --git a/net/minecraft/world/item/SignItem.java b/net/minecraft/world/item/SignItem.java -index 3a41cf80e347ae3b30858879fb91f719625c8bb6..334f30499b8e043bf74ebe7001ded66c42a9aa03 100644 +index 3a41cf80e347ae3b30858879fb91f719625c8bb6..15c5cea7371350115c4d53414cf3f38e66a124f9 100644 --- a/net/minecraft/world/item/SignItem.java +++ b/net/minecraft/world/item/SignItem.java @@ -11,7 +11,7 @@ import net.minecraft.world.level.block.state.BlockState; @@ -614,7 +618,7 @@ index 3a41cf80e347ae3b30858879fb91f719625c8bb6..334f30499b8e043bf74ebe7001ded66c - // signBlock.openTextEdit(player, signBlockEntity, true); - SignItem.openSign = pos; - // CraftBukkit end -+ level.addTask((serverLevel) -> signBlock.openTextEdit(player, signBlockEntity, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE)); // Paper ++ level.addTask($ -> signBlock.openTextEdit(player, signBlockEntity, true, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.PLACE)); // Paper } return flag; @@ -1179,7 +1183,7 @@ index d4f82e39bb688e43decfb5c8e72fd6db2bfeb571..8f11658f55980ea9f7f15d4892a027b7 } diff --git a/net/minecraft/world/level/block/CommandBlock.java b/net/minecraft/world/level/block/CommandBlock.java -index b5a780a929c2b6db91d7f86d7178419f87815944..fac31906af9090541cdd231c1a42725b069049ec 100644 +index b5a780a929c2b6db91d7f86d7178419f87815944..aa7a3e7304aef32424d066d6c49e354e33ba1bd2 100644 --- a/net/minecraft/world/level/block/CommandBlock.java +++ b/net/minecraft/world/level/block/CommandBlock.java @@ -63,12 +63,12 @@ public class CommandBlock extends BaseEntityBlock implements GameMasterBlock { @@ -1215,7 +1219,7 @@ index b5a780a929c2b6db91d7f86d7178419f87815944..fac31906af9090541cdd231c1a42725b if (level.getBlockEntity(pos) instanceof CommandBlockEntity commandBlockEntity) { BaseCommandBlock commandBlock = commandBlockEntity.getCommandBlock(); - if (level instanceof ServerLevel serverLevel) { -+ if (true) { // Paper - block placement capturing ++ if (true || level instanceof ServerLevel serverLevel) { // Paper - block placement capturing if (!stack.has(DataComponents.BLOCK_ENTITY_DATA)) { - commandBlock.setTrackOutput(serverLevel.getGameRules().get(GameRules.SEND_COMMAND_FEEDBACK)); + commandBlock.setTrackOutput(level.getGameRules().get(GameRules.SEND_COMMAND_FEEDBACK)); // Paper - block placement capturing @@ -1652,7 +1656,7 @@ index db6b32016a1ad4264da9d8812e5ea9356d0601df..a9a2e451e54a616ca5d3e0d47381caba } diff --git a/net/minecraft/world/level/block/SaplingBlock.java b/net/minecraft/world/level/block/SaplingBlock.java -index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..3f2e275e95737eddbe116041620299a84abdd284 100644 +index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..4155c1eb68e0521e452b070f43d7cb6d4216a31b 100644 --- a/net/minecraft/world/level/block/SaplingBlock.java +++ b/net/minecraft/world/level/block/SaplingBlock.java @@ -25,7 +25,6 @@ public class SaplingBlock extends VegetationBlock implements BonemealableBlock { @@ -1669,7 +1673,7 @@ index 23e9e5e7ef76fe3d6e1bbc41faf69ee65ca77d80..3f2e275e95737eddbe116041620299a8 + // Paper start public void advanceTree(ServerLevel level, BlockPos pos, BlockState state, RandomSource random) { -+ this.advanceTree(level, pos, state, random, new io.papermc.paper.util.capture.GrowthContext(level)); ++ this.advanceTree(level, pos, state, random, io.papermc.paper.util.capture.GrowthContext.empty()); + } + + public void advanceTree(io.papermc.paper.util.capture.PaperCapturingWorldLevel level, BlockPos pos, BlockState state, RandomSource random, io.papermc.paper.util.capture.GrowthContext growthContext) { @@ -1955,10 +1959,10 @@ index c57cb7b47a01e313a1be5de769c6766ac2787d95..f34731580bc880dcc5d3e96923a7f2b4 } } diff --git a/net/minecraft/world/level/block/grower/TreeGrower.java b/net/minecraft/world/level/block/grower/TreeGrower.java -index 5471619a0484ece08640e2b3fd26746c351dc3e0..2ed060c44386121f18d642251d30d9a045962aee 100644 +index 5471619a0484ece08640e2b3fd26746c351dc3e0..ac5b2e9b10bed4351afdb58c440e1ec72d4b11b5 100644 --- a/net/minecraft/world/level/block/grower/TreeGrower.java +++ b/net/minecraft/world/level/block/grower/TreeGrower.java -@@ -122,7 +122,34 @@ public final class TreeGrower { +@@ -122,7 +122,35 @@ public final class TreeGrower { return this.secondaryMegaTree.isPresent() && random.nextFloat() < this.secondaryChance ? this.secondaryMegaTree.get() : this.megaTree.orElse(null); } @@ -1973,15 +1977,16 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..2ed060c44386121f18d642251d30d9a0 + try (io.papermc.paper.util.capture.SimpleBlockCapture capture = level.forkCaptureSession()) { + io.papermc.paper.util.capture.MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); + if (this.growTree0(captureTreeGeneration, chunkGenerator, pos, state, random, growthContext)) { -+ org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, growthContext.originalLevel); -+ java.util.List blocks = captureTreeGeneration.calculateLatestSnapshots(growthContext.originalLevel); -+ org.bukkit.event.world.StructureGrowEvent event = new org.bukkit.event.world.StructureGrowEvent(location, growthContext.getTreeSpecies(), growthContext.usedBoneMeal, growthContext.getBukkitPlayer(), blocks); ++ org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level.getLevel()); ++ java.util.List blocks = captureTreeGeneration.calculateLatestSnapshots(level.getLevel()); ++ org.bukkit.event.world.StructureGrowEvent event = new org.bukkit.event.world.StructureGrowEvent(location, growthContext.getTreeSpecies(), growthContext.usedBoneMeal(), growthContext.getBukkitPlayer(), blocks); ++ event.setCancelled(growthContext.cancelled); + + if (event.callEvent()) { + captureTreeGeneration.applyTasks(); // todo block list is mutable + return true; + } else { -+ growthContext.cancelledStructureEvent = true; ++ growthContext.cancelled = true; + } + } + } @@ -1994,7 +1999,7 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..2ed060c44386121f18d642251d30d9a0 ResourceKey> configuredMegaFeature = this.getConfiguredMegaFeature(random); if (configuredMegaFeature != null) { Holder> holder = level.registryAccess() -@@ -130,7 +157,7 @@ public final class TreeGrower { +@@ -130,7 +158,7 @@ public final class TreeGrower { .get(configuredMegaFeature) .orElse(null); if (holder != null) { @@ -2003,7 +2008,7 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..2ed060c44386121f18d642251d30d9a0 for (int i = 0; i >= -1; i--) { for (int i1 = 0; i1 >= -1; i1--) { if (isTwoByTwoSapling(state, level, pos, i, i1)) { -@@ -163,7 +190,7 @@ public final class TreeGrower { +@@ -163,7 +191,7 @@ public final class TreeGrower { if (holder1 == null) { return false; } else { @@ -2012,7 +2017,7 @@ index 5471619a0484ece08640e2b3fd26746c351dc3e0..2ed060c44386121f18d642251d30d9a0 ConfiguredFeature configuredFeature2 = holder1.value(); BlockState blockState1 = level.getFluidState(pos).createLegacyBlock(); level.setBlock(pos, blockState1, Block.UPDATE_NONE); -@@ -198,58 +225,4 @@ public final class TreeGrower { +@@ -198,58 +226,4 @@ public final class TreeGrower { return false; } diff --git a/paper-server/src/main/java/io/papermc/paper/util/capture/GrowthContext.java b/paper-server/src/main/java/io/papermc/paper/util/capture/GrowthContext.java index 10aade5ce0bc..b8bb23d12a8d 100644 --- a/paper-server/src/main/java/io/papermc/paper/util/capture/GrowthContext.java +++ b/paper-server/src/main/java/io/papermc/paper/util/capture/GrowthContext.java @@ -3,30 +3,41 @@ import net.minecraft.Optionull; import net.minecraft.core.Holder; import net.minecraft.data.worldgen.features.TreeFeatures; -import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; import org.bukkit.TreeType; import org.jspecify.annotations.Nullable; -public class GrowthContext { +public final class GrowthContext { - public final ServerLevel originalLevel; + public static GrowthContext empty() { + return new GrowthContext(null, false); + } + + public static GrowthContext usingBoneMeal(@Nullable Player user) { + return new GrowthContext(user, true); + } - public @Nullable Player player; - public boolean cancelledStructureEvent = false; + private final @Nullable Player player; + private final boolean usedBoneMeal; + + public boolean cancelled = false; public Holder> feature; // just make this a field - public boolean usedBoneMeal; - public GrowthContext(ServerLevel originalLevel) { - this.originalLevel = originalLevel; + private GrowthContext(@Nullable Player player, boolean usedBoneMeal) { + this.player = player; + this.usedBoneMeal = usedBoneMeal; } public org.bukkit.entity.@Nullable Player getBukkitPlayer() { return (org.bukkit.entity.Player) Optionull.map(this.player, Entity::getBukkitEntity); } + public boolean usedBoneMeal() { + return this.usedBoneMeal; + } + public TreeType getTreeSpecies() { return getTreeSpecies(this.feature); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java index b3512ef0fbde..64ccc620f266 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java @@ -239,17 +239,6 @@ public boolean place(@net.minecraft.world.level.block.Block.UpdateFlags int flag return this.getWorldHandle().setBlock(this.position, this.data, flags); } - // used to revert a block placement due to an event being cancelled for example - public boolean revertPlace() { - return this.place( - net.minecraft.world.level.block.Block.UPDATE_CLIENTS | - net.minecraft.world.level.block.Block.UPDATE_KNOWN_SHAPE | - net.minecraft.world.level.block.Block.UPDATE_SUPPRESS_DROPS | - net.minecraft.world.level.block.Block.UPDATE_SKIP_ON_PLACE | - net.minecraft.world.level.block.Block.UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS - ); - } - @Override public byte getRawData() { return CraftMagicNumbers.toLegacyData(this.data); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 504204cb018a..512c0cbe2606 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -2386,19 +2386,22 @@ public static boolean sendChestLockedNotifications(Vec3 pos) { return false; } + // todo block list is sometimes empty for trees in both events public static boolean structureEvent(PaperCapturingWorldLevel level, BlockPos pos, Function worldGenCapture, GrowthContext context) { + ServerLevel originalLevel = level.getLevel(); try (SimpleBlockCapture capture = level.forkCaptureSession()) { MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); if (worldGenCapture.apply(captureTreeGeneration)) { - Location location = CraftLocation.toBukkit(pos, context.originalLevel); - List blocks = captureTreeGeneration.calculateLatestSnapshots(context.originalLevel); - StructureGrowEvent event = new StructureGrowEvent(location, context.getTreeSpecies(), context.usedBoneMeal, context.getBukkitPlayer(), blocks); + Location location = CraftLocation.toBukkit(pos, originalLevel); + List blocks = captureTreeGeneration.calculateLatestSnapshots(originalLevel); + StructureGrowEvent event = new StructureGrowEvent(location, context.getTreeSpecies(), context.usedBoneMeal(), context.getBukkitPlayer(), blocks); + event.setCancelled(context.cancelled); if (event.callEvent()) { capture.finalizePlacement(); // todo block list is mutable return true; } else { - context.cancelledStructureEvent = true; + context.cancelled = true; } } else { capture.finalizePlacement(); @@ -2408,25 +2411,20 @@ public static boolean structureEvent(PaperCapturingWorldLevel level, BlockPos po return false; } - // todo block list is sometimes empty for trees in both events - public static boolean fertilizedBlock(ServerLevel level, Player player, BlockPos pos, Consumer worldGenCapture, GrowthContext context) { - return fertilizeBlock(level, player, pos, capture -> { - worldGenCapture.accept(capture); - return true; - }, context); - } - - public static boolean fertilizeBlock(ServerLevel level, Player player, BlockPos pos, Function worldGenCapture, GrowthContext context) { + // todo cancelling fertilize event doesn't work for azalea + public static boolean fertilizeBlock(ServerLevel level, BlockPos pos, Function worldGenCapture, GrowthContext context) { try (SimpleBlockCapture capture = level.forkCaptureSession()) { MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel(); if (worldGenCapture.apply(captureTreeGeneration)) { List blocks = captureTreeGeneration.calculateLatestSnapshots(level); - BlockFertilizeEvent event = new BlockFertilizeEvent(CraftBlock.at(level, pos), player, blocks); - event.setCancelled(context.cancelledStructureEvent); + BlockFertilizeEvent event = new BlockFertilizeEvent(CraftBlock.at(level, pos), context.getBukkitPlayer(), blocks); + event.setCancelled(context.cancelled); if (event.callEvent()) { capture.finalizePlacement(); // todo block list is mutable return true; + } else { + context.cancelled = true; } } else { capture.finalizePlacement(); diff --git a/test-plugin/src/main/java/io/papermc/testplugin/OwenPlayground.java b/test-plugin/src/main/java/io/papermc/testplugin/OwenPlayground.java new file mode 100644 index 000000000000..371cf2cbf5ff --- /dev/null +++ b/test-plugin/src/main/java/io/papermc/testplugin/OwenPlayground.java @@ -0,0 +1,86 @@ +package io.papermc.testplugin; + +import org.bukkit.Material; +import org.bukkit.TreeType; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockFertilizeEvent; +import org.bukkit.event.block.BlockMultiPlaceEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.player.PlayerSwapHandItemsEvent; +import org.bukkit.event.world.StructureGrowEvent; + +import static net.kyori.adventure.text.Component.text; + +public record OwenPlayground() implements Listener { + + public static final OwenPlayground INSTANCE = new OwenPlayground(); + + @EventHandler + public void on(BlockFertilizeEvent event) { + //event.setCancelled(true); + +// event.getPlayer().sendBlockChanges(event.getBlocks()); +// +// new BukkitRunnable(){ +// +// @Override +// public void run() { +// event.getBlocks().forEach((state) -> event.getPlayer().sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData())); +// } +// }.runTaskLater(this, 20 * 2); + } + + + @EventHandler + public void on(StructureGrowEvent event) { +// event.setCancelled(true); +// +// event.getPlayer().sendBlockChanges(event.getBlocks()); +// +// new BukkitRunnable(){ +// +// @Override +// public void run() { +// event.getBlocks().forEach((state) -> event.getPlayer().sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData())); +// } +// }.runTaskLater(this, 20 * 2); + } + + + @EventHandler + public void on(PlayerSwapHandItemsEvent event) { + event.getPlayer().getTargetBlockExact(5).applyBoneMeal(BlockFace.UP); + + event.getPlayer().getWorld().generateTree(event.getPlayer().getLocation(), TreeType.values()[(int) (TreeType.values().length * Math.random())]); + } + + @EventHandler + public void on(BlockPlaceEvent event) { + event.getPlayer().sendActionBar(text("Replaced: " + event.getBlockReplacedState().getType())); + if (event.getPlayer().getInventory().contains(Material.DIAMOND)) { + event.getBlock().setType(Material.AIR); + } else if (event.getPlayer().getInventory().contains(Material.GOLD_INGOT)) { + event.setCancelled(true); + } else if (event.getPlayer().getInventory().contains(Material.EMERALD)) { + event.getBlock().setType(Material.STONE); + } else if (event.getPlayer().getInventory().contains(Material.GOLD_NUGGET)) { + event.getBlock().getRelative(BlockFace.SOUTH).setType(Material.STONE); + } + event.getPlayer().sendMessage(event.getBlock().getType().toString()); + + event.getBlockAgainst().setType(Material.DIAMOND_BLOCK); + } + + @EventHandler + public void on(BlockMultiPlaceEvent event) { + event.getPlayer().sendActionBar(text("Replaced: " + event.getReplacedBlockStates().stream().map(BlockState::getBlockData).toList())); + + event.getPlayer().sendMessage(event.getBlock().getType().toString()); + if (event.getPlayer().getInventory().contains(Material.DIAMOND)) { + event.setCancelled(true); + } + } +} diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java index fb067a9af683..952349905061 100644 --- a/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java +++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java @@ -1,99 +1,15 @@ package io.papermc.testplugin; -import org.bukkit.BlockChangeDelegate; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.TreeType; -import org.bukkit.block.BlockFace; -import org.bukkit.block.BlockState; -import org.bukkit.block.Chest; -import org.bukkit.block.data.BlockData; -import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; -import org.bukkit.event.block.BlockFertilizeEvent; -import org.bukkit.event.block.BlockMultiPlaceEvent; -import org.bukkit.event.block.BlockPlaceEvent; -import org.bukkit.event.player.PlayerSwapHandItemsEvent; -import org.bukkit.event.world.StructureGrowEvent; -import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitRunnable; - -import static net.kyori.adventure.text.Component.text; public final class TestPlugin extends JavaPlugin implements Listener { @Override public void onEnable() { this.getServer().getPluginManager().registerEvents(this, this); + this.getServer().getPluginManager().registerEvents(OwenPlayground.INSTANCE, this); // io.papermc.testplugin.brigtests.Registration.registerViaOnEnable(this); } - - @EventHandler - public void on(BlockFertilizeEvent event) { - //event.setCancelled(true); - -// event.getPlayer().sendBlockChanges(event.getBlocks()); -// -// new BukkitRunnable(){ -// -// @Override -// public void run() { -// event.getBlocks().forEach((state) -> event.getPlayer().sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData())); -// } -// }.runTaskLater(this, 20 * 2); - } - - - @EventHandler - public void on(StructureGrowEvent event) { -// event.setCancelled(true); -// -// event.getPlayer().sendBlockChanges(event.getBlocks()); -// -// new BukkitRunnable(){ -// -// @Override -// public void run() { -// event.getBlocks().forEach((state) -> event.getPlayer().sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData())); -// } -// }.runTaskLater(this, 20 * 2); - } - - - @EventHandler - public void on(PlayerSwapHandItemsEvent event) { - event.getPlayer().getTargetBlockExact(5).applyBoneMeal(BlockFace.UP); - - event.getPlayer().getWorld().generateTree(event.getPlayer().getLocation(), TreeType.values()[(int) (TreeType.values().length * Math.random())]); - } - - @EventHandler - public void on(BlockPlaceEvent event) { - event.getPlayer().sendActionBar(text("Replaced: " + event.getBlockReplacedState().getType())); - if (event.getPlayer().getInventory().contains(Material.DIAMOND)) { - event.getBlock().setType(Material.AIR); - } else if (event.getPlayer().getInventory().contains(Material.GOLD_INGOT)) { - event.setCancelled(true); - } else if (event.getPlayer().getInventory().contains(Material.EMERALD)) { - event.getBlock().setType(Material.STONE); - } else if (event.getPlayer().getInventory().contains(Material.GOLD_NUGGET)) { - event.getBlock().getRelative(BlockFace.SOUTH).setType(Material.STONE); - } - event.getPlayer().sendMessage(event.getBlock().getType().toString()); - - event.getBlockAgainst().setType(Material.DIAMOND_BLOCK); - } - - @EventHandler - public void blockPlace(BlockMultiPlaceEvent event) { - event.getPlayer().sendActionBar(text("Replaced: " + event.getReplacedBlockStates().stream().map(BlockState::getBlockData).toList())); - - event.getPlayer().sendMessage(event.getBlock().getType().toString()); - if (event.getPlayer().getInventory().contains(Material.DIAMOND)) { - event.setCancelled(true); - } - } }