From 1d3b20e2f1389a1639a54bfd0cf2400a6436903a Mon Sep 17 00:00:00 2001 From: leir4iks Date: Tue, 21 Oct 2025 10:58:55 +0300 Subject: [PATCH 01/22] replace sync chunk loading with async operations --- .../coreprotect/command/TeleportCommand.java | 27 +++--- .../rollback/RollbackEntityHandler.java | 36 ++++---- .../database/rollback/RollbackProcessor.java | 89 +++++++++++-------- .../listener/block/BlockDispenseListener.java | 6 +- .../block/BlockFertilizeListener.java | 5 +- .../coreprotect/listener/block/BlockUtil.java | 4 +- .../net/coreprotect/thread/CacheHandler.java | 2 +- .../net/coreprotect/thread/Scheduler.java | 6 +- 8 files changed, 102 insertions(+), 73 deletions(-) diff --git a/src/main/java/net/coreprotect/command/TeleportCommand.java b/src/main/java/net/coreprotect/command/TeleportCommand.java index e5796827e..6a5bbc518 100644 --- a/src/main/java/net/coreprotect/command/TeleportCommand.java +++ b/src/main/java/net/coreprotect/command/TeleportCommand.java @@ -100,16 +100,23 @@ else if (y == null) { location.setY(Double.parseDouble(y)); location.setZ(Double.parseDouble(z)); - int chunkX = location.getBlockX() >> 4; - int chunkZ = location.getBlockZ() >> 4; - Scheduler.runTask(CoreProtect.getInstance(), () -> { - if (!location.getWorld().isChunkLoaded(chunkX, chunkZ)) { - location.getWorld().getChunkAt(location); - } - - // Teleport the player to a safe location - Teleport.performSafeTeleport(((Player) player), location, true); - }, location); + if (ConfigHandler.isFolia) { + location.getWorld().getChunkAtAsync(location).thenAccept(chunk -> { + Scheduler.runTask(CoreProtect.getInstance(), () -> { + Teleport.performSafeTeleport(((Player) player), location, true); + }, location); + }); + } + else { + int chunkX = location.getBlockX() >> 4; + int chunkZ = location.getBlockZ() >> 4; + Scheduler.runTask(CoreProtect.getInstance(), () -> { + if (!location.getWorld().isChunkLoaded(chunkX, chunkZ)) { + location.getWorld().getChunkAt(location); + } + Teleport.performSafeTeleport(((Player) player), location, true); + }, location); + } ConfigHandler.teleportThrottle.put(player.getName(), new Object[] { false, System.currentTimeMillis() }); } diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java index 10cdf50e1..a7d424aa1 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java @@ -47,24 +47,30 @@ public class RollbackEntityHandler { * @return The number of entities affected (1 if successful, 0 otherwise) */ public static int processEntity(Object[] row, int rollbackType, String finalUserString, int oldTypeRaw, int rowTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, int rowUserId, String rowUser) { + World bukkitWorld = Bukkit.getServer().getWorld(WorldUtils.getWorldName(rowWorldId)); + if (bukkitWorld == null) { + return 0; + } + + if (ConfigHandler.isFolia) { + bukkitWorld.getChunkAtAsync(rowX >> 4, rowZ >> 4, true).thenAccept(chunk -> { + processEntityLogic(row, rollbackType, finalUserString, oldTypeRaw, rowTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUserId, rowUser, bukkitWorld); + }); + return 1; + } + else { + if (!bukkitWorld.isChunkLoaded(rowX >> 4, rowZ >> 4)) { + bukkitWorld.getChunkAt(rowX >> 4, rowZ >> 4); + } + return processEntityLogic(row, rollbackType, finalUserString, oldTypeRaw, rowTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUserId, rowUser, bukkitWorld); + } + } + + private static int processEntityLogic(Object[] row, int rollbackType, String finalUserString, int oldTypeRaw, int rowTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, int rowUserId, String rowUser, World bukkitWorld) { try { // Entity kill if (rowAction == 3) { - String world = getWorldName(rowWorldId); - if (world.isEmpty()) { - return 0; - } - - World bukkitWorld = Bukkit.getServer().getWorld(world); - if (bukkitWorld == null) { - return 0; - } - Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); - if (!bukkitWorld.isChunkLoaded(block.getChunk())) { - bukkitWorld.getChunkAt(block.getLocation()); - } - if (rowTypeRaw > 0) { // Spawn in entity if (rowRolledBack == 0) { @@ -184,7 +190,7 @@ public static void updateEntityCount(String userString, int increment) { /** * Spawns an entity at the given block location. - * + * * @param user * The username of the player * @param block diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java index 21788c8ee..418cf0cd8 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java @@ -38,32 +38,49 @@ public class RollbackProcessor { - /** - * Process data for a specific chunk - * - * @param finalChunkX - * The chunk X coordinate - * @param finalChunkZ - * The chunk Z coordinate - * @param chunkKey - * The chunk lookup key - * @param blockList - * The list of block data to process - * @param itemList - * The list of item data to process - * @param rollbackType - * The rollback type (0=rollback, 1=restore) - * @param preview - * Whether this is a preview (0=no, 1=yes-non-destructive, 2=yes-destructive) - * @param finalUserString - * The username performing the rollback - * @param finalUser - * The user performing the rollback - * @param bukkitRollbackWorld - * The world to process - * @return True if successful, false if there was an error - */ public static boolean processChunk(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { + if (ConfigHandler.isFolia) { + bukkitRollbackWorld.getChunkAtAsync(finalChunkX, finalChunkZ, true).thenAccept(chunk -> { + try { + processChunkLogic(finalChunkX, finalChunkZ, chunkKey, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); + } + catch (Exception e) { + e.printStackTrace(); + int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); + if (rollbackHashData != null) { + int itemCount = rollbackHashData[0]; + int blockCount = rollbackHashData[1]; + int entityCount = rollbackHashData[2]; + int scannedWorlds = rollbackHashData[4]; + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 2, (scannedWorlds + 1) }); + } + } + }); + return true; + } + else { + try { + if (!bukkitRollbackWorld.isChunkLoaded(finalChunkX, finalChunkZ)) { + bukkitRollbackWorld.getChunkAt(finalChunkX, finalChunkZ); + } + return processChunkLogic(finalChunkX, finalChunkZ, chunkKey, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); + } + catch (Exception e) { + e.printStackTrace(); + int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); + if (rollbackHashData != null) { + int itemCount = rollbackHashData[0]; + int blockCount = rollbackHashData[1]; + int entityCount = rollbackHashData[2]; + int scannedWorlds = rollbackHashData[4]; + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 2, (scannedWorlds + 1) }); + } + return false; + } + } + } + + private static boolean processChunkLogic(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { try { boolean clearInventories = Config.getGlobal().ROLLBACK_ITEMS; ArrayList data = blockList != null ? blockList : new ArrayList<>(); @@ -182,10 +199,6 @@ else if (rowAction == 3) { // entity kill } Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); - if (!bukkitWorld.isChunkLoaded(block.getChunk())) { - bukkitWorld.getChunkAt(block.getLocation()); - } - boolean changeBlock = true; boolean countBlock = true; Material changeType = block.getType(); @@ -243,7 +256,7 @@ else if ((pendingChangeType != Material.AIR) && (pendingChangeType != Material.C data.clear(); // Apply cached block changes - RollbackBlockHandler.applyBlockChanges(chunkChanges, preview, finalUser instanceof Player ? (Player) finalUser : null); + RollbackBlockHandler.applyBlockChanges(chunkChanges, preview, finalUser); // Process container items Map> sortPlayers = new HashMap<>(); @@ -346,9 +359,6 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT continue; } Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); - if (!bukkitWorld.isChunkLoaded(block.getChunk())) { - bukkitWorld.getChunkAt(block.getLocation()); - } if (BlockGroup.CONTAINERS.contains(block.getType())) { BlockState blockState = block.getState(); @@ -441,12 +451,13 @@ else if (entity instanceof ItemFrame) { catch (Exception e) { e.printStackTrace(); int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); - int itemCount = rollbackHashData[0]; - int blockCount = rollbackHashData[1]; - int entityCount = rollbackHashData[2]; - int scannedWorlds = rollbackHashData[4]; - - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 2, (scannedWorlds + 1) }); + if (rollbackHashData != null) { + int itemCount = rollbackHashData[0]; + int blockCount = rollbackHashData[1]; + int entityCount = rollbackHashData[2]; + int scannedWorlds = rollbackHashData[4]; + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 2, (scannedWorlds + 1) }); + } return false; } } diff --git a/src/main/java/net/coreprotect/listener/block/BlockDispenseListener.java b/src/main/java/net/coreprotect/listener/block/BlockDispenseListener.java index cbd761ec6..45ee7750e 100644 --- a/src/main/java/net/coreprotect/listener/block/BlockDispenseListener.java +++ b/src/main/java/net/coreprotect/listener/block/BlockDispenseListener.java @@ -58,7 +58,7 @@ protected void onBlockDispense(BlockDispenseEvent event) { forceItem = true; // droppers always drop items } - ItemStack[] inventory = ((InventoryHolder) block.getState()).getInventory().getStorageContents(); + ItemStack[] inventory = ((InventoryHolder) block.getState()).getInventory().getContents(); if (forceItem) { inventory = Arrays.copyOf(inventory, inventory.length + 1); inventory[inventory.length - 1] = item; @@ -83,7 +83,9 @@ else if (material.equals(Material.FLINT_AND_STEEL)) { } if (!dispenseSuccess && material == Material.BONE_MEAL) { - CacheHandler.redstoneCache.put(newBlock.getLocation(), new Object[] { System.currentTimeMillis(), user }); + Location location = newBlock.getLocation(); + String key = location.getWorld().getName() + ":" + location.getBlockX() + ":" + location.getBlockY() + ":" + location.getBlockZ(); + CacheHandler.redstoneCache.put(key, new Object[] { System.currentTimeMillis(), user }); } if (type == Material.FIRE && (!Config.getConfig(world).BLOCK_IGNITE || !(newBlockData instanceof Lightable))) { diff --git a/src/main/java/net/coreprotect/listener/block/BlockFertilizeListener.java b/src/main/java/net/coreprotect/listener/block/BlockFertilizeListener.java index 325c608b6..3cd0950a6 100644 --- a/src/main/java/net/coreprotect/listener/block/BlockFertilizeListener.java +++ b/src/main/java/net/coreprotect/listener/block/BlockFertilizeListener.java @@ -50,7 +50,8 @@ protected void onBlockFertilize(BlockFertilizeEvent event) { user = player.getName(); } else { - Object[] data = CacheHandler.redstoneCache.get(location); + String key = location.getWorld().getName() + ":" + location.getBlockX() + ":" + location.getBlockY() + ":" + location.getBlockZ(); + Object[] data = CacheHandler.redstoneCache.get(key); if (data != null) { long newTime = System.currentTimeMillis(); long oldTime = (long) data[0]; @@ -58,7 +59,7 @@ protected void onBlockFertilize(BlockFertilizeEvent event) { user = (String) data[1]; } - CacheHandler.redstoneCache.remove(location); + CacheHandler.redstoneCache.remove(key); } } diff --git a/src/main/java/net/coreprotect/listener/block/BlockUtil.java b/src/main/java/net/coreprotect/listener/block/BlockUtil.java index 09c0145cf..817fa47f4 100644 --- a/src/main/java/net/coreprotect/listener/block/BlockUtil.java +++ b/src/main/java/net/coreprotect/listener/block/BlockUtil.java @@ -52,7 +52,8 @@ public static Block gravityScan(Location location, Material type, String player) int yc = y - 1; // user placing sand/gravel. Find the bottom block int bottomfound = 0; - while (bottomfound == 0) { + int i = 0; + while (bottomfound == 0 && i < 256) { if (yc < BukkitAdapter.ADAPTER.getMinHeight(world)) { block = world.getBlockAt(x, yc + 1, z); bottomfound = 1; @@ -81,6 +82,7 @@ else if (down == Material.WATER && type.name().endsWith("_CONCRETE_POWDER")) { } yc--; } + i++; } CacheHandler.lookupCache.put("" + x + "." + block.getY() + "." + z + "." + wid + "", new Object[] { timestamp, player, type }); } diff --git a/src/main/java/net/coreprotect/thread/CacheHandler.java b/src/main/java/net/coreprotect/thread/CacheHandler.java index 70bb55ee7..7c1f7a9eb 100755 --- a/src/main/java/net/coreprotect/thread/CacheHandler.java +++ b/src/main/java/net/coreprotect/thread/CacheHandler.java @@ -19,7 +19,7 @@ public class CacheHandler implements Runnable { public static Map entityCache = Collections.synchronizedMap(new HashMap<>()); public static ConcurrentHashMap pistonCache = new ConcurrentHashMap<>(16, 0.75f, 2); public static ConcurrentHashMap spreadCache = new ConcurrentHashMap<>(16, 0.75f, 2); - public static ConcurrentHashMap redstoneCache = new ConcurrentHashMap<>(16, 0.75f, 2); + public static ConcurrentHashMap redstoneCache = new ConcurrentHashMap<>(16, 0.75f, 2); @SuppressWarnings({ "unchecked", "rawtypes" }) @Override diff --git a/src/main/java/net/coreprotect/thread/Scheduler.java b/src/main/java/net/coreprotect/thread/Scheduler.java index eb16a9a0b..ef38fb597 100644 --- a/src/main/java/net/coreprotect/thread/Scheduler.java +++ b/src/main/java/net/coreprotect/thread/Scheduler.java @@ -31,10 +31,10 @@ public static void scheduleSyncDelayedTask(CoreProtect plugin, Runnable task, Ob else if (regionData instanceof Entity) { Entity entity = (Entity) regionData; if (delay == 0) { - entity.getScheduler().run(plugin, value -> task.run(), task); + entity.getScheduler().run(plugin, value -> task.run(), null); } else { - entity.getScheduler().runDelayed(plugin, value -> task.run(), task, delay); + entity.getScheduler().runDelayed(plugin, value -> task.run(), null, delay); } } else { @@ -64,7 +64,7 @@ public static Object scheduleSyncRepeatingTask(CoreProtect plugin, Runnable task } else if (regionData instanceof Entity) { Entity entity = (Entity) regionData; - return entity.getScheduler().runAtFixedRate(plugin, value -> task.run(), task, delay, period); + return entity.getScheduler().runAtFixedRate(plugin, value -> task.run(), null, delay, period); } else { return Bukkit.getServer().getGlobalRegionScheduler().runAtFixedRate(plugin, value -> task.run(), delay, period); From 18e0e2ba3cc5bd83d1fbbdb92d54aa36c800a260 Mon Sep 17 00:00:00 2001 From: leir4iks Date: Tue, 21 Oct 2025 11:09:23 +0300 Subject: [PATCH 02/22] javadoc --- .../rollback/RollbackEntityHandler.java | 5 +++- .../database/rollback/RollbackProcessor.java | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java index a7d424aa1..c6357604c 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java @@ -24,6 +24,8 @@ public class RollbackEntityHandler { * The type of rollback (0 for rollback, 1 for restore) * @param finalUserString * The user string for tracking operations + * @param oldTypeRaw + * The old raw type value * @param rowTypeRaw * The raw type value * @param rowData @@ -53,10 +55,11 @@ public static int processEntity(Object[] row, int rollbackType, String finalUser } if (ConfigHandler.isFolia) { + // Folia - load chunk async before processing bukkitWorld.getChunkAtAsync(rowX >> 4, rowZ >> 4, true).thenAccept(chunk -> { processEntityLogic(row, rollbackType, finalUserString, oldTypeRaw, rowTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUserId, rowUser, bukkitWorld); }); - return 1; + return 1; // assume task is queued successfully } else { if (!bukkitWorld.isChunkLoaded(rowX >> 4, rowZ >> 4)) { diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java index 418cf0cd8..3ea26e456 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java @@ -38,8 +38,34 @@ public class RollbackProcessor { + /** + * Process data for a specific chunk + * + * @param finalChunkX + * The chunk X coordinate + * @param finalChunkZ + * The chunk Z coordinate + * @param chunkKey + * The chunk lookup key + * @param blockList + * The list of block data to process + * @param itemList + * The list of item data to process + * @param rollbackType + * The rollback type (0=rollback, 1=restore) + * @param preview + * Whether this is a preview (0=no, 1=yes-non-destructive, 2=yes-destructive) + * @param finalUserString + * The username performing the rollback + * @param finalUser + * The user performing the rollback + * @param bukkitRollbackWorld + * The world to process + * @return True if successful, false if there was an error + */ public static boolean processChunk(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { if (ConfigHandler.isFolia) { + // Folia - load chunk async before processing bukkitRollbackWorld.getChunkAtAsync(finalChunkX, finalChunkZ, true).thenAccept(chunk -> { try { processChunkLogic(finalChunkX, finalChunkZ, chunkKey, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); From 1f3fc7ed746e2def79bc5a30329813ea7eacc8df Mon Sep 17 00:00:00 2001 From: leir4iks Date: Tue, 21 Oct 2025 11:43:37 +0300 Subject: [PATCH 03/22] more fixes --- .../database/rollback/Rollback.java | 90 ++++--------------- .../database/rollback/RollbackProcessor.java | 51 +++-------- 2 files changed, 31 insertions(+), 110 deletions(-) diff --git a/src/main/java/net/coreprotect/database/rollback/Rollback.java b/src/main/java/net/coreprotect/database/rollback/Rollback.java index 4d7d28c1a..072d0fb14 100644 --- a/src/main/java/net/coreprotect/database/rollback/Rollback.java +++ b/src/main/java/net/coreprotect/database/rollback/Rollback.java @@ -10,12 +10,15 @@ import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import net.coreprotect.CoreProtect; @@ -213,25 +216,17 @@ else if (table == 1) { // container ConfigHandler.rollbackHash.put(userString, new int[] { 0, 0, 0, 0, 0 }); final String finalUserString = userString; + List> futures = new ArrayList<>(); + for (Entry entry : DatabaseUtils.entriesSortedByValues(chunkList)) { chunkCount++; - - int itemCount = 0; - int blockCount = 0; - int entityCount = 0; - int scannedWorldData = 0; - int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); - itemCount = rollbackHashData[0]; - blockCount = rollbackHashData[1]; - entityCount = rollbackHashData[2]; - scannedWorldData = rollbackHashData[4]; - long chunkKey = entry.getKey(); final int finalChunkX = (int) chunkKey; final int finalChunkZ = (int) (chunkKey >> 32); final CommandSender finalUser = user; + final Integer finalChunkCount = chunkCount; + final Integer totalChunks = chunkList.size(); - HashMap worldMap = new HashMap<>(); for (int rollbackWorldId : worldList) { String rollbackWorld = WorldUtils.getWorldName(rollbackWorldId); if (rollbackWorld.length() == 0) { @@ -243,72 +238,21 @@ else if (table == 1) { // container continue; } - worldMap.put(rollbackWorldId, bukkitRollbackWorld); - } + final ArrayList finalBlockList = dataList.get(rollbackWorldId).getOrDefault(chunkKey, new ArrayList<>()); + final ArrayList finalItemList = itemDataList.get(rollbackWorldId).getOrDefault(chunkKey, new ArrayList<>()); - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 0, scannedWorldData }); - for (Entry rollbackWorlds : worldMap.entrySet()) { - Integer rollbackWorldId = rollbackWorlds.getKey(); - World bukkitRollbackWorld = rollbackWorlds.getValue(); - Location chunkLocation = new Location(bukkitRollbackWorld, (finalChunkX << 4), 0, (finalChunkZ << 4)); - final HashMap> finalBlockList = dataList.get(rollbackWorldId); - final HashMap> finalItemList = itemDataList.get(rollbackWorldId); - - Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), () -> { - // Process this chunk using our new RollbackProcessor class - ArrayList blockData = finalBlockList != null ? finalBlockList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>(); - ArrayList itemData = finalItemList != null ? finalItemList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>(); - RollbackProcessor.processChunk(finalChunkX, finalChunkZ, chunkKey, blockData, itemData, rollbackType, preview, finalUserString, finalUser instanceof Player ? (Player) finalUser : null, bukkitRollbackWorld, inventoryRollback); - }, chunkLocation, 0); - } + CompletableFuture future = RollbackProcessor.processChunk(finalChunkX, finalChunkZ, chunkKey, finalBlockList, finalItemList, rollbackType, preview, finalUserString, finalUser instanceof Player ? (Player) finalUser : null, bukkitRollbackWorld, inventoryRollback); + futures.add(future); - rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); - int next = rollbackHashData[3]; - int scannedWorlds = rollbackHashData[4]; - int sleepTime = 0; - int abort = 0; - - while (next == 0 || scannedWorlds < worldMap.size()) { - if (preview == 1) { - // Not actually changing blocks, so less intensive. - sleepTime = sleepTime + 1; - Thread.sleep(1); - } - else { - sleepTime = sleepTime + 5; - Thread.sleep(5); + if (verbose && user != null && preview == 0 && !actionList.contains(11)) { + future.thenRun(() -> Chat.sendMessage(user, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.ROLLBACK_CHUNKS_MODIFIED, finalChunkCount.toString(), totalChunks.toString(), (totalChunks == 1 ? Selector.FIRST : Selector.SECOND)))); } - - rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); - next = rollbackHashData[3]; - scannedWorlds = rollbackHashData[4]; - - if (sleepTime > 300000) { - abort = 1; - break; - } - } - - if (abort == 1 || next == 2) { - Chat.console(Phrase.build(Phrase.ROLLBACK_ABORTED)); - break; - } - - rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); - itemCount = rollbackHashData[0]; - blockCount = rollbackHashData[1]; - entityCount = rollbackHashData[2]; - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 0, 0 }); - - if (verbose && user != null && preview == 0 && !actionList.contains(11)) { - Integer chunks = chunkList.size(); - Chat.sendMessage(user, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.ROLLBACK_CHUNKS_MODIFIED, chunkCount.toString(), chunks.toString(), (chunks == 1 ? Selector.FIRST : Selector.SECOND))); } } - chunkList.clear(); - dataList.clear(); - itemDataList.clear(); + // wait for all chunk processing tasks to complete + CompletableFuture allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + allFutures.get(); int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); int itemCount = rollbackHashData[0]; @@ -318,7 +262,7 @@ else if (table == 1) { // container double totalSeconds = (timeFinish - timeStart) / 1000.0; if (user != null) { - RollbackComplete.output(user, location, checkUsers, restrictList, excludeList, excludeUserList, actionList, timeString, chunkCount, totalSeconds, itemCount, blockCount, entityCount, rollbackType, radius, verbose, restrictWorld, preview); + RollbackComplete.output(user, location, checkUsers, restrictList, excludeList, excludeUserList, actionList, timeString, chunkList.size(), totalSeconds, itemCount, blockCount, entityCount, rollbackType, radius, verbose, restrictWorld, preview); } list = LookupConverter.convertRawLookup(statement, lookupList); diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java index 3ea26e456..fa3b42e97 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java @@ -8,6 +8,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -61,47 +62,26 @@ public class RollbackProcessor { * The user performing the rollback * @param bukkitRollbackWorld * The world to process - * @return True if successful, false if there was an error + * @return A CompletableFuture that completes with true if successful, false otherwise */ - public static boolean processChunk(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { + public static CompletableFuture processChunk(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { if (ConfigHandler.isFolia) { // Folia - load chunk async before processing - bukkitRollbackWorld.getChunkAtAsync(finalChunkX, finalChunkZ, true).thenAccept(chunk -> { - try { - processChunkLogic(finalChunkX, finalChunkZ, chunkKey, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); - } - catch (Exception e) { - e.printStackTrace(); - int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); - if (rollbackHashData != null) { - int itemCount = rollbackHashData[0]; - int blockCount = rollbackHashData[1]; - int entityCount = rollbackHashData[2]; - int scannedWorlds = rollbackHashData[4]; - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 2, (scannedWorlds + 1) }); - } - } + return bukkitRollbackWorld.getChunkAtAsync(finalChunkX, finalChunkZ, true).thenApply(chunk -> { + return processChunkLogic(finalChunkX, finalChunkZ, chunkKey, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); }); - return true; } else { try { if (!bukkitRollbackWorld.isChunkLoaded(finalChunkX, finalChunkZ)) { bukkitRollbackWorld.getChunkAt(finalChunkX, finalChunkZ); } - return processChunkLogic(finalChunkX, finalChunkZ, chunkKey, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); + boolean result = processChunkLogic(finalChunkX, finalChunkZ, chunkKey, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); + return CompletableFuture.completedFuture(result); } catch (Exception e) { e.printStackTrace(); - int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); - if (rollbackHashData != null) { - int itemCount = rollbackHashData[0]; - int blockCount = rollbackHashData[1]; - int entityCount = rollbackHashData[2]; - int scannedWorlds = rollbackHashData[4]; - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 2, (scannedWorlds + 1) }); - } - return false; + return CompletableFuture.completedFuture(false); } } } @@ -119,7 +99,6 @@ private static boolean processChunkLogic(int finalChunkX, int finalChunkZ, long int itemCount = rollbackHashData[0]; int blockCount = rollbackHashData[1]; int entityCount = rollbackHashData[2]; - int scannedWorlds = rollbackHashData[4]; int rowX = (Integer) row[3]; int rowY = (Integer) row[4]; @@ -277,7 +256,7 @@ else if ((pendingChangeType != Material.AIR) && (pendingChangeType != Material.C } } - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 0, scannedWorlds }); + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 0, 0 }); } data.clear(); @@ -300,7 +279,7 @@ else if ((pendingChangeType != Material.AIR) && (pendingChangeType != Material.C int itemCount1 = rollbackHashData1[0]; int blockCount1 = rollbackHashData1[1]; int entityCount1 = rollbackHashData1[2]; - int scannedWorlds = rollbackHashData1[4]; + int rowX = (Integer) row[3]; int rowY = (Integer) row[4]; int rowZ = (Integer) row[5]; @@ -360,7 +339,7 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT } itemCount1 = itemCount1 + rowAmount; - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, scannedWorlds }); + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, 0 }); continue; // remove this for merged rollbacks in future? (be sure to re-enable chunk sorting) } @@ -442,7 +421,7 @@ else if (entity instanceof ItemFrame) { } } - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, scannedWorlds }); + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, 0 }); } itemData.clear(); @@ -455,8 +434,7 @@ else if (entity instanceof ItemFrame) { int itemCount = rollbackHashData[0]; int blockCount = rollbackHashData[1]; int entityCount = rollbackHashData[2]; - int scannedWorlds = rollbackHashData[4]; - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 1, (scannedWorlds + 1) }); + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 1, 1 }); // Teleport players out of danger if they're within this chunk if (preview == 0) { @@ -481,8 +459,7 @@ else if (entity instanceof ItemFrame) { int itemCount = rollbackHashData[0]; int blockCount = rollbackHashData[1]; int entityCount = rollbackHashData[2]; - int scannedWorlds = rollbackHashData[4]; - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 2, (scannedWorlds + 1) }); + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 2, 1 }); } return false; } From 1e41943b8a679340edb4f1d1314d85f30df05531 Mon Sep 17 00:00:00 2001 From: leir4iks Date: Wed, 29 Oct 2025 18:25:17 +0200 Subject: [PATCH 04/22] improve folia support --- .../coreprotect/command/TeleportCommand.java | 97 +++-- .../rollback/RollbackBlockHandler.java | 243 +++++++----- .../rollback/RollbackEntityHandler.java | 68 ++-- .../database/rollback/RollbackProcessor.java | 110 +++--- .../database/rollback/RollbackUtil.java | 361 +++++++++--------- .../net/coreprotect/utility/BlockUtils.java | 16 +- .../net/coreprotect/utility/Teleport.java | 30 +- .../java/net/coreprotect/utility/Util.java | 14 +- .../utility/entity/HangingUtil.java | 102 +++-- 9 files changed, 564 insertions(+), 477 deletions(-) diff --git a/src/main/java/net/coreprotect/command/TeleportCommand.java b/src/main/java/net/coreprotect/command/TeleportCommand.java index 6a5bbc518..4656a9517 100644 --- a/src/main/java/net/coreprotect/command/TeleportCommand.java +++ b/src/main/java/net/coreprotect/command/TeleportCommand.java @@ -14,14 +14,11 @@ import net.coreprotect.utility.ChatMessage; import net.coreprotect.utility.Color; import net.coreprotect.utility.Teleport; -import net.coreprotect.utility.Util; import net.coreprotect.utility.WorldUtils; public class TeleportCommand { protected static void runCommand(CommandSender player, boolean permission, String[] args) { - int resultc = args.length; - if (!permission) { Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION)); return; @@ -40,22 +37,47 @@ protected static void runCommand(CommandSender player, boolean permission, Strin } } - if (resultc < 3) { - Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_PARAMETERS, "/co teleport ")); + final Location location = parseTeleportLocation((Player) player, args); + if (location == null) { return; } + // folia: safe teleportation logic + if (ConfigHandler.isFolia) { + location.getWorld().getChunkAtAsync(location).thenAccept(chunk -> Scheduler.runTask(CoreProtect.getInstance(), () -> Teleport.performSafeTeleport(((Player) player), location, true), location)); + } + else { + int chunkX = location.getBlockX() >> 4; + int chunkZ = location.getBlockZ() >> 4; + Scheduler.runTask(CoreProtect.getInstance(), () -> { + if (!location.getWorld().isChunkLoaded(chunkX, chunkZ)) { + location.getWorld().getChunkAt(location); + } + Teleport.performSafeTeleport(((Player) player), location, true); + }, location); + } + + ConfigHandler.teleportThrottle.put(player.getName(), new Object[] { false, System.currentTimeMillis() }); + } + + private static Location parseTeleportLocation(Player player, String[] args) { + if (args.length < 3) { + Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_PARAMETERS, "/co teleport ")); + return null; + } + String worldName = args[1]; int wid = WorldUtils.matchWorld(worldName); - if (wid == -1 && resultc >= 5) { + if (wid == -1 && args.length >= 5) { Chat.sendMessage(player, new ChatMessage(Phrase.build(Phrase.WORLD_NOT_FOUND, worldName)).build()); - return; + return null; } - Location location = ((Player) player).getLocation().clone(); - World world = location.getWorld(); - if (wid > -1) { - world = Bukkit.getServer().getWorld(WorldUtils.getWorldName(wid)); + Location location = player.getLocation().clone(); + World world = (wid > -1) ? Bukkit.getServer().getWorld(WorldUtils.getWorldName(wid)) : location.getWorld(); + if (world == null) { + Chat.sendMessage(player, new ChatMessage(Phrase.build(Phrase.WORLD_NOT_FOUND, worldName)).build()); + return null; } String x = null; @@ -68,56 +90,49 @@ protected static void runCommand(CommandSender player, boolean permission, Strin } if (x == null) { - x = args[i].replaceAll("[^0-9.\\-]", ""); + x = args[i]; } else if (z == null) { - z = args[i].replaceAll("[^0-9.\\-]", ""); + z = args[i]; } else if (y == null) { y = z; - z = args[i].replaceAll("[^0-9.\\-]", ""); + z = args[i]; } } if (y == null) { - if (location.getBlockY() > 63) { - location.setY(63); - } - y = Double.toString(location.getY()); + y = Double.toString(Math.max(63, location.getY())); + } + if (x == null || y == null || z == null) { + Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_PARAMETERS, "/co teleport ")); + return null; } + x = x.replaceAll("[^0-9.\\-]", ""); + y = y.replaceAll("[^0-9.\\-]", ""); + z = z.replaceAll("[^0-9.\\-]", ""); + String xValidate = x.replaceAll("[^.\\-]", ""); String yValidate = y.replaceAll("[^.\\-]", ""); String zValidate = z.replaceAll("[^.\\-]", ""); - if ((x.length() == 0 || x.length() >= 12 || x.equals(xValidate)) || (y.length() == 0 || y.length() >= 12 || y.equals(yValidate)) || (z.length() == 0 || z.length() >= 12 || z.equals(zValidate))) { + if ((x.isEmpty() || x.length() >= 12 || x.equals(xValidate)) || (y.isEmpty() || y.length() >= 12 || y.equals(yValidate)) || (z.isEmpty() || z.length() >= 12 || z.equals(zValidate))) { Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_PARAMETERS, "/co teleport ")); - return; + return null; } - location.setWorld(world); - location.setX(Double.parseDouble(x)); - location.setY(Double.parseDouble(y)); - location.setZ(Double.parseDouble(z)); - - if (ConfigHandler.isFolia) { - location.getWorld().getChunkAtAsync(location).thenAccept(chunk -> { - Scheduler.runTask(CoreProtect.getInstance(), () -> { - Teleport.performSafeTeleport(((Player) player), location, true); - }, location); - }); + try { + location.setWorld(world); + location.setX(Double.parseDouble(x)); + location.setY(Double.parseDouble(y)); + location.setZ(Double.parseDouble(z)); } - else { - int chunkX = location.getBlockX() >> 4; - int chunkZ = location.getBlockZ() >> 4; - Scheduler.runTask(CoreProtect.getInstance(), () -> { - if (!location.getWorld().isChunkLoaded(chunkX, chunkZ)) { - location.getWorld().getChunkAt(location); - } - Teleport.performSafeTeleport(((Player) player), location, true); - }, location); + catch (NumberFormatException e) { + Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_PARAMETERS, "/co teleport ")); + return null; } - ConfigHandler.teleportThrottle.put(player.getName(), new Object[] { false, System.currentTimeMillis() }); + return location; } } diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java index 871a0cab2..1340e2900 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java @@ -1,6 +1,8 @@ package net.coreprotect.database.rollback; import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -11,6 +13,7 @@ import org.bukkit.block.Banner; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; import org.bukkit.block.CommandBlock; import org.bukkit.block.CreatureSpawner; import org.bukkit.block.banner.Pattern; @@ -39,12 +42,14 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; import net.coreprotect.config.ConfigHandler; import net.coreprotect.consumer.Queue; import net.coreprotect.model.BlockGroup; import net.coreprotect.paper.PaperAdapter; import net.coreprotect.thread.CacheHandler; +import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.BlockUtils; import net.coreprotect.utility.ChestTool; import net.coreprotect.utility.EntityUtils; @@ -59,15 +64,16 @@ public static boolean processBlockChange(World bukkitWorld, Block block, Object[ try { if (changeBlock) { - /* If modifying the head of a piston, update the base piston block to prevent it from being destroyed */ + // folia: wrapped piston logic in a scheduler task if (changeBlockData instanceof PistonHead) { - PistonHead pistonHead = (PistonHead) changeBlockData; - Block pistonBlock = block.getRelative(pistonHead.getFacing().getOppositeFace()); + final PistonHead pistonHead = (PistonHead) changeBlockData; + final Block pistonBlock = block.getRelative(pistonHead.getFacing().getOppositeFace()); BlockData pistonData = pistonBlock.getBlockData(); if (pistonData instanceof Piston) { - Piston piston = (Piston) pistonData; + final Piston piston = (Piston) pistonData; piston.setExtended(false); - pistonBlock.setBlockData(piston, false); + Location pistonLocation = pistonBlock.getLocation(); + Scheduler.runTask(CoreProtect.getInstance(), () -> pistonBlock.setBlockData(piston, false), pistonLocation); } } else if (rowType == Material.MOVING_PISTON && blockData instanceof TechnicalPiston && !(blockData instanceof PistonHead)) { @@ -99,8 +105,11 @@ else if ((rowType == Material.ARMOR_STAND)) { } if (!exists) { - Entity entity = block.getLocation().getWorld().spawnEntity(location1, EntityType.ARMOR_STAND); - PaperAdapter.ADAPTER.teleportAsync(entity, location1); + // folia: wrapped entity spawn in a scheduler task + Scheduler.runTask(CoreProtect.getInstance(), () -> { + Entity entity = block.getLocation().getWorld().spawnEntity(location1, EntityType.ARMOR_STAND); + PaperAdapter.ADAPTER.teleportAsync(entity, location1); + }, location1); } } else if ((rowType == Material.END_CRYSTAL)) { @@ -118,10 +127,13 @@ else if ((rowType == Material.END_CRYSTAL)) { } if (!exists) { - Entity entity = block.getLocation().getWorld().spawnEntity(location1, BukkitAdapter.ADAPTER.getEntityType(Material.END_CRYSTAL)); - EnderCrystal enderCrystal = (EnderCrystal) entity; - enderCrystal.setShowingBottom((rowData != 0)); - PaperAdapter.ADAPTER.teleportAsync(entity, location1); + // folia: wrapped entity spawn in a scheduler task + Scheduler.runTask(CoreProtect.getInstance(), () -> { + Entity entity = block.getLocation().getWorld().spawnEntity(location1, BukkitAdapter.ADAPTER.getEntityType(Material.END_CRYSTAL)); + EnderCrystal enderCrystal = (EnderCrystal) entity; + enderCrystal.setShowingBottom((rowData != 0)); + PaperAdapter.ADAPTER.teleportAsync(entity, location1); + }, location1); } } else if ((rowType == Material.AIR) && ((oldTypeMaterial == Material.WATER))) { @@ -144,7 +156,9 @@ else if ((rowType == Material.AIR) && ((oldTypeMaterial == Material.END_CRYSTAL) for (Entity entity : block.getChunk().getEntities()) { if (entity instanceof EnderCrystal) { if (entity.getLocation().getBlockX() == rowX && entity.getLocation().getBlockY() == rowY && entity.getLocation().getBlockZ() == rowZ) { - entity.remove(); + // folia: wrapped entity removal in a scheduler task + Location entityLocation = entity.getLocation(); + Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entityLocation); } } } @@ -157,7 +171,8 @@ else if ((rowType == Material.AIR) || (rowType == Material.TNT)) { if (BlockGroup.CONTAINERS.contains(changeType)) { Inventory inventory = BlockUtils.getContainerInventory(block.getState(), false); if (inventory != null) { - inventory.clear(); + // folia: wrapped inventory clear in a scheduler task + Scheduler.runTask(CoreProtect.getInstance(), inventory::clear, block.getLocation()); } } else if (BlockGroup.CONTAINERS.contains(Material.ARMOR_STAND)) { @@ -172,7 +187,9 @@ else if (BlockGroup.CONTAINERS.contains(Material.ARMOR_STAND)) { entityLocation.setY(entityLocation.getY() - 1.99); PaperAdapter.ADAPTER.teleportAsync(entity, entityLocation); - entity.remove(); + + // folia: wrapped entity removal in a scheduler task + Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entityLocation); } } } @@ -202,7 +219,7 @@ else if ((pendingChangeType == Material.WATER)) { if (remove) { boolean physics = true; if ((changeType == Material.NETHER_PORTAL) || changeBlockData instanceof MultipleFacing || changeBlockData instanceof Snow || changeBlockData instanceof Stairs || changeBlockData instanceof RedstoneWire || changeBlockData instanceof Chest) { - physics = true; + // physics = true; } else if (changeBlockData instanceof Bisected && !(changeBlockData instanceof TrapDoor)) { Bisected bisected = (Bisected) changeBlockData; @@ -241,9 +258,14 @@ else if (changeBlockData instanceof Bed) { else if ((rowType == Material.SPAWNER)) { try { BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); - CreatureSpawner mobSpawner = (CreatureSpawner) block.getState(); - mobSpawner.setSpawnedType(EntityUtils.getSpawnerType(rowData)); - mobSpawner.update(); + + // folia: wrapped spawner update in a scheduler task + final Location blockLocation = block.getLocation(); + Scheduler.runTask(CoreProtect.getInstance(), () -> { + CreatureSpawner mobSpawner = (CreatureSpawner) block.getState(); + mobSpawner.setSpawnedType(EntityUtils.getSpawnerType(rowData)); + mobSpawner.update(); + }, blockLocation); return countBlock; } @@ -288,14 +310,18 @@ else if (rowType == Material.COMMAND_BLOCK || rowType == Material.REPEATING_COMM } if (meta != null) { - CommandBlock commandBlock = (CommandBlock) block.getState(); - for (Object value : meta) { - if (value instanceof String) { - String string = (String) value; - commandBlock.setCommand(string); - commandBlock.update(); + // folia: wrapped command block update in a scheduler task + final Location blockLocation = block.getLocation(); + Scheduler.runTask(CoreProtect.getInstance(), () -> { + CommandBlock commandBlock = (CommandBlock) block.getState(); + for (Object value : meta) { + if (value instanceof String) { + String string = (String) value; + commandBlock.setCommand(string); + commandBlock.update(); + } } - } + }, blockLocation); } return false; } @@ -319,39 +345,46 @@ else if (blockData == null && rowData > 0 && (rowType == Material.IRON_DOOR || B updateBlockCount(finalUserString, 1); } - block.setType(rowType, false); - Door door = (Door) block.getBlockData(); - if (rowData >= 8) { - door.setHalf(Half.TOP); - rowData = rowData - 8; - } - else { - door.setHalf(Half.BOTTOM); - } - if (rowData >= 4) { - door.setHinge(Hinge.RIGHT); - rowData = rowData - 4; - } - else { - door.setHinge(Hinge.LEFT); - } - BlockFace face = BlockFace.NORTH; - - switch (rowData) { - case 0: - face = BlockFace.EAST; - break; - case 1: - face = BlockFace.SOUTH; - break; - case 2: - face = BlockFace.WEST; - break; - } + // folia: wrapped door update in a scheduler task + final Location blockLocation = block.getLocation(); + final Material finalRowType = rowType; + final int finalRowData = rowData; + Scheduler.runTask(CoreProtect.getInstance(), () -> { + block.setType(finalRowType, false); + Door door = (Door) block.getBlockData(); + int data = finalRowData; + if (data >= 8) { + door.setHalf(Half.TOP); + data = data - 8; + } + else { + door.setHalf(Half.BOTTOM); + } + if (data >= 4) { + door.setHinge(Hinge.RIGHT); + data = data - 4; + } + else { + door.setHinge(Hinge.LEFT); + } + BlockFace face = BlockFace.NORTH; + + switch (data) { + case 0: + face = BlockFace.EAST; + break; + case 1: + face = BlockFace.SOUTH; + break; + case 2: + face = BlockFace.WEST; + break; + } - door.setFacing(face); - door.setOpen(false); - block.setBlockData(door, false); + door.setFacing(face); + door.setOpen(false); + block.setBlockData(door, false); + }, blockLocation); return false; } else if (blockData == null && rowData > 0 && (rowType.name().endsWith("_BED"))) { @@ -359,29 +392,36 @@ else if (blockData == null && rowData > 0 && (rowType.name().endsWith("_BED"))) updateBlockCount(finalUserString, 1); } - block.setType(rowType, false); - Bed bed = (Bed) block.getBlockData(); - BlockFace face = BlockFace.NORTH; + // folia: wrapped bed update in a scheduler task + final Location blockLocation = block.getLocation(); + final Material finalRowType = rowType; + final int finalRowData = rowData; + Scheduler.runTask(CoreProtect.getInstance(), () -> { + block.setType(finalRowType, false); + Bed bed = (Bed) block.getBlockData(); + BlockFace face = BlockFace.NORTH; + int data = finalRowData; - if (rowData > 4) { - bed.setPart(Part.HEAD); - rowData = rowData - 4; - } + if (data > 4) { + bed.setPart(Part.HEAD); + data = data - 4; + } - switch (rowData) { - case 2: - face = BlockFace.WEST; - break; - case 3: - face = BlockFace.EAST; - break; - case 4: - face = BlockFace.SOUTH; - break; - } + switch (data) { + case 2: + face = BlockFace.WEST; + break; + case 3: + face = BlockFace.EAST; + break; + case 4: + face = BlockFace.SOUTH; + break; + } - bed.setFacing(face); - block.setBlockData(bed, false); + bed.setFacing(face); + block.setBlockData(bed, false); + }, blockLocation); return false; } else if (rowType.name().endsWith("_BANNER")) { @@ -391,25 +431,31 @@ else if (rowType.name().endsWith("_BANNER")) { } if (meta != null) { - Banner banner = (Banner) block.getState(); - - for (Object value : meta) { - if (value instanceof DyeColor) { - banner.setBaseColor((DyeColor) value); - } - else if (value instanceof Map) { - @SuppressWarnings("unchecked") - Pattern pattern = new Pattern((Map) value); - banner.addPattern(pattern); + // folia: wrapped banner update in a scheduler task + final Location blockLocation = block.getLocation(); + Scheduler.runTask(CoreProtect.getInstance(), () -> { + Banner banner = (Banner) block.getState(); + + for (Object value : meta) { + if (value instanceof DyeColor) { + banner.setBaseColor((DyeColor) value); + } + else if (value instanceof Map) { + @SuppressWarnings("unchecked") + Pattern pattern = new Pattern((Map) value); + banner.addPattern(pattern); + } } - } - banner.update(); + banner.update(); + }, blockLocation); } return false; } else if (rowType != changeType && (BlockGroup.CONTAINERS.contains(rowType) || BlockGroup.CONTAINERS.contains(changeType))) { - block.setType(Material.AIR); // Clear existing container to prevent errors + // folia: wrapped container clearing in a scheduler task + final Location blockLocation = block.getLocation(); + Scheduler.runTask(CoreProtect.getInstance(), () -> block.setType(Material.AIR), blockLocation); boolean isChest = (blockData instanceof Chest); BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, (isChest)); @@ -424,7 +470,7 @@ else if (BlockGroup.UPDATE_STATE.contains(rowType) || rowType.name().contains("C ChestTool.updateDoubleChest(block, blockData, true); return countBlock; } - else if (rowType != Material.AIR && rawBlockData instanceof Bisected && !(rawBlockData instanceof Stairs || rawBlockData instanceof TrapDoor)) { + else if (rawBlockData instanceof Bisected && !(rawBlockData instanceof Stairs || rawBlockData instanceof TrapDoor)) { Bisected bisected = (Bisected) rawBlockData; Bisected bisectData = (Bisected) rawBlockData.clone(); Location bisectLocation = block.getLocation().clone(); @@ -450,7 +496,7 @@ else if (rowType != Material.AIR && rawBlockData instanceof Bisected && !(rawBlo } return false; } - else if (rowType != Material.AIR && rawBlockData instanceof Bed) { + else if (rawBlockData instanceof Bed) { Bed bed = (Bed) rawBlockData; if (bed.getPart() == Part.FOOT) { Block adjacentBlock = block.getRelative(bed.getFacing()); @@ -467,11 +513,6 @@ else if (rowType != Material.AIR && rawBlockData instanceof Bed) { } else { boolean physics = true; - /* - if (blockData instanceof MultipleFacing || BukkitAdapter.ADAPTER.isWall(blockData) || blockData instanceof Snow || blockData instanceof Stairs || blockData instanceof RedstoneWire || blockData instanceof Chest) { - physics = !(blockData instanceof Snow) || block.getY() <= BukkitAdapter.ADAPTER.getMinHeight(block.getWorld()) || (block.getWorld().getBlockAt(block.getX(), block.getY() - 1, block.getZ()).getType().equals(Material.GRASS_BLOCK)); - } - */ BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, physics); return countBlock; } @@ -481,8 +522,8 @@ else if (rowType != Material.AIR && rawBlockData instanceof Bed) { e.printStackTrace(); } - if ((rowType != Material.AIR) && changeBlock) { - if (rowUser.length() > 0) { + if (changeBlock) { + if (!rowUser.isEmpty()) { CacheHandler.lookupCache.put(rowX + "." + rowY + "." + rowZ + "." + rowWorldId, new Object[] { unixtimestamp, rowUser, rowType }); } } @@ -492,7 +533,7 @@ else if (rowType != Material.AIR && rawBlockData instanceof Bed) { /** * Update the block count in the rollback hash - * + * * @param userString * The username for this rollback * @param increment @@ -511,7 +552,7 @@ protected static void updateBlockCount(String userString, int increment) { /** * Apply all pending block changes to the world - * + * * @param chunkChanges * Map of blocks to change * @param preview diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java index c6357604c..c6b360ea1 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java @@ -8,8 +8,10 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; +import net.coreprotect.CoreProtect; import net.coreprotect.config.ConfigHandler; import net.coreprotect.thread.CacheHandler; +import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.EntityUtils; import net.coreprotect.utility.WorldUtils; @@ -18,16 +20,12 @@ public class RollbackEntityHandler { /** * Processes an entity-related rollback operation. * - * @param row - * The database row containing entity data (used only for specific operations) * @param rollbackType * The type of rollback (0 for rollback, 1 for restore) * @param finalUserString * The user string for tracking operations * @param oldTypeRaw * The old raw type value - * @param rowTypeRaw - * The raw type value * @param rowData * The data value * @param rowAction @@ -42,55 +40,45 @@ public class RollbackEntityHandler { * The Z coordinate * @param rowWorldId * The world ID - * @param rowUserId - * The user ID * @param rowUser * The username associated with this entity change * @return The number of entities affected (1 if successful, 0 otherwise) */ - public static int processEntity(Object[] row, int rollbackType, String finalUserString, int oldTypeRaw, int rowTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, int rowUserId, String rowUser) { + + public static int processEntity(int rollbackType, String finalUserString, int oldTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, String rowUser) { World bukkitWorld = Bukkit.getServer().getWorld(WorldUtils.getWorldName(rowWorldId)); if (bukkitWorld == null) { return 0; } + Location entityLocation = new Location(bukkitWorld, rowX, rowY, rowZ); + if (ConfigHandler.isFolia) { - // Folia - load chunk async before processing - bukkitWorld.getChunkAtAsync(rowX >> 4, rowZ >> 4, true).thenAccept(chunk -> { - processEntityLogic(row, rollbackType, finalUserString, oldTypeRaw, rowTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUserId, rowUser, bukkitWorld); - }); + // folia: load chunk async before processing, then regionize the entity operations + Scheduler.runTask(CoreProtect.getInstance(), () -> processEntityLogic(finalUserString, oldTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUser, bukkitWorld), entityLocation); + return 1; // assume task is queued successfully } else { if (!bukkitWorld.isChunkLoaded(rowX >> 4, rowZ >> 4)) { bukkitWorld.getChunkAt(rowX >> 4, rowZ >> 4); } - return processEntityLogic(row, rollbackType, finalUserString, oldTypeRaw, rowTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUserId, rowUser, bukkitWorld); + return processEntityLogic(finalUserString, oldTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUser, bukkitWorld); } } - private static int processEntityLogic(Object[] row, int rollbackType, String finalUserString, int oldTypeRaw, int rowTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, int rowUserId, String rowUser, World bukkitWorld) { + private static int processEntityLogic(String finalUserString, int oldTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, String rowUser, World bukkitWorld) { try { // Entity kill if (rowAction == 3) { Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); - if (rowTypeRaw > 0) { - // Spawn in entity - if (rowRolledBack == 0) { - EntityType entityType = EntityUtils.getEntityType(rowTypeRaw); - // Use the spawnEntity method from the RollbackUtil class instead of Queue - spawnEntity(rowUser, block.getState(), entityType, rowData); - updateEntityCount(finalUserString, 1); - return 1; - } - } - else if (rowTypeRaw <= 0) { + if (oldTypeRaw <= 0) { // Attempt to remove entity if (rowRolledBack == 1) { boolean removed = false; int entityId = -1; String entityName = EntityUtils.getEntityType(oldTypeRaw).name(); - String token = "" + rowX + "." + rowY + "." + rowZ + "." + rowWorldId + "." + entityName + ""; + String token = String.valueOf(rowX) + "." + rowY + "." + rowZ + "." + rowWorldId + "." + entityName; Object[] cachedEntity = CacheHandler.entityCache.get(token); if (cachedEntity != null) { @@ -110,7 +98,8 @@ else if (rowTypeRaw <= 0) { if (id == entityId) { updateEntityCount(finalUserString, 1); removed = true; - entity.remove(); + // folia: wrapped entity removal in a scheduler task + Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); break; } } @@ -124,7 +113,8 @@ else if (rowTypeRaw <= 0) { if (entityx >= xmin && entityx <= xmax && entityY >= ymin && entityY <= ymax && entityZ >= zmin && entityZ <= zmax) { updateEntityCount(finalUserString, 1); removed = true; - entity.remove(); + // folia: wrapped entity removal in a scheduler task + Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); break; } } @@ -137,7 +127,8 @@ else if (rowTypeRaw <= 0) { if (id == entityId) { updateEntityCount(finalUserString, 1); removed = true; - entity.remove(); + // folia: wrapped entity removal in a scheduler task + Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); break; } } @@ -148,6 +139,16 @@ else if (rowTypeRaw <= 0) { } } } + else { + // Spawn in entity + if (rowRolledBack == 0) { + EntityType entityType = EntityUtils.getEntityType(oldTypeRaw); + // Use the spawnEntity method from the RollbackUtil class instead of Queue + spawnEntity(rowUser, block.getState(), entityType, rowData); + updateEntityCount(finalUserString, 1); + return 1; + } + } } } catch (Exception e) { @@ -157,17 +158,6 @@ else if (rowTypeRaw <= 0) { return 0; } - /** - * Gets the world name from a world ID. - * - * @param worldId - * The world ID - * @return The world name - */ - private static String getWorldName(int worldId) { - return WorldUtils.getWorldName(worldId); - } - /** * Updates the entity count in the rollback hash for a specific user. * diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java index fa3b42e97..7a9051592 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java @@ -18,7 +18,6 @@ import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.type.Jukebox; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Entity; import org.bukkit.entity.ItemFrame; @@ -26,11 +25,14 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; import net.coreprotect.config.Config; import net.coreprotect.config.ConfigHandler; +import net.coreprotect.consumer.Queue; import net.coreprotect.database.logger.ItemLogger; import net.coreprotect.model.BlockGroup; +import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.BlockUtils; import net.coreprotect.utility.ItemUtils; import net.coreprotect.utility.MaterialUtils; @@ -39,54 +41,48 @@ public class RollbackProcessor { - /** - * Process data for a specific chunk - * - * @param finalChunkX - * The chunk X coordinate - * @param finalChunkZ - * The chunk Z coordinate - * @param chunkKey - * The chunk lookup key - * @param blockList - * The list of block data to process - * @param itemList - * The list of item data to process - * @param rollbackType - * The rollback type (0=rollback, 1=restore) - * @param preview - * Whether this is a preview (0=no, 1=yes-non-destructive, 2=yes-destructive) - * @param finalUserString - * The username performing the rollback - * @param finalUser - * The user performing the rollback - * @param bukkitRollbackWorld - * The world to process - * @return A CompletableFuture that completes with true if successful, false otherwise - */ public static CompletableFuture processChunk(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { if (ConfigHandler.isFolia) { - // Folia - load chunk async before processing - return bukkitRollbackWorld.getChunkAtAsync(finalChunkX, finalChunkZ, true).thenApply(chunk -> { - return processChunkLogic(finalChunkX, finalChunkZ, chunkKey, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); + return bukkitRollbackWorld.getChunkAtAsync(finalChunkX, finalChunkZ, true).thenCompose(chunk -> { + CompletableFuture future = new CompletableFuture<>(); + + Location chunkLocation = new Location(bukkitRollbackWorld, finalChunkX << 4, 64, finalChunkZ << 4); + + Scheduler.runTask(CoreProtect.getInstance(), () -> { + try { + boolean result = processChunkLogic(finalChunkX, finalChunkZ, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); + future.complete(result); + } catch (Exception e) { + e.printStackTrace(); + future.complete(false); + } + }, chunkLocation); + + return future; }); } else { - try { - if (!bukkitRollbackWorld.isChunkLoaded(finalChunkX, finalChunkZ)) { - bukkitRollbackWorld.getChunkAt(finalChunkX, finalChunkZ); + CompletableFuture future = new CompletableFuture<>(); + + Scheduler.runTask(CoreProtect.getInstance(), () -> { + try { + if (!bukkitRollbackWorld.isChunkLoaded(finalChunkX, finalChunkZ)) { + bukkitRollbackWorld.getChunkAt(finalChunkX, finalChunkZ); + } + boolean result = processChunkLogic(finalChunkX, finalChunkZ, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); + future.complete(result); } - boolean result = processChunkLogic(finalChunkX, finalChunkZ, chunkKey, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); - return CompletableFuture.completedFuture(result); - } - catch (Exception e) { - e.printStackTrace(); - return CompletableFuture.completedFuture(false); - } + catch (Exception e) { + e.printStackTrace(); + future.complete(false); + } + }); + + return future; } } - private static boolean processChunkLogic(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { + private static boolean processChunkLogic(int finalChunkX, int finalChunkZ, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { try { boolean clearInventories = Config.getGlobal().ROLLBACK_ITEMS; ArrayList data = blockList != null ? blockList : new ArrayList<>(); @@ -119,7 +115,7 @@ private static boolean processChunkLogic(int finalChunkX, int finalChunkZ, long } BlockData blockData = null; - if (blockDataString != null && blockDataString.contains(":")) { + if (blockDataString != null && !blockDataString.isEmpty()) { try { blockData = Bukkit.getServer().createBlockData(blockDataString); } @@ -143,25 +139,21 @@ private static boolean processChunkLogic(int finalChunkX, int finalChunkZ, long if (rowAction == 1 && rollbackType == 0) { // block placement rowType = Material.AIR; blockData = null; - rowTypeRaw = 0; } else if (rowAction == 0 && rollbackType == 1) { // block removal rowType = Material.AIR; blockData = null; - rowTypeRaw = 0; } else if (rowAction == 4 && rollbackType == 0) { // entity placement rowType = null; - rowTypeRaw = 0; } else if (rowAction == 3 && rollbackType == 1) { // entity removal rowType = null; - rowTypeRaw = 0; } if (preview > 0) { if (rowAction != 3) { // entity kill String world = WorldUtils.getWorldName(rowWorldId); - if (world.length() == 0) { + if (world.isEmpty()) { continue; } @@ -173,13 +165,13 @@ else if (rowAction == 3 && rollbackType == 1) { // entity removal Block block = new Location(bukkitWorld, rowX, rowY, rowZ).getBlock(); if (preview == 2) { Material blockType = block.getType(); - if (!BukkitAdapter.ADAPTER.isItemFrame(blockType) && !blockType.equals(Material.PAINTING) && !blockType.equals(Material.ARMOR_STAND) && !blockType.equals(Material.END_CRYSTAL)) { + if (!net.coreprotect.bukkit.BukkitAdapter.ADAPTER.isItemFrame(blockType) && !blockType.equals(Material.PAINTING) && !blockType.equals(Material.ARMOR_STAND) && !blockType.equals(Material.END_CRYSTAL)) { BlockUtils.prepareTypeAndData(chunkChanges, block, blockType, block.getBlockData(), true); blockCount++; } } else { - if ((!BukkitAdapter.ADAPTER.isItemFrame(rowType)) && (rowType != Material.PAINTING) && (rowType != Material.ARMOR_STAND) && (rowType != Material.END_CRYSTAL)) { + if (rowType != null && (!net.coreprotect.bukkit.BukkitAdapter.ADAPTER.isItemFrame(rowType)) && (rowType != Material.PAINTING) && (rowType != Material.ARMOR_STAND) && (rowType != Material.END_CRYSTAL)) { BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, true); blockCount++; } @@ -190,11 +182,11 @@ else if (rowAction == 3 && rollbackType == 1) { // entity removal } } else if (rowAction == 3) { // entity kill - entityCount += RollbackEntityHandler.processEntity(row, rollbackType, finalUserString, oldTypeRaw, rowTypeRaw, rowData, rowAction, MaterialUtils.rolledBack((Integer) row[9], false), rowX, rowY, rowZ, rowWorldId, (Integer) row[2], rowUser); + entityCount += RollbackEntityHandler.processEntity(rollbackType, finalUserString, oldTypeRaw, rowData, rowAction, MaterialUtils.rolledBack((Integer) row[9], false), rowX, rowY, rowZ, rowWorldId, rowUser); } else { String world = WorldUtils.getWorldName(rowWorldId); - if (world.length() == 0) { + if (world.isEmpty()) { continue; } @@ -218,16 +210,16 @@ else if (rowAction == 3) { // entity kill pendingChangeData = changeBlockData; } - if (rowRolledBack == 1 && rollbackType == 0) { // rollback + if (MaterialUtils.rolledBack((Integer) row[9], false) == 1 && rollbackType == 0) { // rollback countBlock = false; } - if ((rowType == pendingChangeType) && ((!BukkitAdapter.ADAPTER.isItemFrame(oldTypeMaterial)) && (oldTypeMaterial != Material.PAINTING) && (oldTypeMaterial != Material.ARMOR_STAND)) && (oldTypeMaterial != Material.END_CRYSTAL)) { + if ((rowType == pendingChangeType) && ((!net.coreprotect.bukkit.BukkitAdapter.ADAPTER.isItemFrame(oldTypeMaterial)) && (oldTypeMaterial != Material.PAINTING) && (oldTypeMaterial != Material.ARMOR_STAND)) && (oldTypeMaterial != Material.END_CRYSTAL)) { // block is already changed! BlockData checkData = rowType == Material.AIR ? blockData : rawBlockData; if (checkData != null) { if (checkData.getAsString().equals(pendingChangeData.getAsString()) || checkData instanceof org.bukkit.block.data.MultipleFacing || checkData instanceof org.bukkit.block.data.type.Stairs || checkData instanceof org.bukkit.block.data.type.RedstoneWire) { - if (rowType != Material.CHEST && rowType != Material.TRAPPED_CHEST && !BukkitAdapter.ADAPTER.isCopperChest(rowType)) { // always update double chests + if (rowType != Material.CHEST && rowType != Material.TRAPPED_CHEST && !net.coreprotect.bukkit.BukkitAdapter.ADAPTER.isCopperChest(rowType)) { // always update double chests changeBlock = false; } } @@ -284,9 +276,7 @@ else if ((pendingChangeType != Material.AIR) && (pendingChangeType != Material.C int rowY = (Integer) row[4]; int rowZ = (Integer) row[5]; int rowTypeRaw = (Integer) row[6]; - int rowData = (Integer) row[7]; int rowAction = (Integer) row[8]; - int rowRolledBack = MaterialUtils.rolledBack((Integer) row[9], false); int rowWorldId = (Integer) row[10]; int rowAmount = (Integer) row[11]; byte[] rowMetadata = (byte[]) row[12]; @@ -312,7 +302,7 @@ else if ((pendingChangeType != Material.AIR) && (pendingChangeType != Material.C continue; } - int inventoryAction = 0; + int inventoryAction; if (rowAction == ItemLogger.ITEM_DROP || rowAction == ItemLogger.ITEM_PICKUP || rowAction == ItemLogger.ITEM_THROW || rowAction == ItemLogger.ITEM_SHOOT || rowAction == ItemLogger.ITEM_BREAK || rowAction == ItemLogger.ITEM_DESTROY || rowAction == ItemLogger.ITEM_CREATE || rowAction == ItemLogger.ITEM_SELL || rowAction == ItemLogger.ITEM_BUY) { inventoryAction = ((rowAction == ItemLogger.ITEM_PICKUP || rowAction == ItemLogger.ITEM_CREATE || rowAction == ItemLogger.ITEM_BUY) ? 1 : 0); } @@ -347,7 +337,7 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT continue; // skip inventory & ender chest transactions } - if ((rollbackType == 0 && rowRolledBack == 0) || (rollbackType == 1 && rowRolledBack == 1)) { + if ((rollbackType == 0 && MaterialUtils.rolledBack((Integer) row[9], false) == 0) || (rollbackType == 1 && MaterialUtils.rolledBack((Integer) row[9], false) == 1)) { ItemStack itemstack = new ItemStack(rowType, rowAmount); Object[] populatedStack = RollbackItemHandler.populateItemStack(itemstack, rowMetadata); String faceData = (String) populatedStack[1]; @@ -355,7 +345,7 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT if (!containerInit || rowX != lastX || rowY != lastY || rowZ != lastZ || rowWorldId != lastWorldId || !faceData.equals(lastFace)) { container = null; // container patch 2.14.0 String world = WorldUtils.getWorldName(rowWorldId); - if (world.length() == 0) { + if (world.isEmpty()) { continue; } @@ -367,7 +357,7 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT if (BlockGroup.CONTAINERS.contains(block.getType())) { BlockState blockState = block.getState(); - if (blockState instanceof Jukebox) { + if (blockState instanceof org.bukkit.block.Jukebox) { container = blockState; } else { @@ -386,7 +376,7 @@ else if (BlockGroup.CONTAINERS.contains(Material.ARMOR_STAND) || BlockGroup.CONT else if (entity instanceof ItemFrame) { container = entity; containerType = Material.ITEM_FRAME; - if (faceData.length() > 0 && (BlockFace.valueOf(faceData) == ((ItemFrame) entity).getFacing())) { + if (!faceData.isEmpty() && (BlockFace.valueOf(faceData) == ((ItemFrame) entity).getFacing())) { break; } } diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackUtil.java b/src/main/java/net/coreprotect/database/rollback/RollbackUtil.java index e9268c32c..41a79e2a7 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackUtil.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackUtil.java @@ -18,6 +18,7 @@ import org.bukkit.entity.ItemFrame; import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.meta.BannerMeta; @@ -44,7 +45,7 @@ public class RollbackUtil extends Lookup { protected static int modifyContainerItems(Material type, Object container, int slot, ItemStack itemstack, int action) { int modifiedArmor = -1; try { - ItemStack[] contents = null; + ItemStack[] contents; if (type != null && type.equals(Material.ARMOR_STAND)) { EntityEquipment equipment = (EntityEquipment) container; @@ -66,7 +67,9 @@ protected static int modifyContainerItems(Material type, Object container, int s } else { ArmorStand armorStand = (ArmorStand) equipment.getHolder(); - armorStand.setArms(true); + if (armorStand != null) { + armorStand.setArms(true); + } switch (slot) { case 4: equipment.setItemInMainHand(itemstack); @@ -93,8 +96,9 @@ else if (type != null && type.equals(Material.ITEM_FRAME)) { } } else if (type != null && type.equals(Material.JUKEBOX)) { - Jukebox jukebox = (Jukebox) container; - if (jukebox != null) { + // fix: ClassCastException: handle both jukebox block state and jukebox inventory + if (container instanceof Jukebox) { + Jukebox jukebox = (Jukebox) container; if (action == 1 && itemstack.getType().name().startsWith("MUSIC_DISC")) { itemstack.setAmount(1); } @@ -106,6 +110,23 @@ else if (type != null && type.equals(Material.JUKEBOX)) { jukebox.setRecord(itemstack); jukebox.update(); } + else if (container instanceof Inventory) { + // handle case where container is actually the jukebox inventory + Inventory inventory = (Inventory) container; + InventoryHolder holder = inventory.getHolder(); + if (holder instanceof Jukebox) { + Jukebox jukebox = (Jukebox) holder; + if (action == 1 && itemstack.getType().name().startsWith("MUSIC_DISC")) { + itemstack.setAmount(1); + } + else { + itemstack.setType(Material.AIR); + itemstack.setAmount(0); + } + jukebox.setRecord(itemstack); + jukebox.update(); + } + } } else { Inventory inventory = (Inventory) container; @@ -139,17 +160,17 @@ else if (type != null && type.equals(Material.JUKEBOX)) { inventory.setStorageContents(inventoryContents); } else { - addedItem = (inventory.addItem(itemstack).size() == 0); + addedItem = (inventory.addItem(itemstack).isEmpty()); } } else { - addedItem = (inventory.addItem(itemstack).size() == 0); + addedItem = (inventory.addItem(itemstack).isEmpty()); } } if (!addedItem && isPlayerInventory) { PlayerInventory playerInventory = (PlayerInventory) inventory; ItemStack offhand = playerInventory.getItemInOffHand(); - if (offhand == null || offhand.getType() == Material.AIR || (itemstack.isSimilar(offhand) && offhand.getAmount() < offhand.getMaxStackSize())) { + if (offhand.getType() == Material.AIR || (itemstack.isSimilar(offhand) && offhand.getAmount() < offhand.getMaxStackSize())) { ItemStack setOffhand = itemstack.clone(); if (itemstack.isSimilar(offhand)) { setOffhand.setAmount(offhand.getAmount() + 1); @@ -260,13 +281,17 @@ private static void buildFireworkEffect(Builder effectBuilder, Material rowType, FireworkEffect effect = effectBuilder.build(); if ((rowType == Material.FIREWORK_ROCKET)) { FireworkMeta meta = (FireworkMeta) itemstack.getItemMeta(); - meta.addEffect(effect); - itemstack.setItemMeta(meta); + if (meta != null) { + meta.addEffect(effect); + itemstack.setItemMeta(meta); + } } else if ((rowType == Material.FIREWORK_STAR)) { FireworkEffectMeta meta = (FireworkEffectMeta) itemstack.getItemMeta(); - meta.setEffect(effect); - itemstack.setItemMeta(meta); + if (meta != null) { + meta.setEffect(effect); + itemstack.setItemMeta(meta); + } } } catch (Exception e) { @@ -291,28 +316,32 @@ public static Object[] populateItemStack(ItemStack itemstack, Object list) { Material rowType = itemstack.getType(); List metaList = (List) list; - if (metaList.size() > 0 && !(metaList.get(0) instanceof List)) { + if (metaList != null && !metaList.isEmpty() && !(metaList.get(0) instanceof List)) { if (rowType.name().endsWith("_BANNER")) { BannerMeta meta = (BannerMeta) itemstack.getItemMeta(); - for (Object value : metaList) { - if (value instanceof Map) { - Pattern pattern = new Pattern((Map) value); - meta.addPattern(pattern); + if (meta != null) { + for (Object value : metaList) { + if (value instanceof Map) { + Pattern pattern = new Pattern((Map) value); + meta.addPattern(pattern); + } } + itemstack.setItemMeta(meta); } - itemstack.setItemMeta(meta); } else if (BlockGroup.SHULKER_BOXES.contains(rowType)) { BlockStateMeta meta = (BlockStateMeta) itemstack.getItemMeta(); - ShulkerBox shulkerBox = (ShulkerBox) meta.getBlockState(); - for (Object value : metaList) { - ItemStack item = ItemUtils.unserializeItemStackLegacy(value); - if (item != null) { - shulkerBox.getInventory().addItem(item); + if (meta != null) { + ShulkerBox shulkerBox = (ShulkerBox) meta.getBlockState(); + for (Object value : metaList) { + ItemStack item = ItemUtils.unserializeItemStackLegacy(value); + if (item != null) { + shulkerBox.getInventory().addItem(item); + } } + meta.setBlockState(shulkerBox); + itemstack.setItemMeta(meta); } - meta.setBlockState(shulkerBox); - itemstack.setItemMeta(meta); } return new Object[] { slot, faceData, itemstack }; @@ -320,152 +349,168 @@ else if (BlockGroup.SHULKER_BOXES.contains(rowType)) { int itemCount = 0; Builder effectBuilder = FireworkEffect.builder(); - for (List> map : (List>>) list) { - if (map.size() == 0) { - if (itemCount == 3 && (rowType == Material.FIREWORK_ROCKET || rowType == Material.FIREWORK_STAR)) { - buildFireworkEffect(effectBuilder, rowType, itemstack); - itemCount = 0; - } + if (list instanceof List) { + for (List> map : (List>>) list) { + if (map.isEmpty()) { + if (itemCount == 3 && (rowType == Material.FIREWORK_ROCKET || rowType == Material.FIREWORK_STAR)) { + buildFireworkEffect(effectBuilder, rowType, itemstack); + itemCount = 0; + } - itemCount++; - continue; - } - Map mapData = map.get(0); + itemCount++; + continue; + } + Map mapData = map.get(0); - if (mapData.get("slot") != null) { - slot = (Integer) mapData.get("slot"); - } - else if (mapData.get("facing") != null) { - faceData = (String) mapData.get("facing"); - } - else if (mapData.get("modifiers") != null) { - ItemMeta itemMeta = itemstack.getItemMeta(); - if (itemMeta.hasAttributeModifiers()) { - for (Map.Entry entry : itemMeta.getAttributeModifiers().entries()) { - itemMeta.removeAttributeModifier(entry.getKey(), entry.getValue()); - } + if (mapData.get("slot") != null) { + slot = (Integer) mapData.get("slot"); + } + else if (mapData.get("facing") != null) { + faceData = (String) mapData.get("facing"); } + else if (mapData.get("modifiers") != null) { + ItemMeta itemMeta = itemstack.getItemMeta(); + if (itemMeta != null && itemMeta.hasAttributeModifiers()) { + for (Map.Entry entry : itemMeta.getAttributeModifiers().entries()) { + itemMeta.removeAttributeModifier(entry.getKey(), entry.getValue()); + } + } + + List modifiers = (List) mapData.get("modifiers"); - List modifiers = (List) mapData.get("modifiers"); + for (Object item : modifiers) { + Map> modifiersMap = (Map>) item; + for (Map.Entry> entry : modifiersMap.entrySet()) { + try { + Attribute attribute = null; + if (entry.getKey() instanceof Attribute) { + attribute = (Attribute) entry.getKey(); + } + else { + attribute = (Attribute) BukkitAdapter.ADAPTER.getRegistryValue((String) entry.getKey(), Attribute.class); + } - for (Object item : modifiers) { - Map> modifiersMap = (Map>) item; - for (Map.Entry> entry : modifiersMap.entrySet()) { - try { - Attribute attribute = null; - if (entry.getKey() instanceof Attribute) { - attribute = (Attribute) entry.getKey(); + AttributeModifier modifier = AttributeModifier.deserialize(entry.getValue()); + if (itemMeta != null && attribute != null) { + itemMeta.addAttributeModifier(attribute, modifier); + } } - else { - attribute = (Attribute) BukkitAdapter.ADAPTER.getRegistryValue((String) entry.getKey(), Attribute.class); + catch (IllegalArgumentException e) { + // AttributeModifier already exists } - - AttributeModifier modifier = AttributeModifier.deserialize(entry.getValue()); - itemMeta.addAttributeModifier(attribute, modifier); - } - catch (IllegalArgumentException e) { - // AttributeModifier already exists } } - } - - itemstack.setItemMeta(itemMeta); - } - else if (itemCount == 0) { - ItemMeta meta = ItemUtils.deserializeItemMeta(itemstack.getItemMeta().getClass(), map.get(0)); - itemstack.setItemMeta(meta); - if (map.size() > 1 && (rowType == Material.POTION)) { - PotionMeta subMeta = (PotionMeta) itemstack.getItemMeta(); - org.bukkit.Color color = org.bukkit.Color.deserialize(map.get(1)); - subMeta.setColor(color); - itemstack.setItemMeta(subMeta); - } - } - else { - if ((rowType == Material.LEATHER_HORSE_ARMOR) || (rowType == Material.LEATHER_HELMET) || (rowType == Material.LEATHER_CHESTPLATE) || (rowType == Material.LEATHER_LEGGINGS) || (rowType == Material.LEATHER_BOOTS)) { // leather armor - for (Map colorData : map) { - LeatherArmorMeta meta = (LeatherArmorMeta) itemstack.getItemMeta(); - org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); - meta.setColor(color); - itemstack.setItemMeta(meta); - } + itemstack.setItemMeta(itemMeta); } - else if ((rowType == Material.POTION)) { // potion - for (Map potionData : map) { - PotionMeta meta = (PotionMeta) itemstack.getItemMeta(); - PotionEffect effect = new PotionEffect(potionData); - meta.addCustomEffect(effect, true); - itemstack.setItemMeta(meta); + else if (itemCount == 0) { + ItemMeta meta = ItemUtils.deserializeItemMeta(itemstack.getItemMeta().getClass(), map.get(0)); + itemstack.setItemMeta(meta); + + if (map.size() > 1 && (rowType == Material.POTION)) { + PotionMeta subMeta = (PotionMeta) itemstack.getItemMeta(); + if (subMeta != null) { + org.bukkit.Color color = org.bukkit.Color.deserialize(map.get(1)); + subMeta.setColor(color); + itemstack.setItemMeta(subMeta); + } } } - else if (rowType.name().endsWith("_BANNER")) { - for (Map patternData : map) { - BannerMeta meta = (BannerMeta) itemstack.getItemMeta(); - Pattern pattern = new Pattern(patternData); - meta.addPattern(pattern); - itemstack.setItemMeta(meta); + else { + if ((rowType == Material.LEATHER_HORSE_ARMOR) || (rowType == Material.LEATHER_HELMET) || (rowType == Material.LEATHER_CHESTPLATE) || (rowType == Material.LEATHER_LEGGINGS) || (rowType == Material.LEATHER_BOOTS)) { // leather armor + for (Map colorData : map) { + LeatherArmorMeta meta = (LeatherArmorMeta) itemstack.getItemMeta(); + org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); + meta.setColor(color); + itemstack.setItemMeta(meta); + } } - } - else if ((rowType == Material.CROSSBOW)) { - CrossbowMeta meta = (CrossbowMeta) itemstack.getItemMeta(); - for (Map itemData : map) { - ItemStack crossbowItem = ItemUtils.unserializeItemStack(itemData); - if (crossbowItem != null) { - meta.addChargedProjectile(crossbowItem); + else if ((rowType == Material.POTION)) { // potion + for (Map potionData : map) { + PotionMeta meta = (PotionMeta) itemstack.getItemMeta(); + PotionEffect effect = new PotionEffect(potionData); + if (meta != null) { + meta.addCustomEffect(effect, true); + itemstack.setItemMeta(meta); + } } } - itemstack.setItemMeta(meta); - } - else if (rowType == Material.MAP || rowType == Material.FILLED_MAP) { - for (Map colorData : map) { - MapMeta meta = (MapMeta) itemstack.getItemMeta(); - org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); - meta.setColor(color); - itemstack.setItemMeta(meta); + else if (rowType.name().endsWith("_BANNER")) { + for (Map patternData : map) { + BannerMeta meta = (BannerMeta) itemstack.getItemMeta(); + Pattern pattern = new Pattern(patternData); + if (meta != null) { + meta.addPattern(pattern); + itemstack.setItemMeta(meta); + } + } } - } - else if ((rowType == Material.FIREWORK_ROCKET) || (rowType == Material.FIREWORK_STAR)) { - if (itemCount == 1) { - effectBuilder = FireworkEffect.builder(); - for (Map fireworkData : map) { - org.bukkit.FireworkEffect.Type type = (org.bukkit.FireworkEffect.Type) fireworkData.getOrDefault("type", org.bukkit.FireworkEffect.Type.BALL); - boolean hasFlicker = (Boolean) fireworkData.get("flicker"); - boolean hasTrail = (Boolean) fireworkData.get("trail"); - effectBuilder.with(type); - effectBuilder.flicker(hasFlicker); - effectBuilder.trail(hasTrail); + else if ((rowType == Material.CROSSBOW)) { + CrossbowMeta meta = (CrossbowMeta) itemstack.getItemMeta(); + if (meta != null) { + for (Map itemData : map) { + ItemStack crossbowItem = ItemUtils.unserializeItemStack(itemData); + if (crossbowItem != null) { + meta.addChargedProjectile(crossbowItem); + } + } + itemstack.setItemMeta(meta); } } - else if (itemCount == 2) { + else if (rowType == Material.MAP || rowType == Material.FILLED_MAP) { for (Map colorData : map) { - org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); - effectBuilder.withColor(color); + MapMeta meta = (MapMeta) itemstack.getItemMeta(); + if (meta != null) { + org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); + meta.setColor(color); + itemstack.setItemMeta(meta); + } } } - else if (itemCount == 3) { - for (Map colorData : map) { - org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); - effectBuilder.withFade(color); + else if ((rowType == Material.FIREWORK_ROCKET) || (rowType == Material.FIREWORK_STAR)) { + if (itemCount == 1) { + effectBuilder = FireworkEffect.builder(); + for (Map fireworkData : map) { + org.bukkit.FireworkEffect.Type type = (org.bukkit.FireworkEffect.Type) fireworkData.getOrDefault("type", org.bukkit.FireworkEffect.Type.BALL); + boolean hasFlicker = (Boolean) fireworkData.get("flicker"); + boolean hasTrail = (Boolean) fireworkData.get("trail"); + effectBuilder.with(type); + effectBuilder.flicker(hasFlicker); + effectBuilder.trail(hasTrail); + } + } + else if (itemCount == 2) { + for (Map colorData : map) { + org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); + effectBuilder.withColor(color); + } + } + else if (itemCount == 3) { + for (Map colorData : map) { + org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); + effectBuilder.withFade(color); + } + buildFireworkEffect(effectBuilder, rowType, itemstack); + itemCount = 0; } - buildFireworkEffect(effectBuilder, rowType, itemstack); - itemCount = 0; } - } - else if ((rowType == Material.SUSPICIOUS_STEW)) { - for (Map suspiciousStewData : map) { - SuspiciousStewMeta meta = (SuspiciousStewMeta) itemstack.getItemMeta(); - PotionEffect effect = new PotionEffect(suspiciousStewData); - meta.addCustomEffect(effect, true); - itemstack.setItemMeta(meta); + else if ((rowType == Material.SUSPICIOUS_STEW)) { + for (Map suspiciousStewData : map) { + SuspiciousStewMeta meta = (SuspiciousStewMeta) itemstack.getItemMeta(); + PotionEffect effect = new PotionEffect(suspiciousStewData); + if (meta != null) { + meta.addCustomEffect(effect, true); + itemstack.setItemMeta(meta); + } + } + } + else { + BukkitAdapter.ADAPTER.setItemMeta(rowType, itemstack, map); } } - else { - BukkitAdapter.ADAPTER.setItemMeta(rowType, itemstack, map); - } - } - itemCount++; + itemCount++; + } } } catch (Exception e) { @@ -535,34 +580,4 @@ public static List deserializeMetadata(byte[] metadata) { public static void queueEntitySpawn(String user, BlockState block, EntityType type, int data) { Queue.queueEntitySpawn(user, block, type, data); } - - /** - * Queues a skull update operation for processing. - * - * @param user - * The username of the player - * @param block - * The block state to update - * @param rowId - * The row ID for the skull data - */ - public static void queueSkullUpdate(String user, BlockState block, int rowId) { - Queue.queueSkullUpdate(user, block, rowId); - } - - /** - * Queues a sign update operation for processing. - * - * @param user - * The username of the player - * @param block - * The block state to update - * @param action - * The action type - * @param time - * The time of the update - */ - public static void queueSignUpdate(String user, BlockState block, int action, int time) { - Queue.queueSignUpdate(user, block, action, time); - } } diff --git a/src/main/java/net/coreprotect/utility/BlockUtils.java b/src/main/java/net/coreprotect/utility/BlockUtils.java index b0bd6807f..3eef358ab 100644 --- a/src/main/java/net/coreprotect/utility/BlockUtils.java +++ b/src/main/java/net/coreprotect/utility/BlockUtils.java @@ -22,6 +22,7 @@ import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; +import net.coreprotect.thread.CacheHandler; import net.coreprotect.thread.Scheduler; public class BlockUtils { @@ -40,7 +41,8 @@ public static byte[] stringToByteData(String string, int type) { return result; } - if (material.isBlock() && !createBlockData(material).getAsString().equals(string) && string.startsWith(NAMESPACE + material.name().toLowerCase(Locale.ROOT) + "[") && string.endsWith("]")) { + BlockData defaultBlockData = createBlockData(material); + if (defaultBlockData != null && material.isBlock() && !defaultBlockData.getAsString().equals(string) && string.startsWith(NAMESPACE + material.name().toLowerCase(Locale.ROOT) + "[") && string.endsWith("]")) { String substring = string.substring(material.name().length() + 11, string.length() - 1); String[] blockDataSplit = substring.split(","); ArrayList blockDataArray = new ArrayList<>(); @@ -80,7 +82,7 @@ public static String byteDataToString(byte[] data, int type) { } result = new String(data, StandardCharsets.UTF_8); - if (result.length() > 0) { + if (!result.isEmpty()) { if (result.matches("\\d+")) { result = result + ","; } @@ -89,7 +91,7 @@ public static String byteDataToString(byte[] data, int type) { ArrayList blockDataArray = new ArrayList<>(); for (String blockData : blockDataSplit) { String block = MaterialUtils.getBlockDataString(Integer.parseInt(blockData)); - if (block.length() > 0) { + if (!block.isEmpty()) { blockDataArray.add(block); } } @@ -142,7 +144,7 @@ public static boolean iceBreakCheck(BlockState block, String user, Material type if (type.equals(Material.ICE)) { // Ice block int unixtimestamp = (int) (System.currentTimeMillis() / 1000L); int wid = WorldUtils.getWorldId(block.getWorld().getName()); - net.coreprotect.thread.CacheHandler.lookupCache.put("" + block.getX() + "." + block.getY() + "." + block.getZ() + "." + wid + "", new Object[] { unixtimestamp, user, Material.WATER }); + CacheHandler.lookupCache.put("" + block.getX() + "." + block.getY() + "." + block.getZ() + "." + wid, new Object[] { unixtimestamp, user, Material.WATER }); return true; } return false; @@ -162,7 +164,7 @@ public static BlockData createBlockData(Material material) { } public static void prepareTypeAndData(Map map, Block block, Material type, BlockData blockData, boolean update) { - if (blockData == null) { + if (blockData == null && type != null) { blockData = createBlockData(type); } @@ -231,7 +233,7 @@ public static List processMeta(BlockState block) { if (block instanceof CommandBlock) { CommandBlock commandBlock = (CommandBlock) block; String command = commandBlock.getCommand(); - if (command.length() > 0) { + if (!command.isEmpty()) { meta.add(command); } } @@ -249,7 +251,7 @@ else if (block instanceof ShulkerBox) { int slot = 0; for (ItemStack itemStack : inventory) { Map itemMap = ItemUtils.serializeItemStackLegacy(itemStack, null, slot); - if (itemMap.size() > 0) { + if (!itemMap.isEmpty()) { meta.add(itemMap); } slot++; diff --git a/src/main/java/net/coreprotect/utility/Teleport.java b/src/main/java/net/coreprotect/utility/Teleport.java index 2e5e64e79..7997d52c0 100644 --- a/src/main/java/net/coreprotect/utility/Teleport.java +++ b/src/main/java/net/coreprotect/utility/Teleport.java @@ -1,6 +1,5 @@ package net.coreprotect.utility; -import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -18,7 +17,7 @@ import net.coreprotect.model.BlockGroup; import net.coreprotect.paper.PaperAdapter; import net.coreprotect.thread.Scheduler; -import net.coreprotect.utility.BlockUtils; +// import net.coreprotect.utility.BlockUtils; public class Teleport { @@ -30,7 +29,8 @@ private Teleport() { public static void performSafeTeleport(Player player, Location location, boolean enforceTeleport) { try { - Set unsafeBlocks = new HashSet<>(Arrays.asList(Material.LAVA)); + Set unsafeBlocks = new HashSet<>(); + unsafeBlocks.add(Material.LAVA); unsafeBlocks.addAll(BlockGroup.FIRE); int worldHeight = location.getWorld().getMaxHeight(); @@ -48,10 +48,9 @@ public static void performSafeTeleport(Player player, Location location, boolean above = worldHeight; } - Block block1 = location.getWorld().getBlockAt(playerX, checkY, playerZ); + final Block block1 = location.getWorld().getBlockAt(playerX, checkY, playerZ); Block block2 = location.getWorld().getBlockAt(playerX, above, playerZ); Material type1 = block1.getType(); - Material type2 = block2.getType(); if (BlockUtils.passableBlock(block1) && BlockUtils.passableBlock(block2)) { if (unsafeBlocks.contains(type1)) { @@ -65,15 +64,19 @@ public static void performSafeTeleport(Player player, Location location, boolean if (checkY < worldHeight && unsafeBlocks.contains(blockBelow.getType())) { alert = true; - Location revertLocation = block1.getLocation(); - BlockData revertBlockData = block1.getBlockData(); + final Location revertLocation = block1.getLocation(); + final BlockData revertBlockData = block1.getBlockData(); revertBlocks.put(revertLocation, revertBlockData); - if (!ConfigHandler.isFolia) { - block1.setType(Material.BARRIER); - } - else { - block1.setType(Material.DIRT); - } + + Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), () -> { + if (!ConfigHandler.isFolia) { + block1.setType(Material.BARRIER); + } + else { + block1.setType(Material.DIRT); + } + }, revertLocation, 0); + checkY++; Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), () -> { @@ -108,7 +111,6 @@ public static void performSafeTeleport(Player player, Location location, boolean } if (!enforceTeleport) { - // Only send a message if the player was moved by at least 1 block if (location.getY() >= (oldY + 1.00)) { Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.TELEPORTED_SAFETY)); } diff --git a/src/main/java/net/coreprotect/utility/Util.java b/src/main/java/net/coreprotect/utility/Util.java index b5d0707fa..af0222698 100755 --- a/src/main/java/net/coreprotect/utility/Util.java +++ b/src/main/java/net/coreprotect/utility/Util.java @@ -6,13 +6,15 @@ import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; -import net.coreprotect.consumer.Queue; +import net.coreprotect.CoreProtect; +import net.coreprotect.config.ConfigHandler; +import net.coreprotect.thread.Scheduler; /** * Central utility class that provides access to various utility functions. * Most methods delegate to specialized utility classes. */ -public class Util extends Queue { +public class Util { public static final Pattern tagParser = Pattern.compile(Chat.COMPONENT_TAG_OPEN + "(.+?)" + Chat.COMPONENT_TAG_CLOSE + "|(.+?)", Pattern.DOTALL); @@ -21,6 +23,12 @@ private Util() { } public static void sendBlockChange(Player player, Location location, BlockData blockData) { - player.sendBlockChange(location, blockData); + // folia: wrapped sendBlockChange in a scheduler task + if (ConfigHandler.isFolia) { + Scheduler.runTask(CoreProtect.getInstance(), () -> player.sendBlockChange(location, blockData), player); + } + else { + player.sendBlockChange(location, blockData); + } } } diff --git a/src/main/java/net/coreprotect/utility/entity/HangingUtil.java b/src/main/java/net/coreprotect/utility/entity/HangingUtil.java index 4ae634859..f8e714d2b 100644 --- a/src/main/java/net/coreprotect/utility/entity/HangingUtil.java +++ b/src/main/java/net/coreprotect/utility/entity/HangingUtil.java @@ -14,8 +14,10 @@ import org.bukkit.entity.Painting; import org.bukkit.inventory.ItemStack; +import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; import net.coreprotect.model.BlockGroup; +import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.BlockUtils; import net.coreprotect.utility.MaterialUtils; @@ -29,9 +31,9 @@ private HangingUtil() { public static void spawnHanging(final BlockState blockstate, final Material rowType, final String hangingData, final int rowData) { try { Block block = blockstate.getBlock(); - int x = block.getX(); - int y = block.getY(); - int z = block.getZ(); + final int x = block.getX(); + final int y = block.getY(); + final int z = block.getZ(); BlockFace hangingFace = null; if (hangingData != null && !hangingData.contains(":") && hangingData.contains("=")) { @@ -48,7 +50,8 @@ public static void spawnHanging(final BlockState blockstate, final Material rowT Location el = e.getLocation(); if (el.getBlockX() == x && el.getBlockY() == y && el.getBlockZ() == z) { if (hangingFace == null || ((Hanging) e).getFacing() == hangingFace) { - e.remove(); + // folia: wrapped entity removal in a scheduler task + Scheduler.runTask(CoreProtect.getInstance(), e::remove, e); break; } } @@ -101,7 +104,10 @@ else if (!BlockUtils.solidBlock(BlockUtils.getType(block.getRelative(BlockFace.S if (faceSet != null && face != null) { if (rowType.equals(Material.PAINTING)) { String name = MaterialUtils.getArtName(rowData); - Art painting = Art.getByName(name.toUpperCase(Locale.ROOT)); + final Art painting = Art.getByName(name.toUpperCase(Locale.ROOT)); + if (painting == null) { + return; // Art type not found + } int height = painting.getBlockHeight(); int width = painting.getBlockWidth(); int paintingX = x; @@ -122,44 +128,60 @@ else if (faceSet.equals(BlockFace.SOUTH)) { } } } - Block spawnBlock = hangingFace != null ? block : block.getRelative(face); + final Block spawnBlock = hangingFace != null ? block : block.getRelative(face); if (hangingFace == null) { BlockUtils.setTypeAndData(spawnBlock, Material.AIR, null, true); } - Painting hanging = null; - try { - hanging = block.getWorld().spawn(spawnBlock.getLocation(), Painting.class); - } - catch (Exception e) { - } - if (hanging != null) { - hanging.teleport(block.getWorld().getBlockAt(paintingX, paintingY, paintingZ).getLocation()); - hanging.setFacingDirection(faceSet, true); - hanging.setArt(painting, true); - } + + // folia: wrapped entity spawn in a scheduler task. + final int finalPaintingX = paintingX; + final int finalPaintingY = paintingY; + final int finalPaintingZ = paintingZ; + final BlockFace finalFaceSet = faceSet; + + Scheduler.runTask(CoreProtect.getInstance(), () -> { + Painting hanging = null; + try { + hanging = spawnBlock.getWorld().spawn(spawnBlock.getLocation(), Painting.class); + } + catch (Exception e) { + e.printStackTrace(); + } + if (hanging != null) { + hanging.teleport(spawnBlock.getWorld().getBlockAt(finalPaintingX, finalPaintingY, finalPaintingZ).getLocation()); + hanging.setFacingDirection(finalFaceSet, true); + hanging.setArt(painting, true); + } + }, spawnBlock.getLocation()); } else if (BukkitAdapter.ADAPTER.isItemFrame(rowType)) { - try { - Block spawnBlock = hangingFace != null ? block : block.getRelative(face); - if (hangingFace == null) { - BlockUtils.setTypeAndData(spawnBlock, Material.AIR, null, true); - } - Class itemFrame = BukkitAdapter.ADAPTER.getFrameClass(rowType); - Entity entity = block.getWorld().spawn(spawnBlock.getLocation(), itemFrame); - if (entity instanceof ItemFrame) { - ItemFrame hanging = (ItemFrame) entity; - hanging.teleport(block.getWorld().getBlockAt(x, y, z).getLocation()); - hanging.setFacingDirection(faceSet, true); - - Material type = MaterialUtils.getType(rowData); - if (type != null) { - ItemStack istack = new ItemStack(type, 1); - hanging.setItem(istack); + final Block spawnBlock = hangingFace != null ? block : block.getRelative(face); + if (hangingFace == null) { + BlockUtils.setTypeAndData(spawnBlock, Material.AIR, null, true); + } + + // folia: wrapped entity spawn in a scheduler task. + final BlockFace finalFaceSet = faceSet; + Scheduler.runTask(CoreProtect.getInstance(), () -> { + try { + Class itemFrame = BukkitAdapter.ADAPTER.getFrameClass(rowType); + Entity entity = spawnBlock.getWorld().spawn(spawnBlock.getLocation(), itemFrame); + if (entity instanceof ItemFrame) { + ItemFrame hanging = (ItemFrame) entity; + hanging.teleport(spawnBlock.getWorld().getBlockAt(x, y, z).getLocation()); + hanging.setFacingDirection(finalFaceSet, true); + + Material type = MaterialUtils.getType(rowData); + if (type != null) { + ItemStack istack = new ItemStack(type, 1); + hanging.setItem(istack); + } } } - } - catch (Exception e) { - } + catch (Exception e) { + e.printStackTrace(); + } + }, spawnBlock.getLocation()); } } } @@ -180,12 +202,14 @@ public static void removeHanging(final BlockState block, final String hangingDat } } - for (Entity e : block.getChunk().getEntities()) { + final BlockFace finalHangingFace = hangingFace; + for (final Entity e : block.getChunk().getEntities()) { if (e instanceof ItemFrame || e instanceof Painting) { Location el = e.getLocation(); if (el.getBlockX() == block.getX() && el.getBlockY() == block.getY() && el.getBlockZ() == block.getZ()) { - if (hangingFace == null || ((Hanging) e).getFacing() == hangingFace) { - e.remove(); + if (finalHangingFace == null || ((Hanging) e).getFacing() == finalHangingFace) { + // folia: wrapped entity removal in a scheduler task. + Scheduler.runTask(CoreProtect.getInstance(), e::remove, e); } } } From c0d6c263583b27b151122e49d328db6df47cbc90 Mon Sep 17 00:00:00 2001 From: leir4iks Date: Wed, 29 Oct 2025 20:08:32 +0200 Subject: [PATCH 05/22] fix folia container rollback --- .../rollback/RollbackBlockHandler.java | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java index 1340e2900..8771a35eb 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java @@ -453,17 +453,33 @@ else if (value instanceof Map) { return false; } else if (rowType != changeType && (BlockGroup.CONTAINERS.contains(rowType) || BlockGroup.CONTAINERS.contains(changeType))) { - // folia: wrapped container clearing in a scheduler task + // folia: wrapped entire container rollback in region scheduler task final Location blockLocation = block.getLocation(); - Scheduler.runTask(CoreProtect.getInstance(), () -> block.setType(Material.AIR), blockLocation); + final Material finalRowType = rowType; + final BlockData finalBlockData = blockData; + final Map finalChunkChanges = chunkChanges; + final boolean finalCountBlock = countBlock; + final String finalUserStringCopy = finalUserString; + + Runnable containerTask = () -> { + // entire container rollback logic in main thread + block.setType(Material.AIR); + + boolean isChest = (finalBlockData instanceof Chest); + BlockUtils.prepareTypeAndData(finalChunkChanges, block, finalRowType, finalBlockData, isChest); + if (isChest) { + ChestTool.updateDoubleChest(block, finalBlockData, false); + } - boolean isChest = (blockData instanceof Chest); - BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, (isChest)); - if (isChest) { - ChestTool.updateDoubleChest(block, blockData, false); - } + if (finalCountBlock) { + updateBlockCount(finalUserStringCopy, 1); + } + }; - return countBlock; + + Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), containerTask, blockLocation, 0); + + return false; } else if (BlockGroup.UPDATE_STATE.contains(rowType) || rowType.name().contains("CANDLE")) { BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, true); From 3049759173896795a1e4ed20c1b9b3835f6f6dea Mon Sep 17 00:00:00 2001 From: leir4iks Date: Wed, 29 Oct 2025 20:54:35 +0200 Subject: [PATCH 06/22] fix folia container rollback --- .../database/rollback/RollbackBlockHandler.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java index 8771a35eb..776df446a 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java @@ -457,28 +457,22 @@ else if (rowType != changeType && (BlockGroup.CONTAINERS.contains(rowType) || Bl final Location blockLocation = block.getLocation(); final Material finalRowType = rowType; final BlockData finalBlockData = blockData; - final Map finalChunkChanges = chunkChanges; final boolean finalCountBlock = countBlock; final String finalUserStringCopy = finalUserString; + final boolean isChest = (finalBlockData instanceof Chest); Runnable containerTask = () -> { - // entire container rollback logic in main thread - block.setType(Material.AIR); - - boolean isChest = (finalBlockData instanceof Chest); - BlockUtils.prepareTypeAndData(finalChunkChanges, block, finalRowType, finalBlockData, isChest); + block.setType(Material.AIR, false); + block.setBlockData(finalBlockData, isChest); if (isChest) { ChestTool.updateDoubleChest(block, finalBlockData, false); } - if (finalCountBlock) { updateBlockCount(finalUserStringCopy, 1); } }; - - Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), containerTask, blockLocation, 0); - + Scheduler.runTask(CoreProtect.getInstance(), containerTask, blockLocation); return false; } else if (BlockGroup.UPDATE_STATE.contains(rowType) || rowType.name().contains("CANDLE")) { From 83efeb9b71c112e26ddf9559b5207e92f6ffc83d Mon Sep 17 00:00:00 2001 From: leir4iks Date: Sun, 2 Nov 2025 14:12:30 +0200 Subject: [PATCH 07/22] Revert "fix folia container rollback" This reverts commit 3049759173896795a1e4ed20c1b9b3835f6f6dea. --- .../database/rollback/RollbackBlockHandler.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java index 776df446a..8771a35eb 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java @@ -457,22 +457,28 @@ else if (rowType != changeType && (BlockGroup.CONTAINERS.contains(rowType) || Bl final Location blockLocation = block.getLocation(); final Material finalRowType = rowType; final BlockData finalBlockData = blockData; + final Map finalChunkChanges = chunkChanges; final boolean finalCountBlock = countBlock; final String finalUserStringCopy = finalUserString; - final boolean isChest = (finalBlockData instanceof Chest); Runnable containerTask = () -> { - block.setType(Material.AIR, false); - block.setBlockData(finalBlockData, isChest); + // entire container rollback logic in main thread + block.setType(Material.AIR); + + boolean isChest = (finalBlockData instanceof Chest); + BlockUtils.prepareTypeAndData(finalChunkChanges, block, finalRowType, finalBlockData, isChest); if (isChest) { ChestTool.updateDoubleChest(block, finalBlockData, false); } + if (finalCountBlock) { updateBlockCount(finalUserStringCopy, 1); } }; - Scheduler.runTask(CoreProtect.getInstance(), containerTask, blockLocation); + + Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), containerTask, blockLocation, 0); + return false; } else if (BlockGroup.UPDATE_STATE.contains(rowType) || rowType.name().contains("CANDLE")) { From 23b281666674ed8754b601761d91e5c69693c472 Mon Sep 17 00:00:00 2001 From: leir4iks Date: Sun, 2 Nov 2025 14:13:24 +0200 Subject: [PATCH 08/22] Revert "fix folia container rollback" This reverts commit c0d6c263583b27b151122e49d328db6df47cbc90. --- .../rollback/RollbackBlockHandler.java | 32 +++++-------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java index 8771a35eb..1340e2900 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java @@ -453,33 +453,17 @@ else if (value instanceof Map) { return false; } else if (rowType != changeType && (BlockGroup.CONTAINERS.contains(rowType) || BlockGroup.CONTAINERS.contains(changeType))) { - // folia: wrapped entire container rollback in region scheduler task + // folia: wrapped container clearing in a scheduler task final Location blockLocation = block.getLocation(); - final Material finalRowType = rowType; - final BlockData finalBlockData = blockData; - final Map finalChunkChanges = chunkChanges; - final boolean finalCountBlock = countBlock; - final String finalUserStringCopy = finalUserString; - - Runnable containerTask = () -> { - // entire container rollback logic in main thread - block.setType(Material.AIR); - - boolean isChest = (finalBlockData instanceof Chest); - BlockUtils.prepareTypeAndData(finalChunkChanges, block, finalRowType, finalBlockData, isChest); - if (isChest) { - ChestTool.updateDoubleChest(block, finalBlockData, false); - } - - if (finalCountBlock) { - updateBlockCount(finalUserStringCopy, 1); - } - }; + Scheduler.runTask(CoreProtect.getInstance(), () -> block.setType(Material.AIR), blockLocation); + boolean isChest = (blockData instanceof Chest); + BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, (isChest)); + if (isChest) { + ChestTool.updateDoubleChest(block, blockData, false); + } - Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), containerTask, blockLocation, 0); - - return false; + return countBlock; } else if (BlockGroup.UPDATE_STATE.contains(rowType) || rowType.name().contains("CANDLE")) { BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, true); From 0c734d7c44d34adf66d4eb464d0cf809f41ac603 Mon Sep 17 00:00:00 2001 From: leir4iks Date: Sun, 2 Nov 2025 14:13:36 +0200 Subject: [PATCH 09/22] Revert "improve folia support" This reverts commit 1e41943b8a679340edb4f1d1314d85f30df05531. --- .../coreprotect/command/TeleportCommand.java | 97 ++--- .../rollback/RollbackBlockHandler.java | 243 +++++------- .../rollback/RollbackEntityHandler.java | 68 ++-- .../database/rollback/RollbackProcessor.java | 110 +++--- .../database/rollback/RollbackUtil.java | 361 +++++++++--------- .../net/coreprotect/utility/BlockUtils.java | 16 +- .../net/coreprotect/utility/Teleport.java | 30 +- .../java/net/coreprotect/utility/Util.java | 14 +- .../utility/entity/HangingUtil.java | 102 ++--- 9 files changed, 477 insertions(+), 564 deletions(-) diff --git a/src/main/java/net/coreprotect/command/TeleportCommand.java b/src/main/java/net/coreprotect/command/TeleportCommand.java index 4656a9517..6a5bbc518 100644 --- a/src/main/java/net/coreprotect/command/TeleportCommand.java +++ b/src/main/java/net/coreprotect/command/TeleportCommand.java @@ -14,11 +14,14 @@ import net.coreprotect.utility.ChatMessage; import net.coreprotect.utility.Color; import net.coreprotect.utility.Teleport; +import net.coreprotect.utility.Util; import net.coreprotect.utility.WorldUtils; public class TeleportCommand { protected static void runCommand(CommandSender player, boolean permission, String[] args) { + int resultc = args.length; + if (!permission) { Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.NO_PERMISSION)); return; @@ -37,47 +40,22 @@ protected static void runCommand(CommandSender player, boolean permission, Strin } } - final Location location = parseTeleportLocation((Player) player, args); - if (location == null) { - return; - } - - // folia: safe teleportation logic - if (ConfigHandler.isFolia) { - location.getWorld().getChunkAtAsync(location).thenAccept(chunk -> Scheduler.runTask(CoreProtect.getInstance(), () -> Teleport.performSafeTeleport(((Player) player), location, true), location)); - } - else { - int chunkX = location.getBlockX() >> 4; - int chunkZ = location.getBlockZ() >> 4; - Scheduler.runTask(CoreProtect.getInstance(), () -> { - if (!location.getWorld().isChunkLoaded(chunkX, chunkZ)) { - location.getWorld().getChunkAt(location); - } - Teleport.performSafeTeleport(((Player) player), location, true); - }, location); - } - - ConfigHandler.teleportThrottle.put(player.getName(), new Object[] { false, System.currentTimeMillis() }); - } - - private static Location parseTeleportLocation(Player player, String[] args) { - if (args.length < 3) { + if (resultc < 3) { Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_PARAMETERS, "/co teleport ")); - return null; + return; } String worldName = args[1]; int wid = WorldUtils.matchWorld(worldName); - if (wid == -1 && args.length >= 5) { + if (wid == -1 && resultc >= 5) { Chat.sendMessage(player, new ChatMessage(Phrase.build(Phrase.WORLD_NOT_FOUND, worldName)).build()); - return null; + return; } - Location location = player.getLocation().clone(); - World world = (wid > -1) ? Bukkit.getServer().getWorld(WorldUtils.getWorldName(wid)) : location.getWorld(); - if (world == null) { - Chat.sendMessage(player, new ChatMessage(Phrase.build(Phrase.WORLD_NOT_FOUND, worldName)).build()); - return null; + Location location = ((Player) player).getLocation().clone(); + World world = location.getWorld(); + if (wid > -1) { + world = Bukkit.getServer().getWorld(WorldUtils.getWorldName(wid)); } String x = null; @@ -90,49 +68,56 @@ private static Location parseTeleportLocation(Player player, String[] args) { } if (x == null) { - x = args[i]; + x = args[i].replaceAll("[^0-9.\\-]", ""); } else if (z == null) { - z = args[i]; + z = args[i].replaceAll("[^0-9.\\-]", ""); } else if (y == null) { y = z; - z = args[i]; + z = args[i].replaceAll("[^0-9.\\-]", ""); } } if (y == null) { - y = Double.toString(Math.max(63, location.getY())); - } - if (x == null || y == null || z == null) { - Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_PARAMETERS, "/co teleport ")); - return null; + if (location.getBlockY() > 63) { + location.setY(63); + } + y = Double.toString(location.getY()); } - x = x.replaceAll("[^0-9.\\-]", ""); - y = y.replaceAll("[^0-9.\\-]", ""); - z = z.replaceAll("[^0-9.\\-]", ""); - String xValidate = x.replaceAll("[^.\\-]", ""); String yValidate = y.replaceAll("[^.\\-]", ""); String zValidate = z.replaceAll("[^.\\-]", ""); - if ((x.isEmpty() || x.length() >= 12 || x.equals(xValidate)) || (y.isEmpty() || y.length() >= 12 || y.equals(yValidate)) || (z.isEmpty() || z.length() >= 12 || z.equals(zValidate))) { + if ((x.length() == 0 || x.length() >= 12 || x.equals(xValidate)) || (y.length() == 0 || y.length() >= 12 || y.equals(yValidate)) || (z.length() == 0 || z.length() >= 12 || z.equals(zValidate))) { Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_PARAMETERS, "/co teleport ")); - return null; + return; } - try { - location.setWorld(world); - location.setX(Double.parseDouble(x)); - location.setY(Double.parseDouble(y)); - location.setZ(Double.parseDouble(z)); + location.setWorld(world); + location.setX(Double.parseDouble(x)); + location.setY(Double.parseDouble(y)); + location.setZ(Double.parseDouble(z)); + + if (ConfigHandler.isFolia) { + location.getWorld().getChunkAtAsync(location).thenAccept(chunk -> { + Scheduler.runTask(CoreProtect.getInstance(), () -> { + Teleport.performSafeTeleport(((Player) player), location, true); + }, location); + }); } - catch (NumberFormatException e) { - Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.MISSING_PARAMETERS, "/co teleport ")); - return null; + else { + int chunkX = location.getBlockX() >> 4; + int chunkZ = location.getBlockZ() >> 4; + Scheduler.runTask(CoreProtect.getInstance(), () -> { + if (!location.getWorld().isChunkLoaded(chunkX, chunkZ)) { + location.getWorld().getChunkAt(location); + } + Teleport.performSafeTeleport(((Player) player), location, true); + }, location); } - return location; + ConfigHandler.teleportThrottle.put(player.getName(), new Object[] { false, System.currentTimeMillis() }); } } diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java index 1340e2900..871a0cab2 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java @@ -1,8 +1,6 @@ package net.coreprotect.database.rollback; import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -13,7 +11,6 @@ import org.bukkit.block.Banner; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; -import org.bukkit.block.BlockState; import org.bukkit.block.CommandBlock; import org.bukkit.block.CreatureSpawner; import org.bukkit.block.banner.Pattern; @@ -42,14 +39,12 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; import net.coreprotect.config.ConfigHandler; import net.coreprotect.consumer.Queue; import net.coreprotect.model.BlockGroup; import net.coreprotect.paper.PaperAdapter; import net.coreprotect.thread.CacheHandler; -import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.BlockUtils; import net.coreprotect.utility.ChestTool; import net.coreprotect.utility.EntityUtils; @@ -64,16 +59,15 @@ public static boolean processBlockChange(World bukkitWorld, Block block, Object[ try { if (changeBlock) { - // folia: wrapped piston logic in a scheduler task + /* If modifying the head of a piston, update the base piston block to prevent it from being destroyed */ if (changeBlockData instanceof PistonHead) { - final PistonHead pistonHead = (PistonHead) changeBlockData; - final Block pistonBlock = block.getRelative(pistonHead.getFacing().getOppositeFace()); + PistonHead pistonHead = (PistonHead) changeBlockData; + Block pistonBlock = block.getRelative(pistonHead.getFacing().getOppositeFace()); BlockData pistonData = pistonBlock.getBlockData(); if (pistonData instanceof Piston) { - final Piston piston = (Piston) pistonData; + Piston piston = (Piston) pistonData; piston.setExtended(false); - Location pistonLocation = pistonBlock.getLocation(); - Scheduler.runTask(CoreProtect.getInstance(), () -> pistonBlock.setBlockData(piston, false), pistonLocation); + pistonBlock.setBlockData(piston, false); } } else if (rowType == Material.MOVING_PISTON && blockData instanceof TechnicalPiston && !(blockData instanceof PistonHead)) { @@ -105,11 +99,8 @@ else if ((rowType == Material.ARMOR_STAND)) { } if (!exists) { - // folia: wrapped entity spawn in a scheduler task - Scheduler.runTask(CoreProtect.getInstance(), () -> { - Entity entity = block.getLocation().getWorld().spawnEntity(location1, EntityType.ARMOR_STAND); - PaperAdapter.ADAPTER.teleportAsync(entity, location1); - }, location1); + Entity entity = block.getLocation().getWorld().spawnEntity(location1, EntityType.ARMOR_STAND); + PaperAdapter.ADAPTER.teleportAsync(entity, location1); } } else if ((rowType == Material.END_CRYSTAL)) { @@ -127,13 +118,10 @@ else if ((rowType == Material.END_CRYSTAL)) { } if (!exists) { - // folia: wrapped entity spawn in a scheduler task - Scheduler.runTask(CoreProtect.getInstance(), () -> { - Entity entity = block.getLocation().getWorld().spawnEntity(location1, BukkitAdapter.ADAPTER.getEntityType(Material.END_CRYSTAL)); - EnderCrystal enderCrystal = (EnderCrystal) entity; - enderCrystal.setShowingBottom((rowData != 0)); - PaperAdapter.ADAPTER.teleportAsync(entity, location1); - }, location1); + Entity entity = block.getLocation().getWorld().spawnEntity(location1, BukkitAdapter.ADAPTER.getEntityType(Material.END_CRYSTAL)); + EnderCrystal enderCrystal = (EnderCrystal) entity; + enderCrystal.setShowingBottom((rowData != 0)); + PaperAdapter.ADAPTER.teleportAsync(entity, location1); } } else if ((rowType == Material.AIR) && ((oldTypeMaterial == Material.WATER))) { @@ -156,9 +144,7 @@ else if ((rowType == Material.AIR) && ((oldTypeMaterial == Material.END_CRYSTAL) for (Entity entity : block.getChunk().getEntities()) { if (entity instanceof EnderCrystal) { if (entity.getLocation().getBlockX() == rowX && entity.getLocation().getBlockY() == rowY && entity.getLocation().getBlockZ() == rowZ) { - // folia: wrapped entity removal in a scheduler task - Location entityLocation = entity.getLocation(); - Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entityLocation); + entity.remove(); } } } @@ -171,8 +157,7 @@ else if ((rowType == Material.AIR) || (rowType == Material.TNT)) { if (BlockGroup.CONTAINERS.contains(changeType)) { Inventory inventory = BlockUtils.getContainerInventory(block.getState(), false); if (inventory != null) { - // folia: wrapped inventory clear in a scheduler task - Scheduler.runTask(CoreProtect.getInstance(), inventory::clear, block.getLocation()); + inventory.clear(); } } else if (BlockGroup.CONTAINERS.contains(Material.ARMOR_STAND)) { @@ -187,9 +172,7 @@ else if (BlockGroup.CONTAINERS.contains(Material.ARMOR_STAND)) { entityLocation.setY(entityLocation.getY() - 1.99); PaperAdapter.ADAPTER.teleportAsync(entity, entityLocation); - - // folia: wrapped entity removal in a scheduler task - Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entityLocation); + entity.remove(); } } } @@ -219,7 +202,7 @@ else if ((pendingChangeType == Material.WATER)) { if (remove) { boolean physics = true; if ((changeType == Material.NETHER_PORTAL) || changeBlockData instanceof MultipleFacing || changeBlockData instanceof Snow || changeBlockData instanceof Stairs || changeBlockData instanceof RedstoneWire || changeBlockData instanceof Chest) { - // physics = true; + physics = true; } else if (changeBlockData instanceof Bisected && !(changeBlockData instanceof TrapDoor)) { Bisected bisected = (Bisected) changeBlockData; @@ -258,14 +241,9 @@ else if (changeBlockData instanceof Bed) { else if ((rowType == Material.SPAWNER)) { try { BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false); - - // folia: wrapped spawner update in a scheduler task - final Location blockLocation = block.getLocation(); - Scheduler.runTask(CoreProtect.getInstance(), () -> { - CreatureSpawner mobSpawner = (CreatureSpawner) block.getState(); - mobSpawner.setSpawnedType(EntityUtils.getSpawnerType(rowData)); - mobSpawner.update(); - }, blockLocation); + CreatureSpawner mobSpawner = (CreatureSpawner) block.getState(); + mobSpawner.setSpawnedType(EntityUtils.getSpawnerType(rowData)); + mobSpawner.update(); return countBlock; } @@ -310,18 +288,14 @@ else if (rowType == Material.COMMAND_BLOCK || rowType == Material.REPEATING_COMM } if (meta != null) { - // folia: wrapped command block update in a scheduler task - final Location blockLocation = block.getLocation(); - Scheduler.runTask(CoreProtect.getInstance(), () -> { - CommandBlock commandBlock = (CommandBlock) block.getState(); - for (Object value : meta) { - if (value instanceof String) { - String string = (String) value; - commandBlock.setCommand(string); - commandBlock.update(); - } + CommandBlock commandBlock = (CommandBlock) block.getState(); + for (Object value : meta) { + if (value instanceof String) { + String string = (String) value; + commandBlock.setCommand(string); + commandBlock.update(); } - }, blockLocation); + } } return false; } @@ -345,46 +319,39 @@ else if (blockData == null && rowData > 0 && (rowType == Material.IRON_DOOR || B updateBlockCount(finalUserString, 1); } - // folia: wrapped door update in a scheduler task - final Location blockLocation = block.getLocation(); - final Material finalRowType = rowType; - final int finalRowData = rowData; - Scheduler.runTask(CoreProtect.getInstance(), () -> { - block.setType(finalRowType, false); - Door door = (Door) block.getBlockData(); - int data = finalRowData; - if (data >= 8) { - door.setHalf(Half.TOP); - data = data - 8; - } - else { - door.setHalf(Half.BOTTOM); - } - if (data >= 4) { - door.setHinge(Hinge.RIGHT); - data = data - 4; - } - else { - door.setHinge(Hinge.LEFT); - } - BlockFace face = BlockFace.NORTH; - - switch (data) { - case 0: - face = BlockFace.EAST; - break; - case 1: - face = BlockFace.SOUTH; - break; - case 2: - face = BlockFace.WEST; - break; - } + block.setType(rowType, false); + Door door = (Door) block.getBlockData(); + if (rowData >= 8) { + door.setHalf(Half.TOP); + rowData = rowData - 8; + } + else { + door.setHalf(Half.BOTTOM); + } + if (rowData >= 4) { + door.setHinge(Hinge.RIGHT); + rowData = rowData - 4; + } + else { + door.setHinge(Hinge.LEFT); + } + BlockFace face = BlockFace.NORTH; + + switch (rowData) { + case 0: + face = BlockFace.EAST; + break; + case 1: + face = BlockFace.SOUTH; + break; + case 2: + face = BlockFace.WEST; + break; + } - door.setFacing(face); - door.setOpen(false); - block.setBlockData(door, false); - }, blockLocation); + door.setFacing(face); + door.setOpen(false); + block.setBlockData(door, false); return false; } else if (blockData == null && rowData > 0 && (rowType.name().endsWith("_BED"))) { @@ -392,36 +359,29 @@ else if (blockData == null && rowData > 0 && (rowType.name().endsWith("_BED"))) updateBlockCount(finalUserString, 1); } - // folia: wrapped bed update in a scheduler task - final Location blockLocation = block.getLocation(); - final Material finalRowType = rowType; - final int finalRowData = rowData; - Scheduler.runTask(CoreProtect.getInstance(), () -> { - block.setType(finalRowType, false); - Bed bed = (Bed) block.getBlockData(); - BlockFace face = BlockFace.NORTH; - int data = finalRowData; + block.setType(rowType, false); + Bed bed = (Bed) block.getBlockData(); + BlockFace face = BlockFace.NORTH; - if (data > 4) { - bed.setPart(Part.HEAD); - data = data - 4; - } + if (rowData > 4) { + bed.setPart(Part.HEAD); + rowData = rowData - 4; + } - switch (data) { - case 2: - face = BlockFace.WEST; - break; - case 3: - face = BlockFace.EAST; - break; - case 4: - face = BlockFace.SOUTH; - break; - } + switch (rowData) { + case 2: + face = BlockFace.WEST; + break; + case 3: + face = BlockFace.EAST; + break; + case 4: + face = BlockFace.SOUTH; + break; + } - bed.setFacing(face); - block.setBlockData(bed, false); - }, blockLocation); + bed.setFacing(face); + block.setBlockData(bed, false); return false; } else if (rowType.name().endsWith("_BANNER")) { @@ -431,31 +391,25 @@ else if (rowType.name().endsWith("_BANNER")) { } if (meta != null) { - // folia: wrapped banner update in a scheduler task - final Location blockLocation = block.getLocation(); - Scheduler.runTask(CoreProtect.getInstance(), () -> { - Banner banner = (Banner) block.getState(); - - for (Object value : meta) { - if (value instanceof DyeColor) { - banner.setBaseColor((DyeColor) value); - } - else if (value instanceof Map) { - @SuppressWarnings("unchecked") - Pattern pattern = new Pattern((Map) value); - banner.addPattern(pattern); - } + Banner banner = (Banner) block.getState(); + + for (Object value : meta) { + if (value instanceof DyeColor) { + banner.setBaseColor((DyeColor) value); } + else if (value instanceof Map) { + @SuppressWarnings("unchecked") + Pattern pattern = new Pattern((Map) value); + banner.addPattern(pattern); + } + } - banner.update(); - }, blockLocation); + banner.update(); } return false; } else if (rowType != changeType && (BlockGroup.CONTAINERS.contains(rowType) || BlockGroup.CONTAINERS.contains(changeType))) { - // folia: wrapped container clearing in a scheduler task - final Location blockLocation = block.getLocation(); - Scheduler.runTask(CoreProtect.getInstance(), () -> block.setType(Material.AIR), blockLocation); + block.setType(Material.AIR); // Clear existing container to prevent errors boolean isChest = (blockData instanceof Chest); BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, (isChest)); @@ -470,7 +424,7 @@ else if (BlockGroup.UPDATE_STATE.contains(rowType) || rowType.name().contains("C ChestTool.updateDoubleChest(block, blockData, true); return countBlock; } - else if (rawBlockData instanceof Bisected && !(rawBlockData instanceof Stairs || rawBlockData instanceof TrapDoor)) { + else if (rowType != Material.AIR && rawBlockData instanceof Bisected && !(rawBlockData instanceof Stairs || rawBlockData instanceof TrapDoor)) { Bisected bisected = (Bisected) rawBlockData; Bisected bisectData = (Bisected) rawBlockData.clone(); Location bisectLocation = block.getLocation().clone(); @@ -496,7 +450,7 @@ else if (rawBlockData instanceof Bisected && !(rawBlockData instanceof Stairs || } return false; } - else if (rawBlockData instanceof Bed) { + else if (rowType != Material.AIR && rawBlockData instanceof Bed) { Bed bed = (Bed) rawBlockData; if (bed.getPart() == Part.FOOT) { Block adjacentBlock = block.getRelative(bed.getFacing()); @@ -513,6 +467,11 @@ else if (rawBlockData instanceof Bed) { } else { boolean physics = true; + /* + if (blockData instanceof MultipleFacing || BukkitAdapter.ADAPTER.isWall(blockData) || blockData instanceof Snow || blockData instanceof Stairs || blockData instanceof RedstoneWire || blockData instanceof Chest) { + physics = !(blockData instanceof Snow) || block.getY() <= BukkitAdapter.ADAPTER.getMinHeight(block.getWorld()) || (block.getWorld().getBlockAt(block.getX(), block.getY() - 1, block.getZ()).getType().equals(Material.GRASS_BLOCK)); + } + */ BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, physics); return countBlock; } @@ -522,8 +481,8 @@ else if (rawBlockData instanceof Bed) { e.printStackTrace(); } - if (changeBlock) { - if (!rowUser.isEmpty()) { + if ((rowType != Material.AIR) && changeBlock) { + if (rowUser.length() > 0) { CacheHandler.lookupCache.put(rowX + "." + rowY + "." + rowZ + "." + rowWorldId, new Object[] { unixtimestamp, rowUser, rowType }); } } @@ -533,7 +492,7 @@ else if (rawBlockData instanceof Bed) { /** * Update the block count in the rollback hash - * + * * @param userString * The username for this rollback * @param increment @@ -552,7 +511,7 @@ protected static void updateBlockCount(String userString, int increment) { /** * Apply all pending block changes to the world - * + * * @param chunkChanges * Map of blocks to change * @param preview diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java index c6b360ea1..c6357604c 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java @@ -8,10 +8,8 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; -import net.coreprotect.CoreProtect; import net.coreprotect.config.ConfigHandler; import net.coreprotect.thread.CacheHandler; -import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.EntityUtils; import net.coreprotect.utility.WorldUtils; @@ -20,12 +18,16 @@ public class RollbackEntityHandler { /** * Processes an entity-related rollback operation. * + * @param row + * The database row containing entity data (used only for specific operations) * @param rollbackType * The type of rollback (0 for rollback, 1 for restore) * @param finalUserString * The user string for tracking operations * @param oldTypeRaw * The old raw type value + * @param rowTypeRaw + * The raw type value * @param rowData * The data value * @param rowAction @@ -40,45 +42,55 @@ public class RollbackEntityHandler { * The Z coordinate * @param rowWorldId * The world ID + * @param rowUserId + * The user ID * @param rowUser * The username associated with this entity change * @return The number of entities affected (1 if successful, 0 otherwise) */ - - public static int processEntity(int rollbackType, String finalUserString, int oldTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, String rowUser) { + public static int processEntity(Object[] row, int rollbackType, String finalUserString, int oldTypeRaw, int rowTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, int rowUserId, String rowUser) { World bukkitWorld = Bukkit.getServer().getWorld(WorldUtils.getWorldName(rowWorldId)); if (bukkitWorld == null) { return 0; } - Location entityLocation = new Location(bukkitWorld, rowX, rowY, rowZ); - if (ConfigHandler.isFolia) { - // folia: load chunk async before processing, then regionize the entity operations - Scheduler.runTask(CoreProtect.getInstance(), () -> processEntityLogic(finalUserString, oldTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUser, bukkitWorld), entityLocation); - + // Folia - load chunk async before processing + bukkitWorld.getChunkAtAsync(rowX >> 4, rowZ >> 4, true).thenAccept(chunk -> { + processEntityLogic(row, rollbackType, finalUserString, oldTypeRaw, rowTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUserId, rowUser, bukkitWorld); + }); return 1; // assume task is queued successfully } else { if (!bukkitWorld.isChunkLoaded(rowX >> 4, rowZ >> 4)) { bukkitWorld.getChunkAt(rowX >> 4, rowZ >> 4); } - return processEntityLogic(finalUserString, oldTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUser, bukkitWorld); + return processEntityLogic(row, rollbackType, finalUserString, oldTypeRaw, rowTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUserId, rowUser, bukkitWorld); } } - private static int processEntityLogic(String finalUserString, int oldTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, String rowUser, World bukkitWorld) { + private static int processEntityLogic(Object[] row, int rollbackType, String finalUserString, int oldTypeRaw, int rowTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, int rowUserId, String rowUser, World bukkitWorld) { try { // Entity kill if (rowAction == 3) { Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); - if (oldTypeRaw <= 0) { + if (rowTypeRaw > 0) { + // Spawn in entity + if (rowRolledBack == 0) { + EntityType entityType = EntityUtils.getEntityType(rowTypeRaw); + // Use the spawnEntity method from the RollbackUtil class instead of Queue + spawnEntity(rowUser, block.getState(), entityType, rowData); + updateEntityCount(finalUserString, 1); + return 1; + } + } + else if (rowTypeRaw <= 0) { // Attempt to remove entity if (rowRolledBack == 1) { boolean removed = false; int entityId = -1; String entityName = EntityUtils.getEntityType(oldTypeRaw).name(); - String token = String.valueOf(rowX) + "." + rowY + "." + rowZ + "." + rowWorldId + "." + entityName; + String token = "" + rowX + "." + rowY + "." + rowZ + "." + rowWorldId + "." + entityName + ""; Object[] cachedEntity = CacheHandler.entityCache.get(token); if (cachedEntity != null) { @@ -98,8 +110,7 @@ private static int processEntityLogic(String finalUserString, int oldTypeRaw, in if (id == entityId) { updateEntityCount(finalUserString, 1); removed = true; - // folia: wrapped entity removal in a scheduler task - Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); + entity.remove(); break; } } @@ -113,8 +124,7 @@ private static int processEntityLogic(String finalUserString, int oldTypeRaw, in if (entityx >= xmin && entityx <= xmax && entityY >= ymin && entityY <= ymax && entityZ >= zmin && entityZ <= zmax) { updateEntityCount(finalUserString, 1); removed = true; - // folia: wrapped entity removal in a scheduler task - Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); + entity.remove(); break; } } @@ -127,8 +137,7 @@ private static int processEntityLogic(String finalUserString, int oldTypeRaw, in if (id == entityId) { updateEntityCount(finalUserString, 1); removed = true; - // folia: wrapped entity removal in a scheduler task - Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); + entity.remove(); break; } } @@ -139,16 +148,6 @@ private static int processEntityLogic(String finalUserString, int oldTypeRaw, in } } } - else { - // Spawn in entity - if (rowRolledBack == 0) { - EntityType entityType = EntityUtils.getEntityType(oldTypeRaw); - // Use the spawnEntity method from the RollbackUtil class instead of Queue - spawnEntity(rowUser, block.getState(), entityType, rowData); - updateEntityCount(finalUserString, 1); - return 1; - } - } } } catch (Exception e) { @@ -158,6 +157,17 @@ private static int processEntityLogic(String finalUserString, int oldTypeRaw, in return 0; } + /** + * Gets the world name from a world ID. + * + * @param worldId + * The world ID + * @return The world name + */ + private static String getWorldName(int worldId) { + return WorldUtils.getWorldName(worldId); + } + /** * Updates the entity count in the rollback hash for a specific user. * diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java index 7a9051592..fa3b42e97 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java @@ -18,6 +18,7 @@ import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.Jukebox; import org.bukkit.entity.ArmorStand; import org.bukkit.entity.Entity; import org.bukkit.entity.ItemFrame; @@ -25,14 +26,11 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; import net.coreprotect.config.Config; import net.coreprotect.config.ConfigHandler; -import net.coreprotect.consumer.Queue; import net.coreprotect.database.logger.ItemLogger; import net.coreprotect.model.BlockGroup; -import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.BlockUtils; import net.coreprotect.utility.ItemUtils; import net.coreprotect.utility.MaterialUtils; @@ -41,48 +39,54 @@ public class RollbackProcessor { + /** + * Process data for a specific chunk + * + * @param finalChunkX + * The chunk X coordinate + * @param finalChunkZ + * The chunk Z coordinate + * @param chunkKey + * The chunk lookup key + * @param blockList + * The list of block data to process + * @param itemList + * The list of item data to process + * @param rollbackType + * The rollback type (0=rollback, 1=restore) + * @param preview + * Whether this is a preview (0=no, 1=yes-non-destructive, 2=yes-destructive) + * @param finalUserString + * The username performing the rollback + * @param finalUser + * The user performing the rollback + * @param bukkitRollbackWorld + * The world to process + * @return A CompletableFuture that completes with true if successful, false otherwise + */ public static CompletableFuture processChunk(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { if (ConfigHandler.isFolia) { - return bukkitRollbackWorld.getChunkAtAsync(finalChunkX, finalChunkZ, true).thenCompose(chunk -> { - CompletableFuture future = new CompletableFuture<>(); - - Location chunkLocation = new Location(bukkitRollbackWorld, finalChunkX << 4, 64, finalChunkZ << 4); - - Scheduler.runTask(CoreProtect.getInstance(), () -> { - try { - boolean result = processChunkLogic(finalChunkX, finalChunkZ, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); - future.complete(result); - } catch (Exception e) { - e.printStackTrace(); - future.complete(false); - } - }, chunkLocation); - - return future; + // Folia - load chunk async before processing + return bukkitRollbackWorld.getChunkAtAsync(finalChunkX, finalChunkZ, true).thenApply(chunk -> { + return processChunkLogic(finalChunkX, finalChunkZ, chunkKey, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); }); } else { - CompletableFuture future = new CompletableFuture<>(); - - Scheduler.runTask(CoreProtect.getInstance(), () -> { - try { - if (!bukkitRollbackWorld.isChunkLoaded(finalChunkX, finalChunkZ)) { - bukkitRollbackWorld.getChunkAt(finalChunkX, finalChunkZ); - } - boolean result = processChunkLogic(finalChunkX, finalChunkZ, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); - future.complete(result); + try { + if (!bukkitRollbackWorld.isChunkLoaded(finalChunkX, finalChunkZ)) { + bukkitRollbackWorld.getChunkAt(finalChunkX, finalChunkZ); } - catch (Exception e) { - e.printStackTrace(); - future.complete(false); - } - }); - - return future; + boolean result = processChunkLogic(finalChunkX, finalChunkZ, chunkKey, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); + return CompletableFuture.completedFuture(result); + } + catch (Exception e) { + e.printStackTrace(); + return CompletableFuture.completedFuture(false); + } } } - private static boolean processChunkLogic(int finalChunkX, int finalChunkZ, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { + private static boolean processChunkLogic(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { try { boolean clearInventories = Config.getGlobal().ROLLBACK_ITEMS; ArrayList data = blockList != null ? blockList : new ArrayList<>(); @@ -115,7 +119,7 @@ private static boolean processChunkLogic(int finalChunkX, int finalChunkZ, Array } BlockData blockData = null; - if (blockDataString != null && !blockDataString.isEmpty()) { + if (blockDataString != null && blockDataString.contains(":")) { try { blockData = Bukkit.getServer().createBlockData(blockDataString); } @@ -139,21 +143,25 @@ private static boolean processChunkLogic(int finalChunkX, int finalChunkZ, Array if (rowAction == 1 && rollbackType == 0) { // block placement rowType = Material.AIR; blockData = null; + rowTypeRaw = 0; } else if (rowAction == 0 && rollbackType == 1) { // block removal rowType = Material.AIR; blockData = null; + rowTypeRaw = 0; } else if (rowAction == 4 && rollbackType == 0) { // entity placement rowType = null; + rowTypeRaw = 0; } else if (rowAction == 3 && rollbackType == 1) { // entity removal rowType = null; + rowTypeRaw = 0; } if (preview > 0) { if (rowAction != 3) { // entity kill String world = WorldUtils.getWorldName(rowWorldId); - if (world.isEmpty()) { + if (world.length() == 0) { continue; } @@ -165,13 +173,13 @@ else if (rowAction == 3 && rollbackType == 1) { // entity removal Block block = new Location(bukkitWorld, rowX, rowY, rowZ).getBlock(); if (preview == 2) { Material blockType = block.getType(); - if (!net.coreprotect.bukkit.BukkitAdapter.ADAPTER.isItemFrame(blockType) && !blockType.equals(Material.PAINTING) && !blockType.equals(Material.ARMOR_STAND) && !blockType.equals(Material.END_CRYSTAL)) { + if (!BukkitAdapter.ADAPTER.isItemFrame(blockType) && !blockType.equals(Material.PAINTING) && !blockType.equals(Material.ARMOR_STAND) && !blockType.equals(Material.END_CRYSTAL)) { BlockUtils.prepareTypeAndData(chunkChanges, block, blockType, block.getBlockData(), true); blockCount++; } } else { - if (rowType != null && (!net.coreprotect.bukkit.BukkitAdapter.ADAPTER.isItemFrame(rowType)) && (rowType != Material.PAINTING) && (rowType != Material.ARMOR_STAND) && (rowType != Material.END_CRYSTAL)) { + if ((!BukkitAdapter.ADAPTER.isItemFrame(rowType)) && (rowType != Material.PAINTING) && (rowType != Material.ARMOR_STAND) && (rowType != Material.END_CRYSTAL)) { BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, true); blockCount++; } @@ -182,11 +190,11 @@ else if (rowAction == 3 && rollbackType == 1) { // entity removal } } else if (rowAction == 3) { // entity kill - entityCount += RollbackEntityHandler.processEntity(rollbackType, finalUserString, oldTypeRaw, rowData, rowAction, MaterialUtils.rolledBack((Integer) row[9], false), rowX, rowY, rowZ, rowWorldId, rowUser); + entityCount += RollbackEntityHandler.processEntity(row, rollbackType, finalUserString, oldTypeRaw, rowTypeRaw, rowData, rowAction, MaterialUtils.rolledBack((Integer) row[9], false), rowX, rowY, rowZ, rowWorldId, (Integer) row[2], rowUser); } else { String world = WorldUtils.getWorldName(rowWorldId); - if (world.isEmpty()) { + if (world.length() == 0) { continue; } @@ -210,16 +218,16 @@ else if (rowAction == 3) { // entity kill pendingChangeData = changeBlockData; } - if (MaterialUtils.rolledBack((Integer) row[9], false) == 1 && rollbackType == 0) { // rollback + if (rowRolledBack == 1 && rollbackType == 0) { // rollback countBlock = false; } - if ((rowType == pendingChangeType) && ((!net.coreprotect.bukkit.BukkitAdapter.ADAPTER.isItemFrame(oldTypeMaterial)) && (oldTypeMaterial != Material.PAINTING) && (oldTypeMaterial != Material.ARMOR_STAND)) && (oldTypeMaterial != Material.END_CRYSTAL)) { + if ((rowType == pendingChangeType) && ((!BukkitAdapter.ADAPTER.isItemFrame(oldTypeMaterial)) && (oldTypeMaterial != Material.PAINTING) && (oldTypeMaterial != Material.ARMOR_STAND)) && (oldTypeMaterial != Material.END_CRYSTAL)) { // block is already changed! BlockData checkData = rowType == Material.AIR ? blockData : rawBlockData; if (checkData != null) { if (checkData.getAsString().equals(pendingChangeData.getAsString()) || checkData instanceof org.bukkit.block.data.MultipleFacing || checkData instanceof org.bukkit.block.data.type.Stairs || checkData instanceof org.bukkit.block.data.type.RedstoneWire) { - if (rowType != Material.CHEST && rowType != Material.TRAPPED_CHEST && !net.coreprotect.bukkit.BukkitAdapter.ADAPTER.isCopperChest(rowType)) { // always update double chests + if (rowType != Material.CHEST && rowType != Material.TRAPPED_CHEST && !BukkitAdapter.ADAPTER.isCopperChest(rowType)) { // always update double chests changeBlock = false; } } @@ -276,7 +284,9 @@ else if ((pendingChangeType != Material.AIR) && (pendingChangeType != Material.C int rowY = (Integer) row[4]; int rowZ = (Integer) row[5]; int rowTypeRaw = (Integer) row[6]; + int rowData = (Integer) row[7]; int rowAction = (Integer) row[8]; + int rowRolledBack = MaterialUtils.rolledBack((Integer) row[9], false); int rowWorldId = (Integer) row[10]; int rowAmount = (Integer) row[11]; byte[] rowMetadata = (byte[]) row[12]; @@ -302,7 +312,7 @@ else if ((pendingChangeType != Material.AIR) && (pendingChangeType != Material.C continue; } - int inventoryAction; + int inventoryAction = 0; if (rowAction == ItemLogger.ITEM_DROP || rowAction == ItemLogger.ITEM_PICKUP || rowAction == ItemLogger.ITEM_THROW || rowAction == ItemLogger.ITEM_SHOOT || rowAction == ItemLogger.ITEM_BREAK || rowAction == ItemLogger.ITEM_DESTROY || rowAction == ItemLogger.ITEM_CREATE || rowAction == ItemLogger.ITEM_SELL || rowAction == ItemLogger.ITEM_BUY) { inventoryAction = ((rowAction == ItemLogger.ITEM_PICKUP || rowAction == ItemLogger.ITEM_CREATE || rowAction == ItemLogger.ITEM_BUY) ? 1 : 0); } @@ -337,7 +347,7 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT continue; // skip inventory & ender chest transactions } - if ((rollbackType == 0 && MaterialUtils.rolledBack((Integer) row[9], false) == 0) || (rollbackType == 1 && MaterialUtils.rolledBack((Integer) row[9], false) == 1)) { + if ((rollbackType == 0 && rowRolledBack == 0) || (rollbackType == 1 && rowRolledBack == 1)) { ItemStack itemstack = new ItemStack(rowType, rowAmount); Object[] populatedStack = RollbackItemHandler.populateItemStack(itemstack, rowMetadata); String faceData = (String) populatedStack[1]; @@ -345,7 +355,7 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT if (!containerInit || rowX != lastX || rowY != lastY || rowZ != lastZ || rowWorldId != lastWorldId || !faceData.equals(lastFace)) { container = null; // container patch 2.14.0 String world = WorldUtils.getWorldName(rowWorldId); - if (world.isEmpty()) { + if (world.length() == 0) { continue; } @@ -357,7 +367,7 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT if (BlockGroup.CONTAINERS.contains(block.getType())) { BlockState blockState = block.getState(); - if (blockState instanceof org.bukkit.block.Jukebox) { + if (blockState instanceof Jukebox) { container = blockState; } else { @@ -376,7 +386,7 @@ else if (BlockGroup.CONTAINERS.contains(Material.ARMOR_STAND) || BlockGroup.CONT else if (entity instanceof ItemFrame) { container = entity; containerType = Material.ITEM_FRAME; - if (!faceData.isEmpty() && (BlockFace.valueOf(faceData) == ((ItemFrame) entity).getFacing())) { + if (faceData.length() > 0 && (BlockFace.valueOf(faceData) == ((ItemFrame) entity).getFacing())) { break; } } diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackUtil.java b/src/main/java/net/coreprotect/database/rollback/RollbackUtil.java index 41a79e2a7..e9268c32c 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackUtil.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackUtil.java @@ -18,7 +18,6 @@ import org.bukkit.entity.ItemFrame; import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.meta.BannerMeta; @@ -45,7 +44,7 @@ public class RollbackUtil extends Lookup { protected static int modifyContainerItems(Material type, Object container, int slot, ItemStack itemstack, int action) { int modifiedArmor = -1; try { - ItemStack[] contents; + ItemStack[] contents = null; if (type != null && type.equals(Material.ARMOR_STAND)) { EntityEquipment equipment = (EntityEquipment) container; @@ -67,9 +66,7 @@ protected static int modifyContainerItems(Material type, Object container, int s } else { ArmorStand armorStand = (ArmorStand) equipment.getHolder(); - if (armorStand != null) { - armorStand.setArms(true); - } + armorStand.setArms(true); switch (slot) { case 4: equipment.setItemInMainHand(itemstack); @@ -96,9 +93,8 @@ else if (type != null && type.equals(Material.ITEM_FRAME)) { } } else if (type != null && type.equals(Material.JUKEBOX)) { - // fix: ClassCastException: handle both jukebox block state and jukebox inventory - if (container instanceof Jukebox) { - Jukebox jukebox = (Jukebox) container; + Jukebox jukebox = (Jukebox) container; + if (jukebox != null) { if (action == 1 && itemstack.getType().name().startsWith("MUSIC_DISC")) { itemstack.setAmount(1); } @@ -110,23 +106,6 @@ else if (type != null && type.equals(Material.JUKEBOX)) { jukebox.setRecord(itemstack); jukebox.update(); } - else if (container instanceof Inventory) { - // handle case where container is actually the jukebox inventory - Inventory inventory = (Inventory) container; - InventoryHolder holder = inventory.getHolder(); - if (holder instanceof Jukebox) { - Jukebox jukebox = (Jukebox) holder; - if (action == 1 && itemstack.getType().name().startsWith("MUSIC_DISC")) { - itemstack.setAmount(1); - } - else { - itemstack.setType(Material.AIR); - itemstack.setAmount(0); - } - jukebox.setRecord(itemstack); - jukebox.update(); - } - } } else { Inventory inventory = (Inventory) container; @@ -160,17 +139,17 @@ else if (container instanceof Inventory) { inventory.setStorageContents(inventoryContents); } else { - addedItem = (inventory.addItem(itemstack).isEmpty()); + addedItem = (inventory.addItem(itemstack).size() == 0); } } else { - addedItem = (inventory.addItem(itemstack).isEmpty()); + addedItem = (inventory.addItem(itemstack).size() == 0); } } if (!addedItem && isPlayerInventory) { PlayerInventory playerInventory = (PlayerInventory) inventory; ItemStack offhand = playerInventory.getItemInOffHand(); - if (offhand.getType() == Material.AIR || (itemstack.isSimilar(offhand) && offhand.getAmount() < offhand.getMaxStackSize())) { + if (offhand == null || offhand.getType() == Material.AIR || (itemstack.isSimilar(offhand) && offhand.getAmount() < offhand.getMaxStackSize())) { ItemStack setOffhand = itemstack.clone(); if (itemstack.isSimilar(offhand)) { setOffhand.setAmount(offhand.getAmount() + 1); @@ -281,17 +260,13 @@ private static void buildFireworkEffect(Builder effectBuilder, Material rowType, FireworkEffect effect = effectBuilder.build(); if ((rowType == Material.FIREWORK_ROCKET)) { FireworkMeta meta = (FireworkMeta) itemstack.getItemMeta(); - if (meta != null) { - meta.addEffect(effect); - itemstack.setItemMeta(meta); - } + meta.addEffect(effect); + itemstack.setItemMeta(meta); } else if ((rowType == Material.FIREWORK_STAR)) { FireworkEffectMeta meta = (FireworkEffectMeta) itemstack.getItemMeta(); - if (meta != null) { - meta.setEffect(effect); - itemstack.setItemMeta(meta); - } + meta.setEffect(effect); + itemstack.setItemMeta(meta); } } catch (Exception e) { @@ -316,32 +291,28 @@ public static Object[] populateItemStack(ItemStack itemstack, Object list) { Material rowType = itemstack.getType(); List metaList = (List) list; - if (metaList != null && !metaList.isEmpty() && !(metaList.get(0) instanceof List)) { + if (metaList.size() > 0 && !(metaList.get(0) instanceof List)) { if (rowType.name().endsWith("_BANNER")) { BannerMeta meta = (BannerMeta) itemstack.getItemMeta(); - if (meta != null) { - for (Object value : metaList) { - if (value instanceof Map) { - Pattern pattern = new Pattern((Map) value); - meta.addPattern(pattern); - } + for (Object value : metaList) { + if (value instanceof Map) { + Pattern pattern = new Pattern((Map) value); + meta.addPattern(pattern); } - itemstack.setItemMeta(meta); } + itemstack.setItemMeta(meta); } else if (BlockGroup.SHULKER_BOXES.contains(rowType)) { BlockStateMeta meta = (BlockStateMeta) itemstack.getItemMeta(); - if (meta != null) { - ShulkerBox shulkerBox = (ShulkerBox) meta.getBlockState(); - for (Object value : metaList) { - ItemStack item = ItemUtils.unserializeItemStackLegacy(value); - if (item != null) { - shulkerBox.getInventory().addItem(item); - } + ShulkerBox shulkerBox = (ShulkerBox) meta.getBlockState(); + for (Object value : metaList) { + ItemStack item = ItemUtils.unserializeItemStackLegacy(value); + if (item != null) { + shulkerBox.getInventory().addItem(item); } - meta.setBlockState(shulkerBox); - itemstack.setItemMeta(meta); } + meta.setBlockState(shulkerBox); + itemstack.setItemMeta(meta); } return new Object[] { slot, faceData, itemstack }; @@ -349,168 +320,152 @@ else if (BlockGroup.SHULKER_BOXES.contains(rowType)) { int itemCount = 0; Builder effectBuilder = FireworkEffect.builder(); - if (list instanceof List) { - for (List> map : (List>>) list) { - if (map.isEmpty()) { - if (itemCount == 3 && (rowType == Material.FIREWORK_ROCKET || rowType == Material.FIREWORK_STAR)) { - buildFireworkEffect(effectBuilder, rowType, itemstack); - itemCount = 0; - } - - itemCount++; - continue; + for (List> map : (List>>) list) { + if (map.size() == 0) { + if (itemCount == 3 && (rowType == Material.FIREWORK_ROCKET || rowType == Material.FIREWORK_STAR)) { + buildFireworkEffect(effectBuilder, rowType, itemstack); + itemCount = 0; } - Map mapData = map.get(0); - if (mapData.get("slot") != null) { - slot = (Integer) mapData.get("slot"); - } - else if (mapData.get("facing") != null) { - faceData = (String) mapData.get("facing"); - } - else if (mapData.get("modifiers") != null) { - ItemMeta itemMeta = itemstack.getItemMeta(); - if (itemMeta != null && itemMeta.hasAttributeModifiers()) { - for (Map.Entry entry : itemMeta.getAttributeModifiers().entries()) { - itemMeta.removeAttributeModifier(entry.getKey(), entry.getValue()); - } - } + itemCount++; + continue; + } + Map mapData = map.get(0); - List modifiers = (List) mapData.get("modifiers"); + if (mapData.get("slot") != null) { + slot = (Integer) mapData.get("slot"); + } + else if (mapData.get("facing") != null) { + faceData = (String) mapData.get("facing"); + } + else if (mapData.get("modifiers") != null) { + ItemMeta itemMeta = itemstack.getItemMeta(); + if (itemMeta.hasAttributeModifiers()) { + for (Map.Entry entry : itemMeta.getAttributeModifiers().entries()) { + itemMeta.removeAttributeModifier(entry.getKey(), entry.getValue()); + } + } - for (Object item : modifiers) { - Map> modifiersMap = (Map>) item; - for (Map.Entry> entry : modifiersMap.entrySet()) { - try { - Attribute attribute = null; - if (entry.getKey() instanceof Attribute) { - attribute = (Attribute) entry.getKey(); - } - else { - attribute = (Attribute) BukkitAdapter.ADAPTER.getRegistryValue((String) entry.getKey(), Attribute.class); - } + List modifiers = (List) mapData.get("modifiers"); - AttributeModifier modifier = AttributeModifier.deserialize(entry.getValue()); - if (itemMeta != null && attribute != null) { - itemMeta.addAttributeModifier(attribute, modifier); - } + for (Object item : modifiers) { + Map> modifiersMap = (Map>) item; + for (Map.Entry> entry : modifiersMap.entrySet()) { + try { + Attribute attribute = null; + if (entry.getKey() instanceof Attribute) { + attribute = (Attribute) entry.getKey(); } - catch (IllegalArgumentException e) { - // AttributeModifier already exists + else { + attribute = (Attribute) BukkitAdapter.ADAPTER.getRegistryValue((String) entry.getKey(), Attribute.class); } + + AttributeModifier modifier = AttributeModifier.deserialize(entry.getValue()); + itemMeta.addAttributeModifier(attribute, modifier); + } + catch (IllegalArgumentException e) { + // AttributeModifier already exists } } - - itemstack.setItemMeta(itemMeta); } - else if (itemCount == 0) { - ItemMeta meta = ItemUtils.deserializeItemMeta(itemstack.getItemMeta().getClass(), map.get(0)); - itemstack.setItemMeta(meta); - if (map.size() > 1 && (rowType == Material.POTION)) { - PotionMeta subMeta = (PotionMeta) itemstack.getItemMeta(); - if (subMeta != null) { - org.bukkit.Color color = org.bukkit.Color.deserialize(map.get(1)); - subMeta.setColor(color); - itemstack.setItemMeta(subMeta); - } + itemstack.setItemMeta(itemMeta); + } + else if (itemCount == 0) { + ItemMeta meta = ItemUtils.deserializeItemMeta(itemstack.getItemMeta().getClass(), map.get(0)); + itemstack.setItemMeta(meta); + + if (map.size() > 1 && (rowType == Material.POTION)) { + PotionMeta subMeta = (PotionMeta) itemstack.getItemMeta(); + org.bukkit.Color color = org.bukkit.Color.deserialize(map.get(1)); + subMeta.setColor(color); + itemstack.setItemMeta(subMeta); + } + } + else { + if ((rowType == Material.LEATHER_HORSE_ARMOR) || (rowType == Material.LEATHER_HELMET) || (rowType == Material.LEATHER_CHESTPLATE) || (rowType == Material.LEATHER_LEGGINGS) || (rowType == Material.LEATHER_BOOTS)) { // leather armor + for (Map colorData : map) { + LeatherArmorMeta meta = (LeatherArmorMeta) itemstack.getItemMeta(); + org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); + meta.setColor(color); + itemstack.setItemMeta(meta); } } - else { - if ((rowType == Material.LEATHER_HORSE_ARMOR) || (rowType == Material.LEATHER_HELMET) || (rowType == Material.LEATHER_CHESTPLATE) || (rowType == Material.LEATHER_LEGGINGS) || (rowType == Material.LEATHER_BOOTS)) { // leather armor - for (Map colorData : map) { - LeatherArmorMeta meta = (LeatherArmorMeta) itemstack.getItemMeta(); - org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); - meta.setColor(color); - itemstack.setItemMeta(meta); - } + else if ((rowType == Material.POTION)) { // potion + for (Map potionData : map) { + PotionMeta meta = (PotionMeta) itemstack.getItemMeta(); + PotionEffect effect = new PotionEffect(potionData); + meta.addCustomEffect(effect, true); + itemstack.setItemMeta(meta); } - else if ((rowType == Material.POTION)) { // potion - for (Map potionData : map) { - PotionMeta meta = (PotionMeta) itemstack.getItemMeta(); - PotionEffect effect = new PotionEffect(potionData); - if (meta != null) { - meta.addCustomEffect(effect, true); - itemstack.setItemMeta(meta); - } - } + } + else if (rowType.name().endsWith("_BANNER")) { + for (Map patternData : map) { + BannerMeta meta = (BannerMeta) itemstack.getItemMeta(); + Pattern pattern = new Pattern(patternData); + meta.addPattern(pattern); + itemstack.setItemMeta(meta); } - else if (rowType.name().endsWith("_BANNER")) { - for (Map patternData : map) { - BannerMeta meta = (BannerMeta) itemstack.getItemMeta(); - Pattern pattern = new Pattern(patternData); - if (meta != null) { - meta.addPattern(pattern); - itemstack.setItemMeta(meta); - } + } + else if ((rowType == Material.CROSSBOW)) { + CrossbowMeta meta = (CrossbowMeta) itemstack.getItemMeta(); + for (Map itemData : map) { + ItemStack crossbowItem = ItemUtils.unserializeItemStack(itemData); + if (crossbowItem != null) { + meta.addChargedProjectile(crossbowItem); } } - else if ((rowType == Material.CROSSBOW)) { - CrossbowMeta meta = (CrossbowMeta) itemstack.getItemMeta(); - if (meta != null) { - for (Map itemData : map) { - ItemStack crossbowItem = ItemUtils.unserializeItemStack(itemData); - if (crossbowItem != null) { - meta.addChargedProjectile(crossbowItem); - } - } - itemstack.setItemMeta(meta); - } + itemstack.setItemMeta(meta); + } + else if (rowType == Material.MAP || rowType == Material.FILLED_MAP) { + for (Map colorData : map) { + MapMeta meta = (MapMeta) itemstack.getItemMeta(); + org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); + meta.setColor(color); + itemstack.setItemMeta(meta); } - else if (rowType == Material.MAP || rowType == Material.FILLED_MAP) { - for (Map colorData : map) { - MapMeta meta = (MapMeta) itemstack.getItemMeta(); - if (meta != null) { - org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); - meta.setColor(color); - itemstack.setItemMeta(meta); - } + } + else if ((rowType == Material.FIREWORK_ROCKET) || (rowType == Material.FIREWORK_STAR)) { + if (itemCount == 1) { + effectBuilder = FireworkEffect.builder(); + for (Map fireworkData : map) { + org.bukkit.FireworkEffect.Type type = (org.bukkit.FireworkEffect.Type) fireworkData.getOrDefault("type", org.bukkit.FireworkEffect.Type.BALL); + boolean hasFlicker = (Boolean) fireworkData.get("flicker"); + boolean hasTrail = (Boolean) fireworkData.get("trail"); + effectBuilder.with(type); + effectBuilder.flicker(hasFlicker); + effectBuilder.trail(hasTrail); } } - else if ((rowType == Material.FIREWORK_ROCKET) || (rowType == Material.FIREWORK_STAR)) { - if (itemCount == 1) { - effectBuilder = FireworkEffect.builder(); - for (Map fireworkData : map) { - org.bukkit.FireworkEffect.Type type = (org.bukkit.FireworkEffect.Type) fireworkData.getOrDefault("type", org.bukkit.FireworkEffect.Type.BALL); - boolean hasFlicker = (Boolean) fireworkData.get("flicker"); - boolean hasTrail = (Boolean) fireworkData.get("trail"); - effectBuilder.with(type); - effectBuilder.flicker(hasFlicker); - effectBuilder.trail(hasTrail); - } - } - else if (itemCount == 2) { - for (Map colorData : map) { - org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); - effectBuilder.withColor(color); - } - } - else if (itemCount == 3) { - for (Map colorData : map) { - org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); - effectBuilder.withFade(color); - } - buildFireworkEffect(effectBuilder, rowType, itemstack); - itemCount = 0; + else if (itemCount == 2) { + for (Map colorData : map) { + org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); + effectBuilder.withColor(color); } } - else if ((rowType == Material.SUSPICIOUS_STEW)) { - for (Map suspiciousStewData : map) { - SuspiciousStewMeta meta = (SuspiciousStewMeta) itemstack.getItemMeta(); - PotionEffect effect = new PotionEffect(suspiciousStewData); - if (meta != null) { - meta.addCustomEffect(effect, true); - itemstack.setItemMeta(meta); - } + else if (itemCount == 3) { + for (Map colorData : map) { + org.bukkit.Color color = org.bukkit.Color.deserialize(colorData); + effectBuilder.withFade(color); } + buildFireworkEffect(effectBuilder, rowType, itemstack); + itemCount = 0; } - else { - BukkitAdapter.ADAPTER.setItemMeta(rowType, itemstack, map); + } + else if ((rowType == Material.SUSPICIOUS_STEW)) { + for (Map suspiciousStewData : map) { + SuspiciousStewMeta meta = (SuspiciousStewMeta) itemstack.getItemMeta(); + PotionEffect effect = new PotionEffect(suspiciousStewData); + meta.addCustomEffect(effect, true); + itemstack.setItemMeta(meta); } } - - itemCount++; + else { + BukkitAdapter.ADAPTER.setItemMeta(rowType, itemstack, map); + } } + + itemCount++; } } catch (Exception e) { @@ -580,4 +535,34 @@ public static List deserializeMetadata(byte[] metadata) { public static void queueEntitySpawn(String user, BlockState block, EntityType type, int data) { Queue.queueEntitySpawn(user, block, type, data); } + + /** + * Queues a skull update operation for processing. + * + * @param user + * The username of the player + * @param block + * The block state to update + * @param rowId + * The row ID for the skull data + */ + public static void queueSkullUpdate(String user, BlockState block, int rowId) { + Queue.queueSkullUpdate(user, block, rowId); + } + + /** + * Queues a sign update operation for processing. + * + * @param user + * The username of the player + * @param block + * The block state to update + * @param action + * The action type + * @param time + * The time of the update + */ + public static void queueSignUpdate(String user, BlockState block, int action, int time) { + Queue.queueSignUpdate(user, block, action, time); + } } diff --git a/src/main/java/net/coreprotect/utility/BlockUtils.java b/src/main/java/net/coreprotect/utility/BlockUtils.java index 3eef358ab..b0bd6807f 100644 --- a/src/main/java/net/coreprotect/utility/BlockUtils.java +++ b/src/main/java/net/coreprotect/utility/BlockUtils.java @@ -22,7 +22,6 @@ import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; -import net.coreprotect.thread.CacheHandler; import net.coreprotect.thread.Scheduler; public class BlockUtils { @@ -41,8 +40,7 @@ public static byte[] stringToByteData(String string, int type) { return result; } - BlockData defaultBlockData = createBlockData(material); - if (defaultBlockData != null && material.isBlock() && !defaultBlockData.getAsString().equals(string) && string.startsWith(NAMESPACE + material.name().toLowerCase(Locale.ROOT) + "[") && string.endsWith("]")) { + if (material.isBlock() && !createBlockData(material).getAsString().equals(string) && string.startsWith(NAMESPACE + material.name().toLowerCase(Locale.ROOT) + "[") && string.endsWith("]")) { String substring = string.substring(material.name().length() + 11, string.length() - 1); String[] blockDataSplit = substring.split(","); ArrayList blockDataArray = new ArrayList<>(); @@ -82,7 +80,7 @@ public static String byteDataToString(byte[] data, int type) { } result = new String(data, StandardCharsets.UTF_8); - if (!result.isEmpty()) { + if (result.length() > 0) { if (result.matches("\\d+")) { result = result + ","; } @@ -91,7 +89,7 @@ public static String byteDataToString(byte[] data, int type) { ArrayList blockDataArray = new ArrayList<>(); for (String blockData : blockDataSplit) { String block = MaterialUtils.getBlockDataString(Integer.parseInt(blockData)); - if (!block.isEmpty()) { + if (block.length() > 0) { blockDataArray.add(block); } } @@ -144,7 +142,7 @@ public static boolean iceBreakCheck(BlockState block, String user, Material type if (type.equals(Material.ICE)) { // Ice block int unixtimestamp = (int) (System.currentTimeMillis() / 1000L); int wid = WorldUtils.getWorldId(block.getWorld().getName()); - CacheHandler.lookupCache.put("" + block.getX() + "." + block.getY() + "." + block.getZ() + "." + wid, new Object[] { unixtimestamp, user, Material.WATER }); + net.coreprotect.thread.CacheHandler.lookupCache.put("" + block.getX() + "." + block.getY() + "." + block.getZ() + "." + wid + "", new Object[] { unixtimestamp, user, Material.WATER }); return true; } return false; @@ -164,7 +162,7 @@ public static BlockData createBlockData(Material material) { } public static void prepareTypeAndData(Map map, Block block, Material type, BlockData blockData, boolean update) { - if (blockData == null && type != null) { + if (blockData == null) { blockData = createBlockData(type); } @@ -233,7 +231,7 @@ public static List processMeta(BlockState block) { if (block instanceof CommandBlock) { CommandBlock commandBlock = (CommandBlock) block; String command = commandBlock.getCommand(); - if (!command.isEmpty()) { + if (command.length() > 0) { meta.add(command); } } @@ -251,7 +249,7 @@ else if (block instanceof ShulkerBox) { int slot = 0; for (ItemStack itemStack : inventory) { Map itemMap = ItemUtils.serializeItemStackLegacy(itemStack, null, slot); - if (!itemMap.isEmpty()) { + if (itemMap.size() > 0) { meta.add(itemMap); } slot++; diff --git a/src/main/java/net/coreprotect/utility/Teleport.java b/src/main/java/net/coreprotect/utility/Teleport.java index 7997d52c0..2e5e64e79 100644 --- a/src/main/java/net/coreprotect/utility/Teleport.java +++ b/src/main/java/net/coreprotect/utility/Teleport.java @@ -1,5 +1,6 @@ package net.coreprotect.utility; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -17,7 +18,7 @@ import net.coreprotect.model.BlockGroup; import net.coreprotect.paper.PaperAdapter; import net.coreprotect.thread.Scheduler; -// import net.coreprotect.utility.BlockUtils; +import net.coreprotect.utility.BlockUtils; public class Teleport { @@ -29,8 +30,7 @@ private Teleport() { public static void performSafeTeleport(Player player, Location location, boolean enforceTeleport) { try { - Set unsafeBlocks = new HashSet<>(); - unsafeBlocks.add(Material.LAVA); + Set unsafeBlocks = new HashSet<>(Arrays.asList(Material.LAVA)); unsafeBlocks.addAll(BlockGroup.FIRE); int worldHeight = location.getWorld().getMaxHeight(); @@ -48,9 +48,10 @@ public static void performSafeTeleport(Player player, Location location, boolean above = worldHeight; } - final Block block1 = location.getWorld().getBlockAt(playerX, checkY, playerZ); + Block block1 = location.getWorld().getBlockAt(playerX, checkY, playerZ); Block block2 = location.getWorld().getBlockAt(playerX, above, playerZ); Material type1 = block1.getType(); + Material type2 = block2.getType(); if (BlockUtils.passableBlock(block1) && BlockUtils.passableBlock(block2)) { if (unsafeBlocks.contains(type1)) { @@ -64,19 +65,15 @@ public static void performSafeTeleport(Player player, Location location, boolean if (checkY < worldHeight && unsafeBlocks.contains(blockBelow.getType())) { alert = true; - final Location revertLocation = block1.getLocation(); - final BlockData revertBlockData = block1.getBlockData(); + Location revertLocation = block1.getLocation(); + BlockData revertBlockData = block1.getBlockData(); revertBlocks.put(revertLocation, revertBlockData); - - Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), () -> { - if (!ConfigHandler.isFolia) { - block1.setType(Material.BARRIER); - } - else { - block1.setType(Material.DIRT); - } - }, revertLocation, 0); - + if (!ConfigHandler.isFolia) { + block1.setType(Material.BARRIER); + } + else { + block1.setType(Material.DIRT); + } checkY++; Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), () -> { @@ -111,6 +108,7 @@ public static void performSafeTeleport(Player player, Location location, boolean } if (!enforceTeleport) { + // Only send a message if the player was moved by at least 1 block if (location.getY() >= (oldY + 1.00)) { Chat.sendMessage(player, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.TELEPORTED_SAFETY)); } diff --git a/src/main/java/net/coreprotect/utility/Util.java b/src/main/java/net/coreprotect/utility/Util.java index af0222698..b5d0707fa 100755 --- a/src/main/java/net/coreprotect/utility/Util.java +++ b/src/main/java/net/coreprotect/utility/Util.java @@ -6,15 +6,13 @@ import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; -import net.coreprotect.CoreProtect; -import net.coreprotect.config.ConfigHandler; -import net.coreprotect.thread.Scheduler; +import net.coreprotect.consumer.Queue; /** * Central utility class that provides access to various utility functions. * Most methods delegate to specialized utility classes. */ -public class Util { +public class Util extends Queue { public static final Pattern tagParser = Pattern.compile(Chat.COMPONENT_TAG_OPEN + "(.+?)" + Chat.COMPONENT_TAG_CLOSE + "|(.+?)", Pattern.DOTALL); @@ -23,12 +21,6 @@ private Util() { } public static void sendBlockChange(Player player, Location location, BlockData blockData) { - // folia: wrapped sendBlockChange in a scheduler task - if (ConfigHandler.isFolia) { - Scheduler.runTask(CoreProtect.getInstance(), () -> player.sendBlockChange(location, blockData), player); - } - else { - player.sendBlockChange(location, blockData); - } + player.sendBlockChange(location, blockData); } } diff --git a/src/main/java/net/coreprotect/utility/entity/HangingUtil.java b/src/main/java/net/coreprotect/utility/entity/HangingUtil.java index f8e714d2b..4ae634859 100644 --- a/src/main/java/net/coreprotect/utility/entity/HangingUtil.java +++ b/src/main/java/net/coreprotect/utility/entity/HangingUtil.java @@ -14,10 +14,8 @@ import org.bukkit.entity.Painting; import org.bukkit.inventory.ItemStack; -import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; import net.coreprotect.model.BlockGroup; -import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.BlockUtils; import net.coreprotect.utility.MaterialUtils; @@ -31,9 +29,9 @@ private HangingUtil() { public static void spawnHanging(final BlockState blockstate, final Material rowType, final String hangingData, final int rowData) { try { Block block = blockstate.getBlock(); - final int x = block.getX(); - final int y = block.getY(); - final int z = block.getZ(); + int x = block.getX(); + int y = block.getY(); + int z = block.getZ(); BlockFace hangingFace = null; if (hangingData != null && !hangingData.contains(":") && hangingData.contains("=")) { @@ -50,8 +48,7 @@ public static void spawnHanging(final BlockState blockstate, final Material rowT Location el = e.getLocation(); if (el.getBlockX() == x && el.getBlockY() == y && el.getBlockZ() == z) { if (hangingFace == null || ((Hanging) e).getFacing() == hangingFace) { - // folia: wrapped entity removal in a scheduler task - Scheduler.runTask(CoreProtect.getInstance(), e::remove, e); + e.remove(); break; } } @@ -104,10 +101,7 @@ else if (!BlockUtils.solidBlock(BlockUtils.getType(block.getRelative(BlockFace.S if (faceSet != null && face != null) { if (rowType.equals(Material.PAINTING)) { String name = MaterialUtils.getArtName(rowData); - final Art painting = Art.getByName(name.toUpperCase(Locale.ROOT)); - if (painting == null) { - return; // Art type not found - } + Art painting = Art.getByName(name.toUpperCase(Locale.ROOT)); int height = painting.getBlockHeight(); int width = painting.getBlockWidth(); int paintingX = x; @@ -128,60 +122,44 @@ else if (faceSet.equals(BlockFace.SOUTH)) { } } } - final Block spawnBlock = hangingFace != null ? block : block.getRelative(face); + Block spawnBlock = hangingFace != null ? block : block.getRelative(face); if (hangingFace == null) { BlockUtils.setTypeAndData(spawnBlock, Material.AIR, null, true); } - - // folia: wrapped entity spawn in a scheduler task. - final int finalPaintingX = paintingX; - final int finalPaintingY = paintingY; - final int finalPaintingZ = paintingZ; - final BlockFace finalFaceSet = faceSet; - - Scheduler.runTask(CoreProtect.getInstance(), () -> { - Painting hanging = null; - try { - hanging = spawnBlock.getWorld().spawn(spawnBlock.getLocation(), Painting.class); - } - catch (Exception e) { - e.printStackTrace(); - } - if (hanging != null) { - hanging.teleport(spawnBlock.getWorld().getBlockAt(finalPaintingX, finalPaintingY, finalPaintingZ).getLocation()); - hanging.setFacingDirection(finalFaceSet, true); - hanging.setArt(painting, true); - } - }, spawnBlock.getLocation()); + Painting hanging = null; + try { + hanging = block.getWorld().spawn(spawnBlock.getLocation(), Painting.class); + } + catch (Exception e) { + } + if (hanging != null) { + hanging.teleport(block.getWorld().getBlockAt(paintingX, paintingY, paintingZ).getLocation()); + hanging.setFacingDirection(faceSet, true); + hanging.setArt(painting, true); + } } else if (BukkitAdapter.ADAPTER.isItemFrame(rowType)) { - final Block spawnBlock = hangingFace != null ? block : block.getRelative(face); - if (hangingFace == null) { - BlockUtils.setTypeAndData(spawnBlock, Material.AIR, null, true); - } - - // folia: wrapped entity spawn in a scheduler task. - final BlockFace finalFaceSet = faceSet; - Scheduler.runTask(CoreProtect.getInstance(), () -> { - try { - Class itemFrame = BukkitAdapter.ADAPTER.getFrameClass(rowType); - Entity entity = spawnBlock.getWorld().spawn(spawnBlock.getLocation(), itemFrame); - if (entity instanceof ItemFrame) { - ItemFrame hanging = (ItemFrame) entity; - hanging.teleport(spawnBlock.getWorld().getBlockAt(x, y, z).getLocation()); - hanging.setFacingDirection(finalFaceSet, true); - - Material type = MaterialUtils.getType(rowData); - if (type != null) { - ItemStack istack = new ItemStack(type, 1); - hanging.setItem(istack); - } - } + try { + Block spawnBlock = hangingFace != null ? block : block.getRelative(face); + if (hangingFace == null) { + BlockUtils.setTypeAndData(spawnBlock, Material.AIR, null, true); } - catch (Exception e) { - e.printStackTrace(); + Class itemFrame = BukkitAdapter.ADAPTER.getFrameClass(rowType); + Entity entity = block.getWorld().spawn(spawnBlock.getLocation(), itemFrame); + if (entity instanceof ItemFrame) { + ItemFrame hanging = (ItemFrame) entity; + hanging.teleport(block.getWorld().getBlockAt(x, y, z).getLocation()); + hanging.setFacingDirection(faceSet, true); + + Material type = MaterialUtils.getType(rowData); + if (type != null) { + ItemStack istack = new ItemStack(type, 1); + hanging.setItem(istack); + } } - }, spawnBlock.getLocation()); + } + catch (Exception e) { + } } } } @@ -202,14 +180,12 @@ public static void removeHanging(final BlockState block, final String hangingDat } } - final BlockFace finalHangingFace = hangingFace; - for (final Entity e : block.getChunk().getEntities()) { + for (Entity e : block.getChunk().getEntities()) { if (e instanceof ItemFrame || e instanceof Painting) { Location el = e.getLocation(); if (el.getBlockX() == block.getX() && el.getBlockY() == block.getY() && el.getBlockZ() == block.getZ()) { - if (finalHangingFace == null || ((Hanging) e).getFacing() == finalHangingFace) { - // folia: wrapped entity removal in a scheduler task. - Scheduler.runTask(CoreProtect.getInstance(), e::remove, e); + if (hangingFace == null || ((Hanging) e).getFacing() == hangingFace) { + e.remove(); } } } From 038dcf922d509d0bddf931e06d43e93c8eabc124 Mon Sep 17 00:00:00 2001 From: leir4iks Date: Mon, 3 Nov 2025 21:27:56 +0200 Subject: [PATCH 10/22] sorry --- .../coreprotect/command/TeleportCommand.java | 9 +++-- .../database/rollback/Rollback.java | 8 +++- .../rollback/RollbackBlockHandler.java | 20 ++++++++-- .../rollback/RollbackEntityHandler.java | 40 ++++++++++++++++--- .../listener/block/BlockDispenseListener.java | 2 +- .../utility/entity/HangingUtil.java | 17 +++++++- 6 files changed, 79 insertions(+), 17 deletions(-) diff --git a/src/main/java/net/coreprotect/command/TeleportCommand.java b/src/main/java/net/coreprotect/command/TeleportCommand.java index 6a5bbc518..bf162a00e 100644 --- a/src/main/java/net/coreprotect/command/TeleportCommand.java +++ b/src/main/java/net/coreprotect/command/TeleportCommand.java @@ -56,6 +56,9 @@ protected static void runCommand(CommandSender player, boolean permission, Strin World world = location.getWorld(); if (wid > -1) { world = Bukkit.getServer().getWorld(WorldUtils.getWorldName(wid)); + if (world == null) { + return; + } } String x = null; @@ -101,10 +104,10 @@ else if (y == null) { location.setZ(Double.parseDouble(z)); if (ConfigHandler.isFolia) { - location.getWorld().getChunkAtAsync(location).thenAccept(chunk -> { - Scheduler.runTask(CoreProtect.getInstance(), () -> { + CoreProtect.getInstance().getServer().getRegionScheduler().run(CoreProtect.getInstance(), location, task -> { + location.getWorld().getChunkAtAsync(location).thenAccept(chunk -> { Teleport.performSafeTeleport(((Player) player), location, true); - }, location); + }); }); } else { diff --git a/src/main/java/net/coreprotect/database/rollback/Rollback.java b/src/main/java/net/coreprotect/database/rollback/Rollback.java index 072d0fb14..8626bf6d8 100644 --- a/src/main/java/net/coreprotect/database/rollback/Rollback.java +++ b/src/main/java/net/coreprotect/database/rollback/Rollback.java @@ -11,6 +11,8 @@ import java.util.Map.Entry; import java.util.TreeMap; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -252,7 +254,11 @@ else if (table == 1) { // container // wait for all chunk processing tasks to complete CompletableFuture allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); - allFutures.get(); + try { + allFutures.get(30, TimeUnit.SECONDS); + } + catch (TimeoutException e) { + } int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); int itemCount = rollbackHashData[0]; diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java index 871a0cab2..beb811f58 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java @@ -39,12 +39,14 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; import net.coreprotect.config.ConfigHandler; import net.coreprotect.consumer.Queue; import net.coreprotect.model.BlockGroup; import net.coreprotect.paper.PaperAdapter; import net.coreprotect.thread.CacheHandler; +import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.BlockUtils; import net.coreprotect.utility.ChestTool; import net.coreprotect.utility.EntityUtils; @@ -144,7 +146,12 @@ else if ((rowType == Material.AIR) && ((oldTypeMaterial == Material.END_CRYSTAL) for (Entity entity : block.getChunk().getEntities()) { if (entity instanceof EnderCrystal) { if (entity.getLocation().getBlockX() == rowX && entity.getLocation().getBlockY() == rowY && entity.getLocation().getBlockZ() == rowZ) { - entity.remove(); + if (ConfigHandler.isFolia) { + Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); + } + else { + entity.remove(); + } } } } @@ -172,7 +179,12 @@ else if (BlockGroup.CONTAINERS.contains(Material.ARMOR_STAND)) { entityLocation.setY(entityLocation.getY() - 1.99); PaperAdapter.ADAPTER.teleportAsync(entity, entityLocation); - entity.remove(); + if (ConfigHandler.isFolia) { + Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); + } + else { + entity.remove(); + } } } } @@ -492,7 +504,7 @@ else if (rowType != Material.AIR && rawBlockData instanceof Bed) { /** * Update the block count in the rollback hash - * + * * @param userString * The username for this rollback * @param increment @@ -511,7 +523,7 @@ protected static void updateBlockCount(String userString, int increment) { /** * Apply all pending block changes to the world - * + * * @param chunkChanges * Map of blocks to change * @param preview diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java index c6357604c..cbbb486b1 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java @@ -1,5 +1,10 @@ package net.coreprotect.database.rollback; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; @@ -8,8 +13,10 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; +import net.coreprotect.CoreProtect; import net.coreprotect.config.ConfigHandler; import net.coreprotect.thread.CacheHandler; +import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.EntityUtils; import net.coreprotect.utility.WorldUtils; @@ -56,10 +63,16 @@ public static int processEntity(Object[] row, int rollbackType, String finalUser if (ConfigHandler.isFolia) { // Folia - load chunk async before processing - bukkitWorld.getChunkAtAsync(rowX >> 4, rowZ >> 4, true).thenAccept(chunk -> { - processEntityLogic(row, rollbackType, finalUserString, oldTypeRaw, rowTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUserId, rowUser, bukkitWorld); + CompletableFuture future = bukkitWorld.getChunkAtAsync(rowX >> 4, rowZ >> 4, true).thenApply(chunk -> { + return processEntityLogic(row, rollbackType, finalUserString, oldTypeRaw, rowTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUserId, rowUser, bukkitWorld); }); - return 1; // assume task is queued successfully + try { + return future.get(10, TimeUnit.SECONDS); + } + catch (InterruptedException | ExecutionException | TimeoutException e) { + e.printStackTrace(); + return 0; + } } else { if (!bukkitWorld.isChunkLoaded(rowX >> 4, rowZ >> 4)) { @@ -110,7 +123,12 @@ else if (rowTypeRaw <= 0) { if (id == entityId) { updateEntityCount(finalUserString, 1); removed = true; - entity.remove(); + if (ConfigHandler.isFolia) { + Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); + } + else { + entity.remove(); + } break; } } @@ -124,7 +142,12 @@ else if (rowTypeRaw <= 0) { if (entityx >= xmin && entityx <= xmax && entityY >= ymin && entityY <= ymax && entityZ >= zmin && entityZ <= zmax) { updateEntityCount(finalUserString, 1); removed = true; - entity.remove(); + if (ConfigHandler.isFolia) { + Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); + } + else { + entity.remove(); + } break; } } @@ -137,7 +160,12 @@ else if (rowTypeRaw <= 0) { if (id == entityId) { updateEntityCount(finalUserString, 1); removed = true; - entity.remove(); + if (ConfigHandler.isFolia) { + Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); + } + else { + entity.remove(); + } break; } } diff --git a/src/main/java/net/coreprotect/listener/block/BlockDispenseListener.java b/src/main/java/net/coreprotect/listener/block/BlockDispenseListener.java index 45ee7750e..175198d64 100644 --- a/src/main/java/net/coreprotect/listener/block/BlockDispenseListener.java +++ b/src/main/java/net/coreprotect/listener/block/BlockDispenseListener.java @@ -58,7 +58,7 @@ protected void onBlockDispense(BlockDispenseEvent event) { forceItem = true; // droppers always drop items } - ItemStack[] inventory = ((InventoryHolder) block.getState()).getInventory().getContents(); + ItemStack[] inventory = ((InventoryHolder) block.getState()).getInventory().getStorageContents(); if (forceItem) { inventory = Arrays.copyOf(inventory, inventory.length + 1); inventory[inventory.length - 1] = item; diff --git a/src/main/java/net/coreprotect/utility/entity/HangingUtil.java b/src/main/java/net/coreprotect/utility/entity/HangingUtil.java index 4ae634859..07483ee8f 100644 --- a/src/main/java/net/coreprotect/utility/entity/HangingUtil.java +++ b/src/main/java/net/coreprotect/utility/entity/HangingUtil.java @@ -14,8 +14,11 @@ import org.bukkit.entity.Painting; import org.bukkit.inventory.ItemStack; +import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; +import net.coreprotect.config.ConfigHandler; import net.coreprotect.model.BlockGroup; +import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.BlockUtils; import net.coreprotect.utility.MaterialUtils; @@ -48,7 +51,12 @@ public static void spawnHanging(final BlockState blockstate, final Material rowT Location el = e.getLocation(); if (el.getBlockX() == x && el.getBlockY() == y && el.getBlockZ() == z) { if (hangingFace == null || ((Hanging) e).getFacing() == hangingFace) { - e.remove(); + if (ConfigHandler.isFolia) { + Scheduler.runTask(CoreProtect.getInstance(), e::remove, e); + } + else { + e.remove(); + } break; } } @@ -185,7 +193,12 @@ public static void removeHanging(final BlockState block, final String hangingDat Location el = e.getLocation(); if (el.getBlockX() == block.getX() && el.getBlockY() == block.getY() && el.getBlockZ() == block.getZ()) { if (hangingFace == null || ((Hanging) e).getFacing() == hangingFace) { - e.remove(); + if (ConfigHandler.isFolia) { + Scheduler.runTask(CoreProtect.getInstance(), e::remove, e); + } + else { + e.remove(); + } } } } From 646305b6fab4bca818ecaf99d00d3ad6c6896094 Mon Sep 17 00:00:00 2001 From: leir4iks Date: Thu, 6 Nov 2025 12:19:00 +0200 Subject: [PATCH 11/22] back + rewrite rewrite --- .../java/net/coreprotect/CoreProtectAPI.java | 135 +++++++++++++----- .../database/rollback/Rollback.java | 96 ++++++++++--- .../rollback/RollbackBlockHandler.java | 16 +-- .../rollback/RollbackEntityHandler.java | 66 --------- .../database/rollback/RollbackProcessor.java | 101 ++++--------- .../listener/block/BlockDispenseListener.java | 4 +- .../block/BlockFertilizeListener.java | 5 +- .../coreprotect/listener/block/BlockUtil.java | 5 +- .../net/coreprotect/thread/CacheHandler.java | 2 +- .../net/coreprotect/thread/Scheduler.java | 6 +- .../utility/entity/HangingUtil.java | 17 +-- 11 files changed, 218 insertions(+), 235 deletions(-) diff --git a/src/main/java/net/coreprotect/CoreProtectAPI.java b/src/main/java/net/coreprotect/CoreProtectAPI.java index 757427b36..2174370e6 100755 --- a/src/main/java/net/coreprotect/CoreProtectAPI.java +++ b/src/main/java/net/coreprotect/CoreProtectAPI.java @@ -4,13 +4,18 @@ import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; import org.bukkit.Bukkit; +import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Server; +import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; @@ -29,6 +34,7 @@ import net.coreprotect.listener.player.InventoryChangeListener; import net.coreprotect.utility.Chat; import net.coreprotect.utility.MaterialUtils; +import net.coreprotect.utility.WorldUtils; /** * The main API class for CoreProtect. @@ -47,7 +53,7 @@ public static class ParseResult extends net.coreprotect.api.result.ParseResult { /** * Creates a new ParseResult from string array data. - * + * * @param data * The string array data to parse */ @@ -58,7 +64,7 @@ public ParseResult(String[] data) { /** * Converts a list of objects to a map for internal processing - * + * * @param list * List of objects to convert * @return Map with objects as keys and Boolean false as values @@ -83,7 +89,7 @@ else if (value instanceof Integer) { /** * Returns the current API version. - * + * * @return The API version as an integer */ public int APIVersion() { @@ -92,7 +98,7 @@ public int APIVersion() { /** * Performs a block lookup at the specified block. - * + * * @param block * The block to look up * @param time @@ -108,7 +114,7 @@ public List blockLookup(Block block, int time) { /** * Performs a lookup on the queue data for the specified block. - * + * * @param block * The block to look up * @return List of results @@ -119,7 +125,7 @@ public List queueLookup(Block block) { /** * Performs a lookup on session data for the specified user. - * + * * @param user * The user to look up * @param time @@ -132,7 +138,7 @@ public List sessionLookup(String user, int time) { /** * Determines if a user has placed a block at the specified location. - * + * * @param user * The username to check * @param block @@ -164,7 +170,7 @@ public boolean hasPlaced(String user, Block block, int time, int offset) { /** * Determines if a user has removed a block at the specified location. - * + * * @param user * The username to check * @param block @@ -196,7 +202,7 @@ public boolean hasRemoved(String user, Block block, int time, int offset) { /** * Gets the current time in milliseconds. Protected to allow mocking in tests. - * + * * @return Current time in milliseconds */ protected long getCurrentTimeMillis() { @@ -205,7 +211,7 @@ protected long getCurrentTimeMillis() { /** * Checks if the CoreProtect API is enabled. - * + * * @return True if the API is enabled */ public boolean isEnabled() { @@ -214,7 +220,7 @@ public boolean isEnabled() { /** * Logs a chat message for a player. - * + * * @param player * The player who sent the message * @param message @@ -237,7 +243,7 @@ public boolean logChat(Player player, String message) { /** * Logs a command executed by a player. - * + * * @param player * The player who executed the command * @param command @@ -260,7 +266,7 @@ public boolean logCommand(Player player, String command) { /** * Logs an interaction by a user at a location. - * + * * @param user * The username * @param location @@ -278,7 +284,7 @@ public boolean logInteraction(String user, Location location) { /** * Logs a container transaction by a user at a location. - * + * * @param user * The username * @param location @@ -295,7 +301,7 @@ public boolean logContainerTransaction(String user, Location location) { /** * Logs a block placement by a user. - * + * * @param user * The username * @param blockState @@ -313,7 +319,7 @@ public boolean logPlacement(String user, BlockState blockState) { /** * Logs a block placement by a user with a specific material and block data. - * + * * @param user * The username * @param location @@ -343,7 +349,7 @@ public boolean logPlacement(String user, Location location, Material type, Block /** * Logs a block placement by a user with a specific material and data value. - * + * * @param user * The username * @param location @@ -367,7 +373,7 @@ public boolean logPlacement(String user, Location location, Material type, byte /** * Logs a block removal by a user. - * + * * @param user * The username * @param blockState @@ -385,7 +391,7 @@ public boolean logRemoval(String user, BlockState blockState) { /** * Logs a block removal by a user with a specific material and block data. - * + * * @param user * The username * @param location @@ -414,7 +420,7 @@ public boolean logRemoval(String user, Location location, Material type, BlockDa /** * Logs a block removal by a user with a specific material and data value. - * + * * @param user * The username * @param location @@ -438,7 +444,7 @@ public boolean logRemoval(String user, Location location, Material type, byte da /** * Parses lookup results into a ParseResult object. - * + * * @param results * The results to parse * @return A ParseResult object containing the parsed data @@ -449,7 +455,7 @@ public ParseResult parseResult(String[] results) { /** * Performs a lookup operation with various filters. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -478,7 +484,7 @@ public List performLookup(int time, List restrictUsers, List performLookup(String user, int time, int radius, Location /** * Performs a partial lookup operation with various filters and pagination support. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -538,7 +544,7 @@ public List performPartialLookup(int time, List restrictUsers, /** * Performs a partial lookup operation with basic filters and pagination support. - * + * * @param user * The user to include in the lookup * @param time @@ -569,7 +575,7 @@ public List performPartialLookup(String user, int time, int radius, Lo /** * Performs a database purge operation. - * + * * @param time * Time in seconds for the purge operation */ @@ -580,7 +586,7 @@ public void performPurge(int time) { /** * Performs a restore operation with various filters. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -609,7 +615,7 @@ public List performRestore(int time, List restrictUsers, List< /** * Performs a restore operation with basic filters. - * + * * @param user * The user to include in the restore * @param time @@ -636,7 +642,7 @@ public List performRestore(String user, int time, int radius, Location /** * Performs a rollback operation with various filters. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -665,7 +671,7 @@ public List performRollback(int time, List restrictUsers, List /** * Performs a rollback operation with basic filters. - * + * * @param user * The user to include in the rollback * @param time @@ -692,7 +698,7 @@ public List performRollback(String user, int time, int radius, Locatio /** * Processes a data request with various filters. - * + * * @param time * Time constraint in seconds * @param radius @@ -818,6 +824,34 @@ else if (argBlock instanceof EntityType && !addedEntity) { } else { if (!Bukkit.isPrimaryThread()) { + if (Config.getGlobal().MYSQL == false) { + List lookupData = Lookup.performLookup(statement, null, uuids, restrictUsers, restrictBlocks, excludeBlocks, excludeUsers, actionList, location, argRadius, startTime, endTime, restrictWorld, true); + + if (lookupData != null && !lookupData.isEmpty()) { + Set chunksToLoad = new HashSet<>(); + for (String[] data : lookupData) { + try { + ParseResult parseResult = new ParseResult(data); + World world = Bukkit.getWorld(parseResult.worldName()); + if (world != null) { + int chunkX = parseResult.getX() >> 4; + int chunkZ = parseResult.getZ() >> 4; + if (!world.isChunkLoaded(chunkX, chunkZ)) { + chunksToLoad.add(world.getChunkAt(chunkX, chunkZ)); + } + } + } + catch (Exception e) { + // ignore + } + } + + if (!chunksToLoad.isEmpty()) { + loadChunksInBatches(chunksToLoad, 10); + } + } + } + boolean verbose = false; result = Rollback.performRollbackRestore(statement, null, uuids, restrictUsers, null, restrictBlocks, excludeBlocks, excludeUsers, actionList, location, argRadius, startTime, endTime, restrictWorld, false, verbose, action, 0); } @@ -838,7 +872,7 @@ else if (argBlock instanceof EntityType && !addedEntity) { /** * Processes a data request with basic filters. - * + * * @param user * The user to include in the operation * @param time @@ -883,7 +917,7 @@ public void testAPI() { /** * Helper method to check if the API is enabled and the player is not null. - * + * * @param player * The player to check * @return True if the API is enabled and the player is not null @@ -894,7 +928,7 @@ private boolean isEnabledForPlayer(Player player) { /** * Helper method to check if a user and location are valid. - * + * * @param user * The username to check * @param location @@ -904,4 +938,37 @@ private boolean isEnabledForPlayer(Player player) { private boolean isValidUserAndLocation(String user, Location location) { return user != null && location != null && !user.isEmpty(); } + + private void loadChunksInBatches(Set chunks, int batchSize) { + List chunkList = new ArrayList<>(chunks); + for (int i = 0; i < chunkList.size(); i += batchSize) { + int end = Math.min(i + batchSize, chunkList.size()); + List batch = chunkList.subList(i, end); + + loadChunkBatch(batch); + + if (i + batchSize < chunkList.size()) { + try { + Thread.sleep(50); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + } + } + + private void loadChunkBatch(List chunkBatch) { + if (chunkBatch.isEmpty()) { + return; + } + + List> futures = new ArrayList<>(); + for (Chunk chunk : chunkBatch) { + futures.add(chunk.getWorld().getChunkAtAsync(chunk.getX(), chunk.getZ())); + } + + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + } } diff --git a/src/main/java/net/coreprotect/database/rollback/Rollback.java b/src/main/java/net/coreprotect/database/rollback/Rollback.java index 8626bf6d8..4d7d28c1a 100644 --- a/src/main/java/net/coreprotect/database/rollback/Rollback.java +++ b/src/main/java/net/coreprotect/database/rollback/Rollback.java @@ -10,17 +10,12 @@ import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; -import org.bukkit.command.BlockCommandSender; import org.bukkit.command.CommandSender; -import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import net.coreprotect.CoreProtect; @@ -218,17 +213,25 @@ else if (table == 1) { // container ConfigHandler.rollbackHash.put(userString, new int[] { 0, 0, 0, 0, 0 }); final String finalUserString = userString; - List> futures = new ArrayList<>(); - for (Entry entry : DatabaseUtils.entriesSortedByValues(chunkList)) { chunkCount++; + + int itemCount = 0; + int blockCount = 0; + int entityCount = 0; + int scannedWorldData = 0; + int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); + itemCount = rollbackHashData[0]; + blockCount = rollbackHashData[1]; + entityCount = rollbackHashData[2]; + scannedWorldData = rollbackHashData[4]; + long chunkKey = entry.getKey(); final int finalChunkX = (int) chunkKey; final int finalChunkZ = (int) (chunkKey >> 32); final CommandSender finalUser = user; - final Integer finalChunkCount = chunkCount; - final Integer totalChunks = chunkList.size(); + HashMap worldMap = new HashMap<>(); for (int rollbackWorldId : worldList) { String rollbackWorld = WorldUtils.getWorldName(rollbackWorldId); if (rollbackWorld.length() == 0) { @@ -240,26 +243,73 @@ else if (table == 1) { // container continue; } - final ArrayList finalBlockList = dataList.get(rollbackWorldId).getOrDefault(chunkKey, new ArrayList<>()); - final ArrayList finalItemList = itemDataList.get(rollbackWorldId).getOrDefault(chunkKey, new ArrayList<>()); + worldMap.put(rollbackWorldId, bukkitRollbackWorld); + } + + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 0, scannedWorldData }); + for (Entry rollbackWorlds : worldMap.entrySet()) { + Integer rollbackWorldId = rollbackWorlds.getKey(); + World bukkitRollbackWorld = rollbackWorlds.getValue(); + Location chunkLocation = new Location(bukkitRollbackWorld, (finalChunkX << 4), 0, (finalChunkZ << 4)); + final HashMap> finalBlockList = dataList.get(rollbackWorldId); + final HashMap> finalItemList = itemDataList.get(rollbackWorldId); + + Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), () -> { + // Process this chunk using our new RollbackProcessor class + ArrayList blockData = finalBlockList != null ? finalBlockList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>(); + ArrayList itemData = finalItemList != null ? finalItemList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>(); + RollbackProcessor.processChunk(finalChunkX, finalChunkZ, chunkKey, blockData, itemData, rollbackType, preview, finalUserString, finalUser instanceof Player ? (Player) finalUser : null, bukkitRollbackWorld, inventoryRollback); + }, chunkLocation, 0); + } + + rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); + int next = rollbackHashData[3]; + int scannedWorlds = rollbackHashData[4]; + int sleepTime = 0; + int abort = 0; + + while (next == 0 || scannedWorlds < worldMap.size()) { + if (preview == 1) { + // Not actually changing blocks, so less intensive. + sleepTime = sleepTime + 1; + Thread.sleep(1); + } + else { + sleepTime = sleepTime + 5; + Thread.sleep(5); + } - CompletableFuture future = RollbackProcessor.processChunk(finalChunkX, finalChunkZ, chunkKey, finalBlockList, finalItemList, rollbackType, preview, finalUserString, finalUser instanceof Player ? (Player) finalUser : null, bukkitRollbackWorld, inventoryRollback); - futures.add(future); + rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); + next = rollbackHashData[3]; + scannedWorlds = rollbackHashData[4]; - if (verbose && user != null && preview == 0 && !actionList.contains(11)) { - future.thenRun(() -> Chat.sendMessage(user, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.ROLLBACK_CHUNKS_MODIFIED, finalChunkCount.toString(), totalChunks.toString(), (totalChunks == 1 ? Selector.FIRST : Selector.SECOND)))); + if (sleepTime > 300000) { + abort = 1; + break; } } - } - // wait for all chunk processing tasks to complete - CompletableFuture allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); - try { - allFutures.get(30, TimeUnit.SECONDS); - } - catch (TimeoutException e) { + if (abort == 1 || next == 2) { + Chat.console(Phrase.build(Phrase.ROLLBACK_ABORTED)); + break; + } + + rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); + itemCount = rollbackHashData[0]; + blockCount = rollbackHashData[1]; + entityCount = rollbackHashData[2]; + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 0, 0 }); + + if (verbose && user != null && preview == 0 && !actionList.contains(11)) { + Integer chunks = chunkList.size(); + Chat.sendMessage(user, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.ROLLBACK_CHUNKS_MODIFIED, chunkCount.toString(), chunks.toString(), (chunks == 1 ? Selector.FIRST : Selector.SECOND))); + } } + chunkList.clear(); + dataList.clear(); + itemDataList.clear(); + int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); int itemCount = rollbackHashData[0]; int blockCount = rollbackHashData[1]; @@ -268,7 +318,7 @@ else if (table == 1) { // container double totalSeconds = (timeFinish - timeStart) / 1000.0; if (user != null) { - RollbackComplete.output(user, location, checkUsers, restrictList, excludeList, excludeUserList, actionList, timeString, chunkList.size(), totalSeconds, itemCount, blockCount, entityCount, rollbackType, radius, verbose, restrictWorld, preview); + RollbackComplete.output(user, location, checkUsers, restrictList, excludeList, excludeUserList, actionList, timeString, chunkCount, totalSeconds, itemCount, blockCount, entityCount, rollbackType, radius, verbose, restrictWorld, preview); } list = LookupConverter.convertRawLookup(statement, lookupList); diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java index beb811f58..acd55e067 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java @@ -39,14 +39,12 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; import net.coreprotect.config.ConfigHandler; import net.coreprotect.consumer.Queue; import net.coreprotect.model.BlockGroup; import net.coreprotect.paper.PaperAdapter; import net.coreprotect.thread.CacheHandler; -import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.BlockUtils; import net.coreprotect.utility.ChestTool; import net.coreprotect.utility.EntityUtils; @@ -146,12 +144,7 @@ else if ((rowType == Material.AIR) && ((oldTypeMaterial == Material.END_CRYSTAL) for (Entity entity : block.getChunk().getEntities()) { if (entity instanceof EnderCrystal) { if (entity.getLocation().getBlockX() == rowX && entity.getLocation().getBlockY() == rowY && entity.getLocation().getBlockZ() == rowZ) { - if (ConfigHandler.isFolia) { - Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); - } - else { - entity.remove(); - } + entity.remove(); } } } @@ -179,12 +172,7 @@ else if (BlockGroup.CONTAINERS.contains(Material.ARMOR_STAND)) { entityLocation.setY(entityLocation.getY() - 1.99); PaperAdapter.ADAPTER.teleportAsync(entity, entityLocation); - if (ConfigHandler.isFolia) { - Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); - } - else { - entity.remove(); - } + entity.remove(); } } } diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java index cbbb486b1..2feeb2b95 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java @@ -22,39 +22,6 @@ public class RollbackEntityHandler { - /** - * Processes an entity-related rollback operation. - * - * @param row - * The database row containing entity data (used only for specific operations) - * @param rollbackType - * The type of rollback (0 for rollback, 1 for restore) - * @param finalUserString - * The user string for tracking operations - * @param oldTypeRaw - * The old raw type value - * @param rowTypeRaw - * The raw type value - * @param rowData - * The data value - * @param rowAction - * The action value - * @param rowRolledBack - * Whether the entity was already rolled back - * @param rowX - * The X coordinate - * @param rowY - * The Y coordinate - * @param rowZ - * The Z coordinate - * @param rowWorldId - * The world ID - * @param rowUserId - * The user ID - * @param rowUser - * The username associated with this entity change - * @return The number of entities affected (1 if successful, 0 otherwise) - */ public static int processEntity(Object[] row, int rollbackType, String finalUserString, int oldTypeRaw, int rowTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, int rowUserId, String rowUser) { World bukkitWorld = Bukkit.getServer().getWorld(WorldUtils.getWorldName(rowWorldId)); if (bukkitWorld == null) { @@ -62,7 +29,6 @@ public static int processEntity(Object[] row, int rollbackType, String finalUser } if (ConfigHandler.isFolia) { - // Folia - load chunk async before processing CompletableFuture future = bukkitWorld.getChunkAtAsync(rowX >> 4, rowZ >> 4, true).thenApply(chunk -> { return processEntityLogic(row, rollbackType, finalUserString, oldTypeRaw, rowTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUserId, rowUser, bukkitWorld); }); @@ -84,21 +50,17 @@ public static int processEntity(Object[] row, int rollbackType, String finalUser private static int processEntityLogic(Object[] row, int rollbackType, String finalUserString, int oldTypeRaw, int rowTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, int rowUserId, String rowUser, World bukkitWorld) { try { - // Entity kill if (rowAction == 3) { Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); if (rowTypeRaw > 0) { - // Spawn in entity if (rowRolledBack == 0) { EntityType entityType = EntityUtils.getEntityType(rowTypeRaw); - // Use the spawnEntity method from the RollbackUtil class instead of Queue spawnEntity(rowUser, block.getState(), entityType, rowData); updateEntityCount(finalUserString, 1); return 1; } } else if (rowTypeRaw <= 0) { - // Attempt to remove entity if (rowRolledBack == 1) { boolean removed = false; int entityId = -1; @@ -185,25 +147,10 @@ else if (rowTypeRaw <= 0) { return 0; } - /** - * Gets the world name from a world ID. - * - * @param worldId - * The world ID - * @return The world name - */ private static String getWorldName(int worldId) { return WorldUtils.getWorldName(worldId); } - /** - * Updates the entity count in the rollback hash for a specific user. - * - * @param userString - * The user string identifier - * @param increment - * The amount to increment the entity count by - */ public static void updateEntityCount(String userString, int increment) { int[] rollbackHashData = ConfigHandler.rollbackHash.get(userString); if (rollbackHashData != null) { @@ -219,20 +166,7 @@ public static void updateEntityCount(String userString, int increment) { } } - /** - * Spawns an entity at the given block location. - * - * @param user - * The username of the player - * @param block - * The block state where the entity should be spawned - * @param type - * The type of entity to spawn - * @param data - * Additional data for the entity - */ public static void spawnEntity(String user, BlockState block, EntityType type, int data) { - // Create a new helper method that will delegate to Queue RollbackUtil.queueEntitySpawn(user, block, type, data); } } diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java index fa3b42e97..7c4d78461 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java @@ -8,7 +8,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -26,11 +25,14 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; +import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; import net.coreprotect.config.Config; import net.coreprotect.config.ConfigHandler; +import net.coreprotect.consumer.Queue; import net.coreprotect.database.logger.ItemLogger; import net.coreprotect.model.BlockGroup; +import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.BlockUtils; import net.coreprotect.utility.ItemUtils; import net.coreprotect.utility.MaterialUtils; @@ -39,66 +41,19 @@ public class RollbackProcessor { - /** - * Process data for a specific chunk - * - * @param finalChunkX - * The chunk X coordinate - * @param finalChunkZ - * The chunk Z coordinate - * @param chunkKey - * The chunk lookup key - * @param blockList - * The list of block data to process - * @param itemList - * The list of item data to process - * @param rollbackType - * The rollback type (0=rollback, 1=restore) - * @param preview - * Whether this is a preview (0=no, 1=yes-non-destructive, 2=yes-destructive) - * @param finalUserString - * The username performing the rollback - * @param finalUser - * The user performing the rollback - * @param bukkitRollbackWorld - * The world to process - * @return A CompletableFuture that completes with true if successful, false otherwise - */ - public static CompletableFuture processChunk(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { - if (ConfigHandler.isFolia) { - // Folia - load chunk async before processing - return bukkitRollbackWorld.getChunkAtAsync(finalChunkX, finalChunkZ, true).thenApply(chunk -> { - return processChunkLogic(finalChunkX, finalChunkZ, chunkKey, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); - }); - } - else { - try { - if (!bukkitRollbackWorld.isChunkLoaded(finalChunkX, finalChunkZ)) { - bukkitRollbackWorld.getChunkAt(finalChunkX, finalChunkZ); - } - boolean result = processChunkLogic(finalChunkX, finalChunkZ, chunkKey, blockList, itemList, rollbackType, preview, finalUserString, finalUser, bukkitRollbackWorld, inventoryRollback); - return CompletableFuture.completedFuture(result); - } - catch (Exception e) { - e.printStackTrace(); - return CompletableFuture.completedFuture(false); - } - } - } - - private static boolean processChunkLogic(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { + public static boolean processChunk(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { try { boolean clearInventories = Config.getGlobal().ROLLBACK_ITEMS; ArrayList data = blockList != null ? blockList : new ArrayList<>(); ArrayList itemData = itemList != null ? itemList : new ArrayList<>(); Map chunkChanges = new LinkedHashMap<>(); - // Process blocks for (Object[] row : data) { int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); int itemCount = rollbackHashData[0]; int blockCount = rollbackHashData[1]; int entityCount = rollbackHashData[2]; + int scannedWorlds = rollbackHashData[4]; int rowX = (Integer) row[3]; int rowY = (Integer) row[4]; @@ -204,6 +159,10 @@ else if (rowAction == 3) { // entity kill } Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); + if (!bukkitWorld.isChunkLoaded(block.getChunk())) { + bukkitWorld.getChunkAt(block.getLocation()); + } + boolean changeBlock = true; boolean countBlock = true; Material changeType = block.getType(); @@ -223,7 +182,6 @@ else if (rowAction == 3) { // entity kill } if ((rowType == pendingChangeType) && ((!BukkitAdapter.ADAPTER.isItemFrame(oldTypeMaterial)) && (oldTypeMaterial != Material.PAINTING) && (oldTypeMaterial != Material.ARMOR_STAND)) && (oldTypeMaterial != Material.END_CRYSTAL)) { - // block is already changed! BlockData checkData = rowType == Material.AIR ? blockData : rawBlockData; if (checkData != null) { if (checkData.getAsString().equals(pendingChangeData.getAsString()) || checkData instanceof org.bukkit.block.data.MultipleFacing || checkData instanceof org.bukkit.block.data.type.Stairs || checkData instanceof org.bukkit.block.data.type.RedstoneWire) { @@ -256,14 +214,12 @@ else if ((pendingChangeType != Material.AIR) && (pendingChangeType != Material.C } } - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 0, 0 }); + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 0, scannedWorlds }); } data.clear(); - // Apply cached block changes - RollbackBlockHandler.applyBlockChanges(chunkChanges, preview, finalUser); + RollbackBlockHandler.applyBlockChanges(chunkChanges, preview, finalUser instanceof Player ? (Player) finalUser : null); - // Process container items Map> sortPlayers = new HashMap<>(); Object container = null; Material containerType = null; @@ -279,7 +235,7 @@ else if ((pendingChangeType != Material.AIR) && (pendingChangeType != Material.C int itemCount1 = rollbackHashData1[0]; int blockCount1 = rollbackHashData1[1]; int entityCount1 = rollbackHashData1[2]; - + int scannedWorlds = rollbackHashData1[4]; int rowX = (Integer) row[3]; int rowY = (Integer) row[4]; int rowZ = (Integer) row[5]; @@ -339,12 +295,12 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT } itemCount1 = itemCount1 + rowAmount; - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, 0 }); - continue; // remove this for merged rollbacks in future? (be sure to re-enable chunk sorting) + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, scannedWorlds }); + continue; } if (inventoryRollback || rowAction > 1) { - continue; // skip inventory & ender chest transactions + continue; } if ((rollbackType == 0 && rowRolledBack == 0) || (rollbackType == 1 && rowRolledBack == 1)) { @@ -353,7 +309,7 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT String faceData = (String) populatedStack[1]; if (!containerInit || rowX != lastX || rowY != lastY || rowZ != lastZ || rowWorldId != lastWorldId || !faceData.equals(lastFace)) { - container = null; // container patch 2.14.0 + container = null; String world = WorldUtils.getWorldName(rowWorldId); if (world.length() == 0) { continue; @@ -364,6 +320,9 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT continue; } Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); + if (!bukkitWorld.isChunkLoaded(block.getChunk())) { + bukkitWorld.getChunkAt(block.getLocation()); + } if (BlockGroup.CONTAINERS.contains(block.getType())) { BlockState blockState = block.getState(); @@ -421,7 +380,7 @@ else if (entity instanceof ItemFrame) { } } - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, 0 }); + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, scannedWorlds }); } itemData.clear(); @@ -434,9 +393,9 @@ else if (entity instanceof ItemFrame) { int itemCount = rollbackHashData[0]; int blockCount = rollbackHashData[1]; int entityCount = rollbackHashData[2]; - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 1, 1 }); + int scannedWorlds = rollbackHashData[4]; + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 1, (scannedWorlds + 1) }); - // Teleport players out of danger if they're within this chunk if (preview == 0) { for (Player player : Bukkit.getOnlinePlayers()) { Location playerLocation = player.getLocation(); @@ -445,7 +404,9 @@ else if (entity instanceof ItemFrame) { int chunkZ = playerLocation.getBlockZ() >> 4; if (bukkitRollbackWorld.getName().equals(playerWorld) && chunkX == finalChunkX && chunkZ == finalChunkZ) { - Teleport.performSafeTeleport(player, playerLocation, false); + Scheduler.runTask(CoreProtect.getInstance(), () -> { + Teleport.performSafeTeleport(player, playerLocation, false); + }, player); } } } @@ -455,12 +416,12 @@ else if (entity instanceof ItemFrame) { catch (Exception e) { e.printStackTrace(); int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); - if (rollbackHashData != null) { - int itemCount = rollbackHashData[0]; - int blockCount = rollbackHashData[1]; - int entityCount = rollbackHashData[2]; - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 2, 1 }); - } + int itemCount = rollbackHashData[0]; + int blockCount = rollbackHashData[1]; + int entityCount = rollbackHashData[2]; + int scannedWorlds = rollbackHashData[4]; + + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 2, (scannedWorlds + 1) }); return false; } } diff --git a/src/main/java/net/coreprotect/listener/block/BlockDispenseListener.java b/src/main/java/net/coreprotect/listener/block/BlockDispenseListener.java index 175198d64..cbd761ec6 100644 --- a/src/main/java/net/coreprotect/listener/block/BlockDispenseListener.java +++ b/src/main/java/net/coreprotect/listener/block/BlockDispenseListener.java @@ -83,9 +83,7 @@ else if (material.equals(Material.FLINT_AND_STEEL)) { } if (!dispenseSuccess && material == Material.BONE_MEAL) { - Location location = newBlock.getLocation(); - String key = location.getWorld().getName() + ":" + location.getBlockX() + ":" + location.getBlockY() + ":" + location.getBlockZ(); - CacheHandler.redstoneCache.put(key, new Object[] { System.currentTimeMillis(), user }); + CacheHandler.redstoneCache.put(newBlock.getLocation(), new Object[] { System.currentTimeMillis(), user }); } if (type == Material.FIRE && (!Config.getConfig(world).BLOCK_IGNITE || !(newBlockData instanceof Lightable))) { diff --git a/src/main/java/net/coreprotect/listener/block/BlockFertilizeListener.java b/src/main/java/net/coreprotect/listener/block/BlockFertilizeListener.java index 3cd0950a6..325c608b6 100644 --- a/src/main/java/net/coreprotect/listener/block/BlockFertilizeListener.java +++ b/src/main/java/net/coreprotect/listener/block/BlockFertilizeListener.java @@ -50,8 +50,7 @@ protected void onBlockFertilize(BlockFertilizeEvent event) { user = player.getName(); } else { - String key = location.getWorld().getName() + ":" + location.getBlockX() + ":" + location.getBlockY() + ":" + location.getBlockZ(); - Object[] data = CacheHandler.redstoneCache.get(key); + Object[] data = CacheHandler.redstoneCache.get(location); if (data != null) { long newTime = System.currentTimeMillis(); long oldTime = (long) data[0]; @@ -59,7 +58,7 @@ protected void onBlockFertilize(BlockFertilizeEvent event) { user = (String) data[1]; } - CacheHandler.redstoneCache.remove(key); + CacheHandler.redstoneCache.remove(location); } } diff --git a/src/main/java/net/coreprotect/listener/block/BlockUtil.java b/src/main/java/net/coreprotect/listener/block/BlockUtil.java index 817fa47f4..53e7ce062 100644 --- a/src/main/java/net/coreprotect/listener/block/BlockUtil.java +++ b/src/main/java/net/coreprotect/listener/block/BlockUtil.java @@ -52,8 +52,7 @@ public static Block gravityScan(Location location, Material type, String player) int yc = y - 1; // user placing sand/gravel. Find the bottom block int bottomfound = 0; - int i = 0; - while (bottomfound == 0 && i < 256) { + while (bottomfound == 0) { if (yc < BukkitAdapter.ADAPTER.getMinHeight(world)) { block = world.getBlockAt(x, yc + 1, z); bottomfound = 1; @@ -82,7 +81,7 @@ else if (down == Material.WATER && type.name().endsWith("_CONCRETE_POWDER")) { } yc--; } - i++; + } CacheHandler.lookupCache.put("" + x + "." + block.getY() + "." + z + "." + wid + "", new Object[] { timestamp, player, type }); } diff --git a/src/main/java/net/coreprotect/thread/CacheHandler.java b/src/main/java/net/coreprotect/thread/CacheHandler.java index 7c1f7a9eb..70bb55ee7 100755 --- a/src/main/java/net/coreprotect/thread/CacheHandler.java +++ b/src/main/java/net/coreprotect/thread/CacheHandler.java @@ -19,7 +19,7 @@ public class CacheHandler implements Runnable { public static Map entityCache = Collections.synchronizedMap(new HashMap<>()); public static ConcurrentHashMap pistonCache = new ConcurrentHashMap<>(16, 0.75f, 2); public static ConcurrentHashMap spreadCache = new ConcurrentHashMap<>(16, 0.75f, 2); - public static ConcurrentHashMap redstoneCache = new ConcurrentHashMap<>(16, 0.75f, 2); + public static ConcurrentHashMap redstoneCache = new ConcurrentHashMap<>(16, 0.75f, 2); @SuppressWarnings({ "unchecked", "rawtypes" }) @Override diff --git a/src/main/java/net/coreprotect/thread/Scheduler.java b/src/main/java/net/coreprotect/thread/Scheduler.java index ef38fb597..eb16a9a0b 100644 --- a/src/main/java/net/coreprotect/thread/Scheduler.java +++ b/src/main/java/net/coreprotect/thread/Scheduler.java @@ -31,10 +31,10 @@ public static void scheduleSyncDelayedTask(CoreProtect plugin, Runnable task, Ob else if (regionData instanceof Entity) { Entity entity = (Entity) regionData; if (delay == 0) { - entity.getScheduler().run(plugin, value -> task.run(), null); + entity.getScheduler().run(plugin, value -> task.run(), task); } else { - entity.getScheduler().runDelayed(plugin, value -> task.run(), null, delay); + entity.getScheduler().runDelayed(plugin, value -> task.run(), task, delay); } } else { @@ -64,7 +64,7 @@ public static Object scheduleSyncRepeatingTask(CoreProtect plugin, Runnable task } else if (regionData instanceof Entity) { Entity entity = (Entity) regionData; - return entity.getScheduler().runAtFixedRate(plugin, value -> task.run(), null, delay, period); + return entity.getScheduler().runAtFixedRate(plugin, value -> task.run(), task, delay, period); } else { return Bukkit.getServer().getGlobalRegionScheduler().runAtFixedRate(plugin, value -> task.run(), delay, period); diff --git a/src/main/java/net/coreprotect/utility/entity/HangingUtil.java b/src/main/java/net/coreprotect/utility/entity/HangingUtil.java index 07483ee8f..4ae634859 100644 --- a/src/main/java/net/coreprotect/utility/entity/HangingUtil.java +++ b/src/main/java/net/coreprotect/utility/entity/HangingUtil.java @@ -14,11 +14,8 @@ import org.bukkit.entity.Painting; import org.bukkit.inventory.ItemStack; -import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; -import net.coreprotect.config.ConfigHandler; import net.coreprotect.model.BlockGroup; -import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.BlockUtils; import net.coreprotect.utility.MaterialUtils; @@ -51,12 +48,7 @@ public static void spawnHanging(final BlockState blockstate, final Material rowT Location el = e.getLocation(); if (el.getBlockX() == x && el.getBlockY() == y && el.getBlockZ() == z) { if (hangingFace == null || ((Hanging) e).getFacing() == hangingFace) { - if (ConfigHandler.isFolia) { - Scheduler.runTask(CoreProtect.getInstance(), e::remove, e); - } - else { - e.remove(); - } + e.remove(); break; } } @@ -193,12 +185,7 @@ public static void removeHanging(final BlockState block, final String hangingDat Location el = e.getLocation(); if (el.getBlockX() == block.getX() && el.getBlockY() == block.getY() && el.getBlockZ() == block.getZ()) { if (hangingFace == null || ((Hanging) e).getFacing() == hangingFace) { - if (ConfigHandler.isFolia) { - Scheduler.runTask(CoreProtect.getInstance(), e::remove, e); - } - else { - e.remove(); - } + e.remove(); } } } From 869c943e8d25b5d8b638c31c8159ad37a67136c3 Mon Sep 17 00:00:00 2001 From: leir4iks Date: Thu, 6 Nov 2025 12:37:27 +0200 Subject: [PATCH 12/22] 123 --- .../coreprotect/command/TeleportCommand.java | 2 + .../rollback/RollbackEntityHandler.java | 62 +++++++++++++++++++ .../database/rollback/RollbackProcessor.java | 35 ++++++++++- .../coreprotect/listener/block/BlockUtil.java | 1 - 4 files changed, 96 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/coreprotect/command/TeleportCommand.java b/src/main/java/net/coreprotect/command/TeleportCommand.java index bf162a00e..275ffd575 100644 --- a/src/main/java/net/coreprotect/command/TeleportCommand.java +++ b/src/main/java/net/coreprotect/command/TeleportCommand.java @@ -117,6 +117,8 @@ else if (y == null) { if (!location.getWorld().isChunkLoaded(chunkX, chunkZ)) { location.getWorld().getChunkAt(location); } + + // Teleport the player to a safe location Teleport.performSafeTeleport(((Player) player), location, true); }, location); } diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java index 2feeb2b95..72c5cda8b 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java @@ -22,6 +22,37 @@ public class RollbackEntityHandler { + /** + * Processes an entity-related rollback operation. + * + * @param row + * The database row containing entity data (used only for specific operations) + * @param rollbackType + * The type of rollback (0 for rollback, 1 for restore) + * @param finalUserString + * The user string for tracking operations + * @param rowTypeRaw + * The raw type value + * @param rowData + * The data value + * @param rowAction + * The action value + * @param rowRolledBack + * Whether the entity was already rolled back + * @param rowX + * The X coordinate + * @param rowY + * The Y coordinate + * @param rowZ + * The Z coordinate + * @param rowWorldId + * The world ID + * @param rowUserId + * The user ID + * @param rowUser + * The username associated with this entity change + * @return The number of entities affected (1 if successful, 0 otherwise) + */ public static int processEntity(Object[] row, int rollbackType, String finalUserString, int oldTypeRaw, int rowTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, int rowUserId, String rowUser) { World bukkitWorld = Bukkit.getServer().getWorld(WorldUtils.getWorldName(rowWorldId)); if (bukkitWorld == null) { @@ -53,14 +84,17 @@ private static int processEntityLogic(Object[] row, int rollbackType, String fin if (rowAction == 3) { Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); if (rowTypeRaw > 0) { + // Spawn in entity if (rowRolledBack == 0) { EntityType entityType = EntityUtils.getEntityType(rowTypeRaw); + // Use the spawnEntity method from the RollbackUtil class instead of Queue spawnEntity(rowUser, block.getState(), entityType, rowData); updateEntityCount(finalUserString, 1); return 1; } } else if (rowTypeRaw <= 0) { + // Attempt to remove entity if (rowRolledBack == 1) { boolean removed = false; int entityId = -1; @@ -147,10 +181,25 @@ else if (rowTypeRaw <= 0) { return 0; } + /** + * Gets the world name from a world ID. + * + * @param worldId + * The world ID + * @return The world name + */ private static String getWorldName(int worldId) { return WorldUtils.getWorldName(worldId); } + /** + * Updates the entity count in the rollback hash for a specific user. + * + * @param userString + * The user string identifier + * @param increment + * The amount to increment the entity count by + */ public static void updateEntityCount(String userString, int increment) { int[] rollbackHashData = ConfigHandler.rollbackHash.get(userString); if (rollbackHashData != null) { @@ -166,7 +215,20 @@ public static void updateEntityCount(String userString, int increment) { } } + /** + * Spawns an entity at the given block location. + * + * @param user + * The username of the player + * @param block + * The block state where the entity should be spawned + * @param type + * The type of entity to spawn + * @param data + * Additional data for the entity + */ public static void spawnEntity(String user, BlockState block, EntityType type, int data) { + // Create a new helper method that will delegate to Queue RollbackUtil.queueEntitySpawn(user, block, type, data); } } diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java index 7c4d78461..2a1850f33 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java @@ -41,6 +41,31 @@ public class RollbackProcessor { + /** + * Process data for a specific chunk + * + * @param finalChunkX + * The chunk X coordinate + * @param finalChunkZ + * The chunk Z coordinate + * @param chunkKey + * The chunk lookup key + * @param blockList + * The list of block data to process + * @param itemList + * The list of item data to process + * @param rollbackType + * The rollback type (0=rollback, 1=restore) + * @param preview + * Whether this is a preview (0=no, 1=yes-non-destructive, 2=yes-destructive) + * @param finalUserString + * The username performing the rollback + * @param finalUser + * The user performing the rollback + * @param bukkitRollbackWorld + * The world to process + * @return True if successful, false if there was an error + */ public static boolean processChunk(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList blockList, ArrayList itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) { try { boolean clearInventories = Config.getGlobal().ROLLBACK_ITEMS; @@ -48,6 +73,7 @@ public static boolean processChunk(int finalChunkX, int finalChunkZ, long chunkK ArrayList itemData = itemList != null ? itemList : new ArrayList<>(); Map chunkChanges = new LinkedHashMap<>(); + // Process blocks for (Object[] row : data) { int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); int itemCount = rollbackHashData[0]; @@ -182,6 +208,7 @@ else if (rowAction == 3) { // entity kill } if ((rowType == pendingChangeType) && ((!BukkitAdapter.ADAPTER.isItemFrame(oldTypeMaterial)) && (oldTypeMaterial != Material.PAINTING) && (oldTypeMaterial != Material.ARMOR_STAND)) && (oldTypeMaterial != Material.END_CRYSTAL)) { + // block is already changed! BlockData checkData = rowType == Material.AIR ? blockData : rawBlockData; if (checkData != null) { if (checkData.getAsString().equals(pendingChangeData.getAsString()) || checkData instanceof org.bukkit.block.data.MultipleFacing || checkData instanceof org.bukkit.block.data.type.Stairs || checkData instanceof org.bukkit.block.data.type.RedstoneWire) { @@ -218,8 +245,10 @@ else if ((pendingChangeType != Material.AIR) && (pendingChangeType != Material.C } data.clear(); + // Apply cached block changes RollbackBlockHandler.applyBlockChanges(chunkChanges, preview, finalUser instanceof Player ? (Player) finalUser : null); + // Process container items Map> sortPlayers = new HashMap<>(); Object container = null; Material containerType = null; @@ -296,11 +325,11 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT itemCount1 = itemCount1 + rowAmount; ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, scannedWorlds }); - continue; + continue; // remove this for merged rollbacks in future? (be sure to re-enable chunk sorting) } if (inventoryRollback || rowAction > 1) { - continue; + continue; // skip inventory & ender chest transactions } if ((rollbackType == 0 && rowRolledBack == 0) || (rollbackType == 1 && rowRolledBack == 1)) { @@ -309,7 +338,7 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT String faceData = (String) populatedStack[1]; if (!containerInit || rowX != lastX || rowY != lastY || rowZ != lastZ || rowWorldId != lastWorldId || !faceData.equals(lastFace)) { - container = null; + container = null; // container patch 2.14.0 String world = WorldUtils.getWorldName(rowWorldId); if (world.length() == 0) { continue; diff --git a/src/main/java/net/coreprotect/listener/block/BlockUtil.java b/src/main/java/net/coreprotect/listener/block/BlockUtil.java index 53e7ce062..09c0145cf 100644 --- a/src/main/java/net/coreprotect/listener/block/BlockUtil.java +++ b/src/main/java/net/coreprotect/listener/block/BlockUtil.java @@ -81,7 +81,6 @@ else if (down == Material.WATER && type.name().endsWith("_CONCRETE_POWDER")) { } yc--; } - } CacheHandler.lookupCache.put("" + x + "." + block.getY() + "." + z + "." + wid + "", new Object[] { timestamp, player, type }); } From a2b59e3520caeea31e3efdb2223bd9ec65b8d040 Mon Sep 17 00:00:00 2001 From: leir4iks <118904014+leir4iks@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:00:25 +0200 Subject: [PATCH 13/22] 456 sounds like... my IDE is bad --- .../java/net/coreprotect/CoreProtectAPI.java | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/main/java/net/coreprotect/CoreProtectAPI.java b/src/main/java/net/coreprotect/CoreProtectAPI.java index 2174370e6..47771f6c3 100755 --- a/src/main/java/net/coreprotect/CoreProtectAPI.java +++ b/src/main/java/net/coreprotect/CoreProtectAPI.java @@ -53,7 +53,7 @@ public static class ParseResult extends net.coreprotect.api.result.ParseResult { /** * Creates a new ParseResult from string array data. - * + * * @param data * The string array data to parse */ @@ -64,7 +64,7 @@ public ParseResult(String[] data) { /** * Converts a list of objects to a map for internal processing - * + * * @param list * List of objects to convert * @return Map with objects as keys and Boolean false as values @@ -89,7 +89,7 @@ else if (value instanceof Integer) { /** * Returns the current API version. - * + * * @return The API version as an integer */ public int APIVersion() { @@ -98,7 +98,7 @@ public int APIVersion() { /** * Performs a block lookup at the specified block. - * + * * @param block * The block to look up * @param time @@ -114,7 +114,7 @@ public List blockLookup(Block block, int time) { /** * Performs a lookup on the queue data for the specified block. - * + * * @param block * The block to look up * @return List of results @@ -125,7 +125,7 @@ public List queueLookup(Block block) { /** * Performs a lookup on session data for the specified user. - * + * * @param user * The user to look up * @param time @@ -138,7 +138,7 @@ public List sessionLookup(String user, int time) { /** * Determines if a user has placed a block at the specified location. - * + * * @param user * The username to check * @param block @@ -170,7 +170,7 @@ public boolean hasPlaced(String user, Block block, int time, int offset) { /** * Determines if a user has removed a block at the specified location. - * + * * @param user * The username to check * @param block @@ -202,7 +202,7 @@ public boolean hasRemoved(String user, Block block, int time, int offset) { /** * Gets the current time in milliseconds. Protected to allow mocking in tests. - * + * * @return Current time in milliseconds */ protected long getCurrentTimeMillis() { @@ -220,7 +220,7 @@ public boolean isEnabled() { /** * Logs a chat message for a player. - * + * * @param player * The player who sent the message * @param message @@ -243,7 +243,7 @@ public boolean logChat(Player player, String message) { /** * Logs a command executed by a player. - * + * * @param player * The player who executed the command * @param command @@ -266,7 +266,7 @@ public boolean logCommand(Player player, String command) { /** * Logs an interaction by a user at a location. - * + * * @param user * The username * @param location @@ -284,7 +284,7 @@ public boolean logInteraction(String user, Location location) { /** * Logs a container transaction by a user at a location. - * + * * @param user * The username * @param location @@ -301,7 +301,7 @@ public boolean logContainerTransaction(String user, Location location) { /** * Logs a block placement by a user. - * + * * @param user * The username * @param blockState @@ -319,7 +319,7 @@ public boolean logPlacement(String user, BlockState blockState) { /** * Logs a block placement by a user with a specific material and block data. - * + * * @param user * The username * @param location @@ -349,7 +349,7 @@ public boolean logPlacement(String user, Location location, Material type, Block /** * Logs a block placement by a user with a specific material and data value. - * + * * @param user * The username * @param location @@ -373,7 +373,7 @@ public boolean logPlacement(String user, Location location, Material type, byte /** * Logs a block removal by a user. - * + * * @param user * The username * @param blockState @@ -391,7 +391,7 @@ public boolean logRemoval(String user, BlockState blockState) { /** * Logs a block removal by a user with a specific material and block data. - * + * * @param user * The username * @param location @@ -420,7 +420,7 @@ public boolean logRemoval(String user, Location location, Material type, BlockDa /** * Logs a block removal by a user with a specific material and data value. - * + * * @param user * The username * @param location @@ -444,7 +444,7 @@ public boolean logRemoval(String user, Location location, Material type, byte da /** * Parses lookup results into a ParseResult object. - * + * * @param results * The results to parse * @return A ParseResult object containing the parsed data @@ -455,7 +455,7 @@ public ParseResult parseResult(String[] results) { /** * Performs a lookup operation with various filters. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -484,7 +484,7 @@ public List performLookup(int time, List restrictUsers, List performLookup(String user, int time, int radius, Location /** * Performs a partial lookup operation with various filters and pagination support. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -544,7 +544,7 @@ public List performPartialLookup(int time, List restrictUsers, /** * Performs a partial lookup operation with basic filters and pagination support. - * + * * @param user * The user to include in the lookup * @param time @@ -575,7 +575,7 @@ public List performPartialLookup(String user, int time, int radius, Lo /** * Performs a database purge operation. - * + * * @param time * Time in seconds for the purge operation */ @@ -586,7 +586,7 @@ public void performPurge(int time) { /** * Performs a restore operation with various filters. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -615,7 +615,7 @@ public List performRestore(int time, List restrictUsers, List< /** * Performs a restore operation with basic filters. - * + * * @param user * The user to include in the restore * @param time @@ -642,7 +642,7 @@ public List performRestore(String user, int time, int radius, Location /** * Performs a rollback operation with various filters. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -671,7 +671,7 @@ public List performRollback(int time, List restrictUsers, List /** * Performs a rollback operation with basic filters. - * + * * @param user * The user to include in the rollback * @param time @@ -698,7 +698,7 @@ public List performRollback(String user, int time, int radius, Locatio /** * Processes a data request with various filters. - * + * * @param time * Time constraint in seconds * @param radius @@ -872,7 +872,7 @@ else if (argBlock instanceof EntityType && !addedEntity) { /** * Processes a data request with basic filters. - * + * * @param user * The user to include in the operation * @param time @@ -917,7 +917,7 @@ public void testAPI() { /** * Helper method to check if the API is enabled and the player is not null. - * + * * @param player * The player to check * @return True if the API is enabled and the player is not null @@ -928,7 +928,7 @@ private boolean isEnabledForPlayer(Player player) { /** * Helper method to check if a user and location are valid. - * + * * @param user * The username to check * @param location From 717d516d3ea06594fa79bd1874b758eebfcebb7b Mon Sep 17 00:00:00 2001 From: leir4iks <118904014+leir4iks@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:01:01 +0200 Subject: [PATCH 14/22] 456 --- src/main/java/net/coreprotect/CoreProtectAPI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/coreprotect/CoreProtectAPI.java b/src/main/java/net/coreprotect/CoreProtectAPI.java index 47771f6c3..c4ec65779 100755 --- a/src/main/java/net/coreprotect/CoreProtectAPI.java +++ b/src/main/java/net/coreprotect/CoreProtectAPI.java @@ -211,7 +211,7 @@ protected long getCurrentTimeMillis() { /** * Checks if the CoreProtect API is enabled. - * + * * @return True if the API is enabled */ public boolean isEnabled() { From e50afe96a29bac72f0b105d051ef26ecd9418693 Mon Sep 17 00:00:00 2001 From: leir4iks <118904014+leir4iks@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:02:56 +0200 Subject: [PATCH 15/22] 789 sounds like... my brain is already dead --- .../coreprotect/database/rollback/RollbackBlockHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java index acd55e067..871a0cab2 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackBlockHandler.java @@ -492,7 +492,7 @@ else if (rowType != Material.AIR && rawBlockData instanceof Bed) { /** * Update the block count in the rollback hash - * + * * @param userString * The username for this rollback * @param increment @@ -511,7 +511,7 @@ protected static void updateBlockCount(String userString, int increment) { /** * Apply all pending block changes to the world - * + * * @param chunkChanges * Map of blocks to change * @param preview From 29933c94e604e4b4e96ed62fe494c9cd5b957e30 Mon Sep 17 00:00:00 2001 From: leir4iks <118904014+leir4iks@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:04:04 +0200 Subject: [PATCH 16/22] 789 --- .../coreprotect/database/rollback/RollbackEntityHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java index 72c5cda8b..810f604a8 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java @@ -217,7 +217,7 @@ public static void updateEntityCount(String userString, int increment) { /** * Spawns an entity at the given block location. - * + * * @param user * The username of the player * @param block From 3593f5085dfede63555305d6021051cefd5601b5 Mon Sep 17 00:00:00 2001 From: leir4iks <118904014+leir4iks@users.noreply.github.com> Date: Thu, 6 Nov 2025 13:05:03 +0200 Subject: [PATCH 17/22] Update RollbackProcessor.java --- .../net/coreprotect/database/rollback/RollbackProcessor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java index 2a1850f33..2fb5aa486 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java @@ -43,7 +43,7 @@ public class RollbackProcessor { /** * Process data for a specific chunk - * + * * @param finalChunkX * The chunk X coordinate * @param finalChunkZ @@ -425,6 +425,7 @@ else if (entity instanceof ItemFrame) { int scannedWorlds = rollbackHashData[4]; ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 1, (scannedWorlds + 1) }); + // Teleport players out of danger if they're within this chunk if (preview == 0) { for (Player player : Bukkit.getOnlinePlayers()) { Location playerLocation = player.getLocation(); From c23280cadc790e5aa57416326b34b86ef5ba1d24 Mon Sep 17 00:00:00 2001 From: leir4iks Date: Sun, 9 Nov 2025 18:08:10 +0200 Subject: [PATCH 18/22] rewrite --- .../java/net/coreprotect/CoreProtectAPI.java | 135 +++-------- .../database/rollback/Rollback.java | 220 ++++++++++++------ .../rollback/RollbackEntityHandler.java | 71 ++---- .../database/rollback/RollbackProcessor.java | 27 +-- 4 files changed, 205 insertions(+), 248 deletions(-) diff --git a/src/main/java/net/coreprotect/CoreProtectAPI.java b/src/main/java/net/coreprotect/CoreProtectAPI.java index c4ec65779..ae27cf4a6 100755 --- a/src/main/java/net/coreprotect/CoreProtectAPI.java +++ b/src/main/java/net/coreprotect/CoreProtectAPI.java @@ -4,18 +4,13 @@ import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; import org.bukkit.Bukkit; -import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.Server; -import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; @@ -34,7 +29,6 @@ import net.coreprotect.listener.player.InventoryChangeListener; import net.coreprotect.utility.Chat; import net.coreprotect.utility.MaterialUtils; -import net.coreprotect.utility.WorldUtils; /** * The main API class for CoreProtect. @@ -53,7 +47,7 @@ public static class ParseResult extends net.coreprotect.api.result.ParseResult { /** * Creates a new ParseResult from string array data. - * + * * @param data * The string array data to parse */ @@ -64,7 +58,7 @@ public ParseResult(String[] data) { /** * Converts a list of objects to a map for internal processing - * + * * @param list * List of objects to convert * @return Map with objects as keys and Boolean false as values @@ -89,7 +83,7 @@ else if (value instanceof Integer) { /** * Returns the current API version. - * + * * @return The API version as an integer */ public int APIVersion() { @@ -98,7 +92,7 @@ public int APIVersion() { /** * Performs a block lookup at the specified block. - * + * * @param block * The block to look up * @param time @@ -114,7 +108,7 @@ public List blockLookup(Block block, int time) { /** * Performs a lookup on the queue data for the specified block. - * + * * @param block * The block to look up * @return List of results @@ -125,7 +119,7 @@ public List queueLookup(Block block) { /** * Performs a lookup on session data for the specified user. - * + * * @param user * The user to look up * @param time @@ -138,7 +132,7 @@ public List sessionLookup(String user, int time) { /** * Determines if a user has placed a block at the specified location. - * + * * @param user * The username to check * @param block @@ -170,7 +164,7 @@ public boolean hasPlaced(String user, Block block, int time, int offset) { /** * Determines if a user has removed a block at the specified location. - * + * * @param user * The username to check * @param block @@ -202,7 +196,7 @@ public boolean hasRemoved(String user, Block block, int time, int offset) { /** * Gets the current time in milliseconds. Protected to allow mocking in tests. - * + * * @return Current time in milliseconds */ protected long getCurrentTimeMillis() { @@ -211,7 +205,7 @@ protected long getCurrentTimeMillis() { /** * Checks if the CoreProtect API is enabled. - * + * * @return True if the API is enabled */ public boolean isEnabled() { @@ -220,7 +214,7 @@ public boolean isEnabled() { /** * Logs a chat message for a player. - * + * * @param player * The player who sent the message * @param message @@ -243,7 +237,7 @@ public boolean logChat(Player player, String message) { /** * Logs a command executed by a player. - * + * * @param player * The player who executed the command * @param command @@ -266,7 +260,7 @@ public boolean logCommand(Player player, String command) { /** * Logs an interaction by a user at a location. - * + * * @param user * The username * @param location @@ -284,7 +278,7 @@ public boolean logInteraction(String user, Location location) { /** * Logs a container transaction by a user at a location. - * + * * @param user * The username * @param location @@ -301,7 +295,7 @@ public boolean logContainerTransaction(String user, Location location) { /** * Logs a block placement by a user. - * + * * @param user * The username * @param blockState @@ -319,7 +313,7 @@ public boolean logPlacement(String user, BlockState blockState) { /** * Logs a block placement by a user with a specific material and block data. - * + * * @param user * The username * @param location @@ -349,7 +343,7 @@ public boolean logPlacement(String user, Location location, Material type, Block /** * Logs a block placement by a user with a specific material and data value. - * + * * @param user * The username * @param location @@ -373,7 +367,7 @@ public boolean logPlacement(String user, Location location, Material type, byte /** * Logs a block removal by a user. - * + * * @param user * The username * @param blockState @@ -391,7 +385,7 @@ public boolean logRemoval(String user, BlockState blockState) { /** * Logs a block removal by a user with a specific material and block data. - * + * * @param user * The username * @param location @@ -420,7 +414,7 @@ public boolean logRemoval(String user, Location location, Material type, BlockDa /** * Logs a block removal by a user with a specific material and data value. - * + * * @param user * The username * @param location @@ -444,7 +438,7 @@ public boolean logRemoval(String user, Location location, Material type, byte da /** * Parses lookup results into a ParseResult object. - * + * * @param results * The results to parse * @return A ParseResult object containing the parsed data @@ -455,7 +449,7 @@ public ParseResult parseResult(String[] results) { /** * Performs a lookup operation with various filters. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -484,7 +478,7 @@ public List performLookup(int time, List restrictUsers, List performLookup(String user, int time, int radius, Location /** * Performs a partial lookup operation with various filters and pagination support. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -544,7 +538,7 @@ public List performPartialLookup(int time, List restrictUsers, /** * Performs a partial lookup operation with basic filters and pagination support. - * + * * @param user * The user to include in the lookup * @param time @@ -575,7 +569,7 @@ public List performPartialLookup(String user, int time, int radius, Lo /** * Performs a database purge operation. - * + * * @param time * Time in seconds for the purge operation */ @@ -586,7 +580,7 @@ public void performPurge(int time) { /** * Performs a restore operation with various filters. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -615,7 +609,7 @@ public List performRestore(int time, List restrictUsers, List< /** * Performs a restore operation with basic filters. - * + * * @param user * The user to include in the restore * @param time @@ -642,7 +636,7 @@ public List performRestore(String user, int time, int radius, Location /** * Performs a rollback operation with various filters. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -671,7 +665,7 @@ public List performRollback(int time, List restrictUsers, List /** * Performs a rollback operation with basic filters. - * + * * @param user * The user to include in the rollback * @param time @@ -698,7 +692,7 @@ public List performRollback(String user, int time, int radius, Locatio /** * Processes a data request with various filters. - * + * * @param time * Time constraint in seconds * @param radius @@ -824,34 +818,6 @@ else if (argBlock instanceof EntityType && !addedEntity) { } else { if (!Bukkit.isPrimaryThread()) { - if (Config.getGlobal().MYSQL == false) { - List lookupData = Lookup.performLookup(statement, null, uuids, restrictUsers, restrictBlocks, excludeBlocks, excludeUsers, actionList, location, argRadius, startTime, endTime, restrictWorld, true); - - if (lookupData != null && !lookupData.isEmpty()) { - Set chunksToLoad = new HashSet<>(); - for (String[] data : lookupData) { - try { - ParseResult parseResult = new ParseResult(data); - World world = Bukkit.getWorld(parseResult.worldName()); - if (world != null) { - int chunkX = parseResult.getX() >> 4; - int chunkZ = parseResult.getZ() >> 4; - if (!world.isChunkLoaded(chunkX, chunkZ)) { - chunksToLoad.add(world.getChunkAt(chunkX, chunkZ)); - } - } - } - catch (Exception e) { - // ignore - } - } - - if (!chunksToLoad.isEmpty()) { - loadChunksInBatches(chunksToLoad, 10); - } - } - } - boolean verbose = false; result = Rollback.performRollbackRestore(statement, null, uuids, restrictUsers, null, restrictBlocks, excludeBlocks, excludeUsers, actionList, location, argRadius, startTime, endTime, restrictWorld, false, verbose, action, 0); } @@ -872,7 +838,7 @@ else if (argBlock instanceof EntityType && !addedEntity) { /** * Processes a data request with basic filters. - * + * * @param user * The user to include in the operation * @param time @@ -917,7 +883,7 @@ public void testAPI() { /** * Helper method to check if the API is enabled and the player is not null. - * + * * @param player * The player to check * @return True if the API is enabled and the player is not null @@ -928,7 +894,7 @@ private boolean isEnabledForPlayer(Player player) { /** * Helper method to check if a user and location are valid. - * + * * @param user * The username to check * @param location @@ -938,37 +904,4 @@ private boolean isEnabledForPlayer(Player player) { private boolean isValidUserAndLocation(String user, Location location) { return user != null && location != null && !user.isEmpty(); } - - private void loadChunksInBatches(Set chunks, int batchSize) { - List chunkList = new ArrayList<>(chunks); - for (int i = 0; i < chunkList.size(); i += batchSize) { - int end = Math.min(i + batchSize, chunkList.size()); - List batch = chunkList.subList(i, end); - - loadChunkBatch(batch); - - if (i + batchSize < chunkList.size()) { - try { - Thread.sleep(50); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - } - } - } - - private void loadChunkBatch(List chunkBatch) { - if (chunkBatch.isEmpty()) { - return; - } - - List> futures = new ArrayList<>(); - for (Chunk chunk : chunkBatch) { - futures.add(chunk.getWorld().getChunkAtAsync(chunk.getX(), chunk.getZ())); - } - - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); - } } diff --git a/src/main/java/net/coreprotect/database/rollback/Rollback.java b/src/main/java/net/coreprotect/database/rollback/Rollback.java index 4d7d28c1a..eab42a6d6 100644 --- a/src/main/java/net/coreprotect/database/rollback/Rollback.java +++ b/src/main/java/net/coreprotect/database/rollback/Rollback.java @@ -10,6 +10,9 @@ import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -182,7 +185,6 @@ public static List performRollbackRestore(Statement statement, Command } } - // Perform update transaction(s) in consumer if (preview == 0) { if (actionList.contains(11)) { List blockList = new ArrayList<>(); @@ -213,96 +215,162 @@ else if (table == 1) { // container ConfigHandler.rollbackHash.put(userString, new int[] { 0, 0, 0, 0, 0 }); final String finalUserString = userString; - for (Entry entry : DatabaseUtils.entriesSortedByValues(chunkList)) { - chunkCount++; - - int itemCount = 0; - int blockCount = 0; - int entityCount = 0; - int scannedWorldData = 0; - int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); - itemCount = rollbackHashData[0]; - blockCount = rollbackHashData[1]; - entityCount = rollbackHashData[2]; - scannedWorldData = rollbackHashData[4]; - - long chunkKey = entry.getKey(); - final int finalChunkX = (int) chunkKey; - final int finalChunkZ = (int) (chunkKey >> 32); - final CommandSender finalUser = user; - - HashMap worldMap = new HashMap<>(); - for (int rollbackWorldId : worldList) { - String rollbackWorld = WorldUtils.getWorldName(rollbackWorldId); - if (rollbackWorld.length() == 0) { - continue; - } - World bukkitRollbackWorld = Bukkit.getServer().getWorld(rollbackWorld); - if (bukkitRollbackWorld == null) { - continue; + if (ConfigHandler.isFolia) { + List> chunkTasks = new ArrayList<>(); + + for (Entry entry : DatabaseUtils.entriesSortedByValues(chunkList)) { + chunkCount++; + long chunkKey = entry.getKey(); + final int finalChunkX = (int) chunkKey; + final int finalChunkZ = (int) (chunkKey >> 32); + + HashMap worldMap = new HashMap<>(); + for (int rollbackWorldId : worldList) { + String rollbackWorld = WorldUtils.getWorldName(rollbackWorldId); + if (rollbackWorld.length() == 0) { + continue; + } + + World bukkitRollbackWorld = Bukkit.getServer().getWorld(rollbackWorld); + if (bukkitRollbackWorld == null) { + continue; + } + + worldMap.put(rollbackWorldId, bukkitRollbackWorld); } - worldMap.put(rollbackWorldId, bukkitRollbackWorld); + for (Entry rollbackWorlds : worldMap.entrySet()) { + Integer rollbackWorldId = rollbackWorlds.getKey(); + World bukkitRollbackWorld = rollbackWorlds.getValue(); + Location chunkLocation = new Location(bukkitRollbackWorld, (finalChunkX << 4), 0, (finalChunkZ << 4)); + final HashMap> finalBlockList = dataList.get(rollbackWorldId); + final HashMap> finalItemList = itemDataList.get(rollbackWorldId); + + CompletableFuture chunkTask = new CompletableFuture<>(); + chunkTasks.add(chunkTask); + + Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), () -> { + try { + ArrayList blockData = finalBlockList != null ? finalBlockList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>(); + ArrayList itemData = finalItemList != null ? finalItemList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>(); + + if (!bukkitRollbackWorld.isChunkLoaded(finalChunkX, finalChunkZ)) { + bukkitRollbackWorld.getChunkAtAsync(finalChunkX, finalChunkZ).thenAccept(chunk -> { + RollbackProcessor.processChunk(finalChunkX, finalChunkZ, chunkKey, blockData, itemData, rollbackType, preview, finalUserString, user instanceof Player ? (Player) user : null, bukkitRollbackWorld, inventoryRollback); + chunkTask.complete(null); + }).exceptionally(ex -> { + chunkTask.complete(null); + return null; + }); + } else { + RollbackProcessor.processChunk(finalChunkX, finalChunkZ, chunkKey, blockData, itemData, rollbackType, preview, finalUserString, user instanceof Player ? (Player) user : null, bukkitRollbackWorld, inventoryRollback); + chunkTask.complete(null); + } + } catch (Exception e) { + chunkTask.complete(null); + } + }, chunkLocation, 0); + } } - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 0, scannedWorldData }); - for (Entry rollbackWorlds : worldMap.entrySet()) { - Integer rollbackWorldId = rollbackWorlds.getKey(); - World bukkitRollbackWorld = rollbackWorlds.getValue(); - Location chunkLocation = new Location(bukkitRollbackWorld, (finalChunkX << 4), 0, (finalChunkZ << 4)); - final HashMap> finalBlockList = dataList.get(rollbackWorldId); - final HashMap> finalItemList = itemDataList.get(rollbackWorldId); - - Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), () -> { - // Process this chunk using our new RollbackProcessor class - ArrayList blockData = finalBlockList != null ? finalBlockList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>(); - ArrayList itemData = finalItemList != null ? finalItemList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>(); - RollbackProcessor.processChunk(finalChunkX, finalChunkZ, chunkKey, blockData, itemData, rollbackType, preview, finalUserString, finalUser instanceof Player ? (Player) finalUser : null, bukkitRollbackWorld, inventoryRollback); - }, chunkLocation, 0); + CompletableFuture allTasks = CompletableFuture.allOf(chunkTasks.toArray(new CompletableFuture[0])); + + try { + allTasks.get(300, TimeUnit.SECONDS); + } catch (TimeoutException e) { + } catch (Exception e) { } + } else { + for (Entry entry : DatabaseUtils.entriesSortedByValues(chunkList)) { + chunkCount++; + + int itemCount = 0; + int blockCount = 0; + int entityCount = 0; + int scannedWorldData = 0; + int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); + itemCount = rollbackHashData[0]; + blockCount = rollbackHashData[1]; + entityCount = rollbackHashData[2]; + scannedWorldData = rollbackHashData[4]; + + long chunkKey = entry.getKey(); + final int finalChunkX = (int) chunkKey; + final int finalChunkZ = (int) (chunkKey >> 32); + final CommandSender finalUser = user; + + HashMap worldMap = new HashMap<>(); + for (int rollbackWorldId : worldList) { + String rollbackWorld = WorldUtils.getWorldName(rollbackWorldId); + if (rollbackWorld.length() == 0) { + continue; + } - rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); - int next = rollbackHashData[3]; - int scannedWorlds = rollbackHashData[4]; - int sleepTime = 0; - int abort = 0; - - while (next == 0 || scannedWorlds < worldMap.size()) { - if (preview == 1) { - // Not actually changing blocks, so less intensive. - sleepTime = sleepTime + 1; - Thread.sleep(1); + World bukkitRollbackWorld = Bukkit.getServer().getWorld(rollbackWorld); + if (bukkitRollbackWorld == null) { + continue; + } + + worldMap.put(rollbackWorldId, bukkitRollbackWorld); } - else { - sleepTime = sleepTime + 5; - Thread.sleep(5); + + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 0, scannedWorldData }); + for (Entry rollbackWorlds : worldMap.entrySet()) { + Integer rollbackWorldId = rollbackWorlds.getKey(); + World bukkitRollbackWorld = rollbackWorlds.getValue(); + Location chunkLocation = new Location(bukkitRollbackWorld, (finalChunkX << 4), 0, (finalChunkZ << 4)); + final HashMap> finalBlockList = dataList.get(rollbackWorldId); + final HashMap> finalItemList = itemDataList.get(rollbackWorldId); + + Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), () -> { + ArrayList blockData = finalBlockList != null ? finalBlockList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>(); + ArrayList itemData = finalItemList != null ? finalItemList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>(); + RollbackProcessor.processChunk(finalChunkX, finalChunkZ, chunkKey, blockData, itemData, rollbackType, preview, finalUserString, finalUser instanceof Player ? (Player) finalUser : null, bukkitRollbackWorld, inventoryRollback); + }, chunkLocation, 0); } rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); - next = rollbackHashData[3]; - scannedWorlds = rollbackHashData[4]; + int next = rollbackHashData[3]; + int scannedWorlds = rollbackHashData[4]; + int sleepTime = 0; + int abort = 0; + + while (next == 0 || scannedWorlds < worldMap.size()) { + if (preview == 1) { + sleepTime = sleepTime + 1; + Thread.sleep(1); + } + else { + sleepTime = sleepTime + 5; + Thread.sleep(5); + } - if (sleepTime > 300000) { - abort = 1; - break; - } - } + rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); + next = rollbackHashData[3]; + scannedWorlds = rollbackHashData[4]; - if (abort == 1 || next == 2) { - Chat.console(Phrase.build(Phrase.ROLLBACK_ABORTED)); - break; - } + if (sleepTime > 300000) { + abort = 1; + break; + } + } - rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); - itemCount = rollbackHashData[0]; - blockCount = rollbackHashData[1]; - entityCount = rollbackHashData[2]; - ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 0, 0 }); + if (abort == 1 || next == 2) { + Chat.console(Phrase.build(Phrase.ROLLBACK_ABORTED)); + break; + } - if (verbose && user != null && preview == 0 && !actionList.contains(11)) { - Integer chunks = chunkList.size(); - Chat.sendMessage(user, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.ROLLBACK_CHUNKS_MODIFIED, chunkCount.toString(), chunks.toString(), (chunks == 1 ? Selector.FIRST : Selector.SECOND))); + rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); + itemCount = rollbackHashData[0]; + blockCount = rollbackHashData[1]; + entityCount = rollbackHashData[2]; + ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 0, 0 }); + + if (verbose && user != null && preview == 0 && !actionList.contains(11)) { + Integer chunks = chunkList.size(); + Chat.sendMessage(user, Color.DARK_AQUA + "CoreProtect " + Color.WHITE + "- " + Phrase.build(Phrase.ROLLBACK_CHUNKS_MODIFIED, chunkCount.toString(), chunks.toString(), (chunks == 1 ? Selector.FIRST : Selector.SECOND))); + } } } diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java index 810f604a8..0a981f7cc 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java @@ -1,10 +1,5 @@ package net.coreprotect.database.rollback; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.World; @@ -13,10 +8,8 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; -import net.coreprotect.CoreProtect; import net.coreprotect.config.ConfigHandler; import net.coreprotect.thread.CacheHandler; -import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.EntityUtils; import net.coreprotect.utility.WorldUtils; @@ -54,35 +47,24 @@ public class RollbackEntityHandler { * @return The number of entities affected (1 if successful, 0 otherwise) */ public static int processEntity(Object[] row, int rollbackType, String finalUserString, int oldTypeRaw, int rowTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, int rowUserId, String rowUser) { - World bukkitWorld = Bukkit.getServer().getWorld(WorldUtils.getWorldName(rowWorldId)); - if (bukkitWorld == null) { - return 0; - } - - if (ConfigHandler.isFolia) { - CompletableFuture future = bukkitWorld.getChunkAtAsync(rowX >> 4, rowZ >> 4, true).thenApply(chunk -> { - return processEntityLogic(row, rollbackType, finalUserString, oldTypeRaw, rowTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUserId, rowUser, bukkitWorld); - }); - try { - return future.get(10, TimeUnit.SECONDS); - } - catch (InterruptedException | ExecutionException | TimeoutException e) { - e.printStackTrace(); - return 0; - } - } - else { - if (!bukkitWorld.isChunkLoaded(rowX >> 4, rowZ >> 4)) { - bukkitWorld.getChunkAt(rowX >> 4, rowZ >> 4); - } - return processEntityLogic(row, rollbackType, finalUserString, oldTypeRaw, rowTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUserId, rowUser, bukkitWorld); - } - } - - private static int processEntityLogic(Object[] row, int rollbackType, String finalUserString, int oldTypeRaw, int rowTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, int rowUserId, String rowUser, World bukkitWorld) { try { + // Entity kill if (rowAction == 3) { + String world = getWorldName(rowWorldId); + if (world.isEmpty()) { + return 0; + } + + World bukkitWorld = Bukkit.getServer().getWorld(world); + if (bukkitWorld == null) { + return 0; + } + Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); + if (!bukkitWorld.isChunkLoaded(block.getChunk())) { + bukkitWorld.getChunkAt(block.getLocation()); + } + if (rowTypeRaw > 0) { // Spawn in entity if (rowRolledBack == 0) { @@ -119,12 +101,7 @@ else if (rowTypeRaw <= 0) { if (id == entityId) { updateEntityCount(finalUserString, 1); removed = true; - if (ConfigHandler.isFolia) { - Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); - } - else { - entity.remove(); - } + entity.remove(); break; } } @@ -138,12 +115,7 @@ else if (rowTypeRaw <= 0) { if (entityx >= xmin && entityx <= xmax && entityY >= ymin && entityY <= ymax && entityZ >= zmin && entityZ <= zmax) { updateEntityCount(finalUserString, 1); removed = true; - if (ConfigHandler.isFolia) { - Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); - } - else { - entity.remove(); - } + entity.remove(); break; } } @@ -156,12 +128,7 @@ else if (rowTypeRaw <= 0) { if (id == entityId) { updateEntityCount(finalUserString, 1); removed = true; - if (ConfigHandler.isFolia) { - Scheduler.runTask(CoreProtect.getInstance(), entity::remove, entity); - } - else { - entity.remove(); - } + entity.remove(); break; } } @@ -217,7 +184,7 @@ public static void updateEntityCount(String userString, int increment) { /** * Spawns an entity at the given block location. - * + * * @param user * The username of the player * @param block diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java index 2fb5aa486..3274e2a73 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java @@ -25,14 +25,11 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import net.coreprotect.CoreProtect; import net.coreprotect.bukkit.BukkitAdapter; import net.coreprotect.config.Config; import net.coreprotect.config.ConfigHandler; -import net.coreprotect.consumer.Queue; import net.coreprotect.database.logger.ItemLogger; import net.coreprotect.model.BlockGroup; -import net.coreprotect.thread.Scheduler; import net.coreprotect.utility.BlockUtils; import net.coreprotect.utility.ItemUtils; import net.coreprotect.utility.MaterialUtils; @@ -43,7 +40,7 @@ public class RollbackProcessor { /** * Process data for a specific chunk - * + * * @param finalChunkX * The chunk X coordinate * @param finalChunkZ @@ -73,7 +70,6 @@ public static boolean processChunk(int finalChunkX, int finalChunkZ, long chunkK ArrayList itemData = itemList != null ? itemList : new ArrayList<>(); Map chunkChanges = new LinkedHashMap<>(); - // Process blocks for (Object[] row : data) { int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); int itemCount = rollbackHashData[0]; @@ -105,7 +101,6 @@ public static boolean processChunk(int finalChunkX, int finalChunkZ, long chunkK blockData = Bukkit.getServer().createBlockData(blockDataString); } catch (Exception e) { - // corrupt BlockData, let the server automatically set the BlockData instead } } @@ -185,7 +180,7 @@ else if (rowAction == 3) { // entity kill } Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); - if (!bukkitWorld.isChunkLoaded(block.getChunk())) { + if (!ConfigHandler.isFolia && !bukkitWorld.isChunkLoaded(block.getChunk())) { bukkitWorld.getChunkAt(block.getLocation()); } @@ -208,11 +203,10 @@ else if (rowAction == 3) { // entity kill } if ((rowType == pendingChangeType) && ((!BukkitAdapter.ADAPTER.isItemFrame(oldTypeMaterial)) && (oldTypeMaterial != Material.PAINTING) && (oldTypeMaterial != Material.ARMOR_STAND)) && (oldTypeMaterial != Material.END_CRYSTAL)) { - // block is already changed! BlockData checkData = rowType == Material.AIR ? blockData : rawBlockData; if (checkData != null) { if (checkData.getAsString().equals(pendingChangeData.getAsString()) || checkData instanceof org.bukkit.block.data.MultipleFacing || checkData instanceof org.bukkit.block.data.type.Stairs || checkData instanceof org.bukkit.block.data.type.RedstoneWire) { - if (rowType != Material.CHEST && rowType != Material.TRAPPED_CHEST && !BukkitAdapter.ADAPTER.isCopperChest(rowType)) { // always update double chests + if (rowType != Material.CHEST && rowType != Material.TRAPPED_CHEST && !BukkitAdapter.ADAPTER.isCopperChest(rowType)) { changeBlock = false; } } @@ -245,10 +239,8 @@ else if ((pendingChangeType != Material.AIR) && (pendingChangeType != Material.C } data.clear(); - // Apply cached block changes RollbackBlockHandler.applyBlockChanges(chunkChanges, preview, finalUser instanceof Player ? (Player) finalUser : null); - // Process container items Map> sortPlayers = new HashMap<>(); Object container = null; Material containerType = null; @@ -325,11 +317,11 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT itemCount1 = itemCount1 + rowAmount; ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, scannedWorlds }); - continue; // remove this for merged rollbacks in future? (be sure to re-enable chunk sorting) + continue; } if (inventoryRollback || rowAction > 1) { - continue; // skip inventory & ender chest transactions + continue; } if ((rollbackType == 0 && rowRolledBack == 0) || (rollbackType == 1 && rowRolledBack == 1)) { @@ -338,7 +330,7 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT String faceData = (String) populatedStack[1]; if (!containerInit || rowX != lastX || rowY != lastY || rowZ != lastZ || rowWorldId != lastWorldId || !faceData.equals(lastFace)) { - container = null; // container patch 2.14.0 + container = null; String world = WorldUtils.getWorldName(rowWorldId); if (world.length() == 0) { continue; @@ -349,7 +341,7 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT continue; } Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ); - if (!bukkitWorld.isChunkLoaded(block.getChunk())) { + if (!ConfigHandler.isFolia && !bukkitWorld.isChunkLoaded(block.getChunk())) { bukkitWorld.getChunkAt(block.getLocation()); } @@ -425,7 +417,6 @@ else if (entity instanceof ItemFrame) { int scannedWorlds = rollbackHashData[4]; ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 1, (scannedWorlds + 1) }); - // Teleport players out of danger if they're within this chunk if (preview == 0) { for (Player player : Bukkit.getOnlinePlayers()) { Location playerLocation = player.getLocation(); @@ -434,9 +425,7 @@ else if (entity instanceof ItemFrame) { int chunkZ = playerLocation.getBlockZ() >> 4; if (bukkitRollbackWorld.getName().equals(playerWorld) && chunkX == finalChunkX && chunkZ == finalChunkZ) { - Scheduler.runTask(CoreProtect.getInstance(), () -> { - Teleport.performSafeTeleport(player, playerLocation, false); - }, player); + Teleport.performSafeTeleport(player, playerLocation, false); } } } From 6cd29649b669ae6929f7488ac84d0fa5fc313c33 Mon Sep 17 00:00:00 2001 From: leir4iks <118904014+leir4iks@users.noreply.github.com> Date: Sun, 9 Nov 2025 18:13:31 +0200 Subject: [PATCH 19/22] Update CoreProtectAPI.java --- .../java/net/coreprotect/CoreProtectAPI.java | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/main/java/net/coreprotect/CoreProtectAPI.java b/src/main/java/net/coreprotect/CoreProtectAPI.java index ae27cf4a6..757427b36 100755 --- a/src/main/java/net/coreprotect/CoreProtectAPI.java +++ b/src/main/java/net/coreprotect/CoreProtectAPI.java @@ -47,7 +47,7 @@ public static class ParseResult extends net.coreprotect.api.result.ParseResult { /** * Creates a new ParseResult from string array data. - * + * * @param data * The string array data to parse */ @@ -58,7 +58,7 @@ public ParseResult(String[] data) { /** * Converts a list of objects to a map for internal processing - * + * * @param list * List of objects to convert * @return Map with objects as keys and Boolean false as values @@ -83,7 +83,7 @@ else if (value instanceof Integer) { /** * Returns the current API version. - * + * * @return The API version as an integer */ public int APIVersion() { @@ -92,7 +92,7 @@ public int APIVersion() { /** * Performs a block lookup at the specified block. - * + * * @param block * The block to look up * @param time @@ -108,7 +108,7 @@ public List blockLookup(Block block, int time) { /** * Performs a lookup on the queue data for the specified block. - * + * * @param block * The block to look up * @return List of results @@ -119,7 +119,7 @@ public List queueLookup(Block block) { /** * Performs a lookup on session data for the specified user. - * + * * @param user * The user to look up * @param time @@ -132,7 +132,7 @@ public List sessionLookup(String user, int time) { /** * Determines if a user has placed a block at the specified location. - * + * * @param user * The username to check * @param block @@ -164,7 +164,7 @@ public boolean hasPlaced(String user, Block block, int time, int offset) { /** * Determines if a user has removed a block at the specified location. - * + * * @param user * The username to check * @param block @@ -196,7 +196,7 @@ public boolean hasRemoved(String user, Block block, int time, int offset) { /** * Gets the current time in milliseconds. Protected to allow mocking in tests. - * + * * @return Current time in milliseconds */ protected long getCurrentTimeMillis() { @@ -205,7 +205,7 @@ protected long getCurrentTimeMillis() { /** * Checks if the CoreProtect API is enabled. - * + * * @return True if the API is enabled */ public boolean isEnabled() { @@ -214,7 +214,7 @@ public boolean isEnabled() { /** * Logs a chat message for a player. - * + * * @param player * The player who sent the message * @param message @@ -237,7 +237,7 @@ public boolean logChat(Player player, String message) { /** * Logs a command executed by a player. - * + * * @param player * The player who executed the command * @param command @@ -260,7 +260,7 @@ public boolean logCommand(Player player, String command) { /** * Logs an interaction by a user at a location. - * + * * @param user * The username * @param location @@ -278,7 +278,7 @@ public boolean logInteraction(String user, Location location) { /** * Logs a container transaction by a user at a location. - * + * * @param user * The username * @param location @@ -295,7 +295,7 @@ public boolean logContainerTransaction(String user, Location location) { /** * Logs a block placement by a user. - * + * * @param user * The username * @param blockState @@ -313,7 +313,7 @@ public boolean logPlacement(String user, BlockState blockState) { /** * Logs a block placement by a user with a specific material and block data. - * + * * @param user * The username * @param location @@ -343,7 +343,7 @@ public boolean logPlacement(String user, Location location, Material type, Block /** * Logs a block placement by a user with a specific material and data value. - * + * * @param user * The username * @param location @@ -367,7 +367,7 @@ public boolean logPlacement(String user, Location location, Material type, byte /** * Logs a block removal by a user. - * + * * @param user * The username * @param blockState @@ -385,7 +385,7 @@ public boolean logRemoval(String user, BlockState blockState) { /** * Logs a block removal by a user with a specific material and block data. - * + * * @param user * The username * @param location @@ -414,7 +414,7 @@ public boolean logRemoval(String user, Location location, Material type, BlockDa /** * Logs a block removal by a user with a specific material and data value. - * + * * @param user * The username * @param location @@ -438,7 +438,7 @@ public boolean logRemoval(String user, Location location, Material type, byte da /** * Parses lookup results into a ParseResult object. - * + * * @param results * The results to parse * @return A ParseResult object containing the parsed data @@ -449,7 +449,7 @@ public ParseResult parseResult(String[] results) { /** * Performs a lookup operation with various filters. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -478,7 +478,7 @@ public List performLookup(int time, List restrictUsers, List performLookup(String user, int time, int radius, Location /** * Performs a partial lookup operation with various filters and pagination support. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -538,7 +538,7 @@ public List performPartialLookup(int time, List restrictUsers, /** * Performs a partial lookup operation with basic filters and pagination support. - * + * * @param user * The user to include in the lookup * @param time @@ -569,7 +569,7 @@ public List performPartialLookup(String user, int time, int radius, Lo /** * Performs a database purge operation. - * + * * @param time * Time in seconds for the purge operation */ @@ -580,7 +580,7 @@ public void performPurge(int time) { /** * Performs a restore operation with various filters. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -609,7 +609,7 @@ public List performRestore(int time, List restrictUsers, List< /** * Performs a restore operation with basic filters. - * + * * @param user * The user to include in the restore * @param time @@ -636,7 +636,7 @@ public List performRestore(String user, int time, int radius, Location /** * Performs a rollback operation with various filters. - * + * * @param time * Time constraint in seconds * @param restrictUsers @@ -665,7 +665,7 @@ public List performRollback(int time, List restrictUsers, List /** * Performs a rollback operation with basic filters. - * + * * @param user * The user to include in the rollback * @param time @@ -692,7 +692,7 @@ public List performRollback(String user, int time, int radius, Locatio /** * Processes a data request with various filters. - * + * * @param time * Time constraint in seconds * @param radius @@ -838,7 +838,7 @@ else if (argBlock instanceof EntityType && !addedEntity) { /** * Processes a data request with basic filters. - * + * * @param user * The user to include in the operation * @param time @@ -883,7 +883,7 @@ public void testAPI() { /** * Helper method to check if the API is enabled and the player is not null. - * + * * @param player * The player to check * @return True if the API is enabled and the player is not null @@ -894,7 +894,7 @@ private boolean isEnabledForPlayer(Player player) { /** * Helper method to check if a user and location are valid. - * + * * @param user * The username to check * @param location From c7088ae6f57897b805b6e66ec5a12aa92f535147 Mon Sep 17 00:00:00 2001 From: leir4iks <118904014+leir4iks@users.noreply.github.com> Date: Sun, 9 Nov 2025 18:17:02 +0200 Subject: [PATCH 20/22] Update Rollback.java --- src/main/java/net/coreprotect/database/rollback/Rollback.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/net/coreprotect/database/rollback/Rollback.java b/src/main/java/net/coreprotect/database/rollback/Rollback.java index eab42a6d6..4665f823f 100644 --- a/src/main/java/net/coreprotect/database/rollback/Rollback.java +++ b/src/main/java/net/coreprotect/database/rollback/Rollback.java @@ -185,6 +185,7 @@ public static List performRollbackRestore(Statement statement, Command } } + // Perform update transaction(s) in consumer if (preview == 0) { if (actionList.contains(11)) { List blockList = new ArrayList<>(); @@ -324,6 +325,7 @@ else if (table == 1) { // container final HashMap> finalItemList = itemDataList.get(rollbackWorldId); Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), () -> { + // Process this chunk using our new RollbackProcessor class ArrayList blockData = finalBlockList != null ? finalBlockList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>(); ArrayList itemData = finalItemList != null ? finalItemList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>(); RollbackProcessor.processChunk(finalChunkX, finalChunkZ, chunkKey, blockData, itemData, rollbackType, preview, finalUserString, finalUser instanceof Player ? (Player) finalUser : null, bukkitRollbackWorld, inventoryRollback); From 854b3cbc82ab61d1018b219efc64fe88cd749789 Mon Sep 17 00:00:00 2001 From: leir4iks <118904014+leir4iks@users.noreply.github.com> Date: Sun, 9 Nov 2025 18:18:06 +0200 Subject: [PATCH 21/22] Update RollbackEntityHandler.java --- .../coreprotect/database/rollback/RollbackEntityHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java index 0a981f7cc..10cdf50e1 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackEntityHandler.java @@ -184,7 +184,7 @@ public static void updateEntityCount(String userString, int increment) { /** * Spawns an entity at the given block location. - * + * * @param user * The username of the player * @param block From 5f788e9b5dce741aac7d645cd03619a54048046d Mon Sep 17 00:00:00 2001 From: leir4iks <118904014+leir4iks@users.noreply.github.com> Date: Sun, 9 Nov 2025 18:22:42 +0200 Subject: [PATCH 22/22] Update RollbackProcessor.java --- .../database/rollback/RollbackProcessor.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java index 3274e2a73..0dff9f691 100644 --- a/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java +++ b/src/main/java/net/coreprotect/database/rollback/RollbackProcessor.java @@ -40,7 +40,7 @@ public class RollbackProcessor { /** * Process data for a specific chunk - * + * * @param finalChunkX * The chunk X coordinate * @param finalChunkZ @@ -70,6 +70,7 @@ public static boolean processChunk(int finalChunkX, int finalChunkZ, long chunkK ArrayList itemData = itemList != null ? itemList : new ArrayList<>(); Map chunkChanges = new LinkedHashMap<>(); + // Process blocks for (Object[] row : data) { int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString); int itemCount = rollbackHashData[0]; @@ -101,6 +102,7 @@ public static boolean processChunk(int finalChunkX, int finalChunkZ, long chunkK blockData = Bukkit.getServer().createBlockData(blockDataString); } catch (Exception e) { + // corrupt BlockData, let the server automatically set the BlockData instead } } @@ -203,10 +205,11 @@ else if (rowAction == 3) { // entity kill } if ((rowType == pendingChangeType) && ((!BukkitAdapter.ADAPTER.isItemFrame(oldTypeMaterial)) && (oldTypeMaterial != Material.PAINTING) && (oldTypeMaterial != Material.ARMOR_STAND)) && (oldTypeMaterial != Material.END_CRYSTAL)) { + // block is already changed! BlockData checkData = rowType == Material.AIR ? blockData : rawBlockData; if (checkData != null) { if (checkData.getAsString().equals(pendingChangeData.getAsString()) || checkData instanceof org.bukkit.block.data.MultipleFacing || checkData instanceof org.bukkit.block.data.type.Stairs || checkData instanceof org.bukkit.block.data.type.RedstoneWire) { - if (rowType != Material.CHEST && rowType != Material.TRAPPED_CHEST && !BukkitAdapter.ADAPTER.isCopperChest(rowType)) { + if (rowType != Material.CHEST && rowType != Material.TRAPPED_CHEST && !BukkitAdapter.ADAPTER.isCopperChest(rowType)) { // always update double chests changeBlock = false; } } @@ -239,8 +242,10 @@ else if ((pendingChangeType != Material.AIR) && (pendingChangeType != Material.C } data.clear(); + // Apply cached block changes RollbackBlockHandler.applyBlockChanges(chunkChanges, preview, finalUser instanceof Player ? (Player) finalUser : null); + // Process container items Map> sortPlayers = new HashMap<>(); Object container = null; Material containerType = null; @@ -317,11 +322,11 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT itemCount1 = itemCount1 + rowAmount; ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, scannedWorlds }); - continue; + continue; // remove this for merged rollbacks in future? (be sure to re-enable chunk sorting) } if (inventoryRollback || rowAction > 1) { - continue; + continue; // skip inventory & ender chest transactions } if ((rollbackType == 0 && rowRolledBack == 0) || (rollbackType == 1 && rowRolledBack == 1)) { @@ -330,7 +335,7 @@ else if (rowAction == ItemLogger.ITEM_REMOVE_ENDER || rowAction == ItemLogger.IT String faceData = (String) populatedStack[1]; if (!containerInit || rowX != lastX || rowY != lastY || rowZ != lastZ || rowWorldId != lastWorldId || !faceData.equals(lastFace)) { - container = null; + container = null; // container patch 2.14.0 String world = WorldUtils.getWorldName(rowWorldId); if (world.length() == 0) { continue; @@ -417,6 +422,7 @@ else if (entity instanceof ItemFrame) { int scannedWorlds = rollbackHashData[4]; ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 1, (scannedWorlds + 1) }); + // Teleport players out of danger if they're within this chunk if (preview == 0) { for (Player player : Bukkit.getOnlinePlayers()) { Location playerLocation = player.getLocation();