diff --git a/src/main/java/dev/loki/lomines/block/BukkitBlockSetter.java b/src/main/java/dev/loki/lomines/block/BukkitBlockSetter.java index 980a24a..e336bcd 100644 --- a/src/main/java/dev/loki/lomines/block/BukkitBlockSetter.java +++ b/src/main/java/dev/loki/lomines/block/BukkitBlockSetter.java @@ -2,6 +2,7 @@ package dev.loki.lomines.block; import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.data.config.block.BlockKey; +import dev.loki.lomines.util.block.BlockUpdateUtil; import dev.loki.lomines.util.location.Cuboid; import dev.lolib.scheduler.Scheduler; import org.bukkit.Bukkit; @@ -18,6 +19,9 @@ import java.util.function.IntConsumer; * BlockSetter implementation for vanilla Minecraft blocks. * Uses Bukkit API to set blocks with optimal performance settings. * Updated for BlockKey type-safe configuration. + * + *
Prevents ghost blocks by sending block update packets to clients + * after bulk block placement.
*/ public final class BukkitBlockSetter extends BlockSetter { @@ -34,7 +38,13 @@ public final class BukkitBlockSetter extends BlockSetter { Scheduler.get(plugin).runAsync(() -> { int count = fillSync(region); + // Send block updates on main thread to prevent ghost blocks Scheduler.get(plugin).run(() -> { + BlockUpdateUtil.sendRegionUpdate( + region.getWorld(), + region.getMinX(), region.getMinY(), region.getMinZ(), + region.getMaxX(), region.getMaxY(), region.getMaxZ() + ); callback.accept(count); }); }); @@ -48,7 +58,12 @@ public final class BukkitBlockSetter extends BlockSetter { } Scheduler.get(plugin).runAsync(() -> { int count = fillAtLocationsSync(locations); - Scheduler.get(plugin).run(() -> callback.accept(count)); + + // Send block updates on main thread to prevent ghost blocks + Scheduler.get(plugin).run(() -> { + BlockUpdateUtil.sendLocationsUpdate(locations); + callback.accept(count); + }); }); } diff --git a/src/main/java/dev/loki/lomines/handler/MineResetHandler.java b/src/main/java/dev/loki/lomines/handler/MineResetHandler.java index 7ff95ec..e6f2a57 100644 --- a/src/main/java/dev/loki/lomines/handler/MineResetHandler.java +++ b/src/main/java/dev/loki/lomines/handler/MineResetHandler.java @@ -3,6 +3,7 @@ package dev.loki.lomines.handler; import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.core.Mine; import dev.loki.lomines.data.config.block.FillMode; +import dev.loki.lomines.util.block.BlockUpdateUtil; import dev.loki.lomines.util.location.Cuboid; import dev.loki.lomines.util.location.LocationParser; import dev.lolib.scheduler.Scheduler; @@ -170,7 +171,8 @@ public final class MineResetHandler { } /** - * Teleports players standing inside the mine to the configured location. + * Teleports players standing inside the mine to a safe location near the configured destination. + * Prevents players from suffocating in blocks by finding a safe teleport spot. */ private void teleportPlayers() { var destOpt = mine.getConfig().teleport().getLocation(); @@ -181,9 +183,13 @@ public final class MineResetHandler { if (dest.getWorld() == null) { return; } + + // Find a safe teleport location to prevent suffocation + Location safeDest = BlockUpdateUtil.findSafeTeleportLocation(dest); + for (Player p : dest.getWorld().getPlayers()) { if (mine.contains(p.getLocation())) { - p.teleport(dest); + p.teleport(safeDest); } } } diff --git a/src/main/java/dev/loki/lomines/util/block/BlockUpdateUtil.java b/src/main/java/dev/loki/lomines/util/block/BlockUpdateUtil.java new file mode 100644 index 0000000..daa26fe --- /dev/null +++ b/src/main/java/dev/loki/lomines/util/block/BlockUpdateUtil.java @@ -0,0 +1,236 @@ +package dev.loki.lomines.util.block; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; + +import java.util.Collection; + +/** + * Utility for sending block updates to clients to prevent ghost blocks. + * + *Ghost blocks occur when the server sets blocks without notifying clients, + * causing a desync where blocks appear invisible or behave strangely on the client. + * This utility forces block updates to be sent to all relevant players.
+ */ +public final class BlockUpdateUtil { + + private BlockUpdateUtil() { + } + + /** + * Sends a block update to all players who can see the specified chunk. + * This prevents ghost blocks by forcing the server to send the block state to clients. + * + * @param block the block to update + */ + public static void sendBlockUpdate(Block block) { + if (block == null) { + return; + } + + // Get chunk and players in it + Chunk chunk = block.getChunk(); + World world = block.getWorld(); + + // Send block change to all players in the world who can see this chunk + for (Player player : world.getPlayers()) { + if (isChunkVisibleToPlayer(player, chunk)) { + player.sendBlockChange(block.getLocation(), block.getBlockData()); + } + } + } + + /** + * Sends block updates for all blocks in a cuboid region. + * Uses batching to minimize packet overhead. + * + * @param world the world containing the blocks + * @param minX minimum X coordinate + * @param minY minimum Y coordinate + * @param minZ minimum Z coordinate + * @param maxX maximum X coordinate + * @param maxY maximum Y coordinate + * @param maxZ maximum Z coordinate + */ + public static void sendRegionUpdate(World world, int minX, int minY, int minZ, + int maxX, int maxY, int maxZ) { + Collection