From 9b9cfc9cbe114d1f0b9b164ddd6fb82761587c55 Mon Sep 17 00:00:00 2001 From: failutee Date: Mon, 1 Dec 2025 18:36:12 +0100 Subject: [PATCH 1/5] Update BorderBlockController.java --- .../block/BorderBlockController.java | 147 +++++++++++------- 1 file changed, 90 insertions(+), 57 deletions(-) diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java index 73e2fb95..8f1854f1 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java @@ -16,6 +16,7 @@ import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange.EncodedBlock; import java.util.Collection; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -46,6 +47,7 @@ public class BorderBlockController implements Listener { private final Set playersToUpdate = ConcurrentHashMap.newKeySet(); private final Map lockedPlayers = new ConcurrentHashMap<>(); + private final Map> originalBlocks = new ConcurrentHashMap<>(); private final ChunkCache chunkCache; public BorderBlockController(BorderService borderService, Supplier settings, Scheduler scheduler, Server server) { @@ -59,12 +61,12 @@ public BorderBlockController(BorderService borderService, Supplier borderPoints = this.getPointsWithoutAir(player, event.getPoints()); + Set borderPoints = this.filterPassablePoints(player, event.getPoints()); event.setPoints(borderPoints); this.showBlocks(player, borderPoints); @@ -73,23 +75,26 @@ void onBorderShowAsyncEvent(BorderShowAsyncEvent event) { @EventHandler void onBorderHideAsyncEvent(BorderHideAsyncEvent event) { - if (!settings.get().enabled) { + if (!this.settings.get().enabled) { return; } - Object lock = lockedPlayers.computeIfAbsent(event.getPlayer().getUniqueId(), k -> new Object()); + UUID playerId = event.getPlayer().getUniqueId(); + Object lock = this.lockedPlayers.computeIfAbsent(playerId, k -> new Object()); + synchronized (lock) { - this.hideBlocks(event.getPlayer(), event.getPoints()); + this.restoreBlocks(event.getPlayer(), event.getPoints()); Set border = this.borderService.getActiveBorder(event.getPlayer()); if (border.isEmpty()) { - this.playersToUpdate.remove(event.getPlayer().getUniqueId()); + this.playersToUpdate.remove(playerId); + this.originalBlocks.remove(playerId); } } } private void updatePlayers() { - if (!settings.get().enabled) { + if (!this.settings.get().enabled) { return; } @@ -105,7 +110,7 @@ private void updatePlayers() { } private void updatePlayer(UUID uuid, Player player) { - Object lock = lockedPlayers.computeIfAbsent(uuid, k -> new Object()); + Object lock = this.lockedPlayers.computeIfAbsent(uuid, k -> new Object()); synchronized (lock) { Set border = this.borderService.getActiveBorder(player); @@ -119,79 +124,107 @@ private void updatePlayer(UUID uuid, Player player) { } private void showBlocks(Player player, Collection blocks) { - this.splitByChunks(blocks).entrySet().stream() - .map(chunkBlocks -> toMultiBlockChangePacket(chunkBlocks)) - .forEach(change -> PLAYER_MANAGER.sendPacket(player, change)); - } + this.splitByChunkSection(blocks).forEach((chunkPos, chunkPoints) -> { + EncodedBlock[] encodedBlocks = chunkPoints.stream() + .map(this::toBorderBlock) + .toArray(EncodedBlock[]::new); - private WrapperPlayServerMultiBlockChange toMultiBlockChangePacket(Entry> chunkBlocks) { - EncodedBlock[] encodedBlocks = chunkBlocks.getValue().stream() - .map(borderPoint -> this.toEncodedBlock(borderPoint)) - .toArray(value -> new EncodedBlock[value]); - - return new WrapperPlayServerMultiBlockChange(chunkBlocks.getKey(), true, encodedBlocks); + PLAYER_MANAGER.sendPacket(player, new WrapperPlayServerMultiBlockChange(chunkPos, true, encodedBlocks)); + }); } - private void hideBlocks(Player player, Collection blocks) { - this.splitByChunks(blocks).entrySet().stream() - .map(chunkBlocks -> toMultiAirChangePacket(chunkBlocks)) - .forEach(change -> PLAYER_MANAGER.sendPacket(player, change)); - } + private void restoreBlocks(Player player, Collection blocks) { + UUID playerId = player.getUniqueId(); + Map savedBlocks = this.originalBlocks.get(playerId); - private WrapperPlayServerMultiBlockChange toMultiAirChangePacket(Entry> chunkBlocks) { - EncodedBlock[] encodedBlocks = chunkBlocks.getValue().stream() - .map(point -> new EncodedBlock(AIR_ID, point.x(), point.y(), point.z())) - .toArray(value -> new EncodedBlock[value]); + this.splitByChunkSection(blocks).forEach((chunkPos, chunkPoints) -> { + EncodedBlock[] encodedBlocks = chunkPoints.stream() + .map(point -> this.toOriginalBlock(point, savedBlocks)) + .toArray(EncodedBlock[]::new); - return new WrapperPlayServerMultiBlockChange(chunkBlocks.getKey(), true, encodedBlocks); + PLAYER_MANAGER.sendPacket(player, new WrapperPlayServerMultiBlockChange(chunkPos, true, encodedBlocks)); + }); + + if (savedBlocks != null) { + blocks.forEach(savedBlocks::remove); + } } - private Set getPointsWithoutAir(Player player, Collection blocks) { - Map> chunksToProcess = blocks.stream() - .map(borderPoint -> borderPoint.toInclusive()) - .collect(Collectors.groupingBy( - inclusive -> new ChunkLocation(inclusive.x() >> MINECRAFT_CHUNK_SHIFT, inclusive.z() >> MINECRAFT_CHUNK_SHIFT), - Collectors.toSet() - )); + private Set filterPassablePoints(Player player, Collection points) { + UUID playerId = player.getUniqueId(); + Map savedBlocks = this.originalBlocks.computeIfAbsent(playerId, key -> new ConcurrentHashMap<>()); - return chunksToProcess.entrySet().stream() - .flatMap(entry -> getPointsWithoutAirOnChunk(player, entry)) + return this.groupByChunk(points).entrySet().stream() + .flatMap(entry -> this.filterChunkPoints(player, entry, savedBlocks)) .collect(Collectors.toSet()); } - private Stream getPointsWithoutAirOnChunk(Player player, Entry> entry) { - ChunkSnapshot snapshot = this.chunkCache.loadSnapshot(player, entry.getKey()); + private Stream filterChunkPoints(Player player, Entry> entry, Map savedBlocks) { + ChunkSnapshot snapshot = this.chunkCache.loadSnapshot(player, entry.getKey()); + if (snapshot == null) { return Stream.empty(); } - return entry.getValue().stream() - .filter(point -> isAir(entry.getKey(), point, snapshot)); + ChunkLocation chunk = entry.getKey(); + + return entry.getValue().stream().filter(point -> this.isPassableAndSave(point, chunk, snapshot, savedBlocks)); } - private static boolean isAir(ChunkLocation location, BorderPoint point, ChunkSnapshot chunk) { - int xInsideChunk = point.x() - (location.x() << MINECRAFT_CHUNK_SHIFT); - int zInsideChunk = point.z() - (location.z() << MINECRAFT_CHUNK_SHIFT); - Material material = chunk.getBlockType(xInsideChunk, point.y(), zInsideChunk); + private boolean isPassableAndSave(BorderPoint point, ChunkLocation chunk, ChunkSnapshot snapshot, Map savedBlocks) { + if (savedBlocks.containsKey(point)) { + return true; + } + + int localX = point.x() - (chunk.x() << MINECRAFT_CHUNK_SHIFT); + int localZ = point.z() - (chunk.z() << MINECRAFT_CHUNK_SHIFT); + + Material material = snapshot.getBlockType(localX, point.y(), localZ); + + if (material.isSolid()) { + return false; + } + + savedBlocks.put(point, this.resolveBlockId(material)); - return material.isAir(); + return true; } - private Map> splitByChunks(Collection blocks) { - return blocks.stream().collect(Collectors.groupingBy( - block -> new Vector3i( - block.x() >> MINECRAFT_CHUNK_SHIFT, - block.y() >> MINECRAFT_CHUNK_SHIFT, - block.z() >> MINECRAFT_CHUNK_SHIFT - ), + private int resolveBlockId(Material material) { + StateType stateType = StateTypes.getByName("minecraft:" + material.name().toLowerCase(Locale.ROOT)); + + if (stateType == null) { + return AIR_ID; + } + + return WrappedBlockState.getDefaultState(SERVER_VERSION, stateType).getGlobalId(); + } + + private EncodedBlock toBorderBlock(BorderPoint point) { + StateType type = this.settings.get().type.getStateType(point); + int blockId = WrappedBlockState.getDefaultState(SERVER_VERSION, type).getGlobalId(); + + return new EncodedBlock(blockId, point.x(), point.y(), point.z()); + } + + private EncodedBlock toOriginalBlock(BorderPoint point, Map savedBlocks) { + int blockId = savedBlocks != null ? savedBlocks.getOrDefault(point, AIR_ID) : AIR_ID; + + return new EncodedBlock(blockId, point.x(), point.y(), point.z()); + } + + private Map> groupByChunk(Collection points) { + return points.stream().collect(Collectors.groupingBy( + point -> new ChunkLocation(point.x() >> MINECRAFT_CHUNK_SHIFT, point.z() >> MINECRAFT_CHUNK_SHIFT), Collectors.toSet() )); } - private EncodedBlock toEncodedBlock(BorderPoint point) { - StateType type = settings.get().type.getStateType(point); - WrappedBlockState state = WrappedBlockState.getDefaultState(SERVER_VERSION, type); - return new EncodedBlock(state.getGlobalId(), point.x(), point.y(), point.z()); + private Map> splitByChunkSection(Collection points) { + return points.stream().collect(Collectors.groupingBy( + point -> new Vector3i(point.x() >> MINECRAFT_CHUNK_SHIFT, point.y() >> MINECRAFT_CHUNK_SHIFT, point.z() >> MINECRAFT_CHUNK_SHIFT), + Collectors.toSet() + )); } } From e578fdf395b29f549c974acd81d9bfd70a10976a Mon Sep 17 00:00:00 2001 From: failutee Date: Mon, 1 Dec 2025 18:51:24 +0100 Subject: [PATCH 2/5] Update BorderBlockController.java --- .../combat/border/animation/block/BorderBlockController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java index 8f1854f1..18cf542f 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java @@ -56,7 +56,7 @@ public BorderBlockController(BorderService borderService, Supplier this.updatePlayers(), settings.get().updateDelay, settings.get().updateDelay); + scheduler.timerAsync(this::updatePlayers, settings.get().updateDelay, settings.get().updateDelay); } @EventHandler From e94da897f442cda987da6d213be323a23a9ca1c3 Mon Sep 17 00:00:00 2001 From: failutee Date: Mon, 1 Dec 2025 18:53:33 +0100 Subject: [PATCH 3/5] Update BorderBlockController.java --- .../border/animation/block/BorderBlockController.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java index 18cf542f..fdc2856f 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java @@ -80,7 +80,8 @@ void onBorderHideAsyncEvent(BorderHideAsyncEvent event) { } UUID playerId = event.getPlayer().getUniqueId(); - Object lock = this.lockedPlayers.computeIfAbsent(playerId, k -> new Object()); + + Object lock = this.getLock(playerId); synchronized (lock) { this.restoreBlocks(event.getPlayer(), event.getPoints()); @@ -110,7 +111,8 @@ private void updatePlayers() { } private void updatePlayer(UUID uuid, Player player) { - Object lock = this.lockedPlayers.computeIfAbsent(uuid, k -> new Object()); + Object lock = this.getLock(uuid); + synchronized (lock) { Set border = this.borderService.getActiveBorder(player); @@ -227,4 +229,7 @@ private Map> splitByChunkSection(Collection new Object()); + } } From 4b448bbc8188fa42661e116e4533c1fe8b0fcd7e Mon Sep 17 00:00:00 2001 From: failutee Date: Mon, 1 Dec 2025 19:05:02 +0100 Subject: [PATCH 4/5] Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../combat/border/animation/block/BorderBlockController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java index fdc2856f..a8145221 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java @@ -193,7 +193,7 @@ private boolean isPassableAndSave(BorderPoint point, ChunkLocation chunk, ChunkS } private int resolveBlockId(Material material) { - StateType stateType = StateTypes.getByName("minecraft:" + material.name().toLowerCase(Locale.ROOT)); + StateType stateType = StateTypes.getByName(material.getKey().toString()); if (stateType == null) { return AIR_ID; From 21334d60b81b2083ad90359fdc119757068ef30b Mon Sep 17 00:00:00 2001 From: failutee Date: Mon, 1 Dec 2025 19:35:55 +0100 Subject: [PATCH 5/5] Follow suggestions --- .../block/BorderBlockController.java | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java index a8145221..70fd12da 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java @@ -16,7 +16,6 @@ import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange; import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange.EncodedBlock; import java.util.Collection; -import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -31,6 +30,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; public class BorderBlockController implements Listener { @@ -66,11 +66,15 @@ void onBorderShowAsyncEvent(BorderShowAsyncEvent event) { } Player player = event.getPlayer(); - Set borderPoints = this.filterPassablePoints(player, event.getPoints()); + UUID playerId = player.getUniqueId(); + + synchronized (this.getLock(playerId)) { + Set borderPoints = this.filterPassablePoints(player, event.getPoints()); - event.setPoints(borderPoints); - this.showBlocks(player, borderPoints); - this.playersToUpdate.add(player.getUniqueId()); + event.setPoints(borderPoints); + this.showBlocks(player, borderPoints); + this.playersToUpdate.add(playerId); + } } @EventHandler @@ -80,7 +84,7 @@ void onBorderHideAsyncEvent(BorderHideAsyncEvent event) { } UUID playerId = event.getPlayer().getUniqueId(); - + Object lock = this.getLock(playerId); synchronized (lock) { @@ -94,6 +98,15 @@ void onBorderHideAsyncEvent(BorderHideAsyncEvent event) { } } + @EventHandler + void onPlayerQuit(PlayerQuitEvent event) { + UUID playerId = event.getPlayer().getUniqueId(); + + this.playersToUpdate.remove(playerId); + this.originalBlocks.remove(playerId); + this.lockedPlayers.remove(playerId); + } + private void updatePlayers() { if (!this.settings.get().enabled) { return; @@ -112,16 +125,18 @@ private void updatePlayers() { private void updatePlayer(UUID uuid, Player player) { Object lock = this.getLock(uuid); - + synchronized (lock) { Set border = this.borderService.getActiveBorder(player); if (border.isEmpty()) { this.playersToUpdate.remove(uuid); + this.originalBlocks.remove(uuid); return; } - this.showBlocks(player, border); + Set passablePoints = this.filterPassablePoints(player, border); + this.showBlocks(player, passablePoints); } } @@ -170,10 +185,10 @@ private Stream filterChunkPoints(Player player, Entry this.isPassableAndSave(point, chunk, snapshot, savedBlocks)); + return entry.getValue().stream().filter(point -> this.trySavePassableBlock(point, chunk, snapshot, savedBlocks)); } - private boolean isPassableAndSave(BorderPoint point, ChunkLocation chunk, ChunkSnapshot snapshot, Map savedBlocks) { + private boolean trySavePassableBlock(BorderPoint point, ChunkLocation chunk, ChunkSnapshot snapshot, Map savedBlocks) { if (savedBlocks.containsKey(point)) { return true; }