diff --git a/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java b/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java index 2ac3f79..209b11a 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java +++ b/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java @@ -1975,6 +1975,10 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter { Msg.error(sender, "A âncora " + loader.displayName() + " já está " + (enabled ? "ativa" : "pausada") + "."); return; } + if (enabled && loader.isExpired()) { + Msg.error(sender, "A âncora " + loader.displayName() + " está expirada! Adicione tempo (/chunkloader tempo " + loader.id() + " ) ou abasteça com combustível antes de reativar."); + return; + } plugin.chunkLoaders().setEnabled(loader.id(), enabled); plugin.blueMap().syncChunkLoaders(); Msg.ok(sender, "Âncora " + loader.displayName() + " " + (enabled ? "ATIVADA." : "PAUSADA/DESATIVADA.")); @@ -1998,16 +2002,20 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter { } try { double hours = Double.parseDouble(args[1]); - if (hours <= 0 && isAdmin) { - plugin.chunkLoaders().addTime(loader.id(), -loader.expiresAt()); // permanent - Msg.ok(sender, "Âncora " + loader.displayName() + " definida como PERMANENTE."); - } else { - long millis = (long) (hours * 3600_000L); - plugin.chunkLoaders().addTime(loader.id(), millis); - ChunkLoader updated = plugin.chunkLoaders().byId(loader.id()); - String time = (updated != null) ? updated.timeLeft() : "Permanente"; - Msg.ok(sender, "Adicionado " + hours + "h à âncora " + loader.displayName() + ". Tempo restante: " + time); + if (hours <= 0) { + if (isAdmin) { + plugin.chunkLoaders().setExpiresAt(loader.id(), 0L); // permanent + Msg.ok(sender, "Âncora " + loader.displayName() + " definida como PERMANENTE."); + } else { + Msg.error(sender, "A quantidade de horas deve ser maior que zero."); + } + return; } + long millis = (long) (hours * 3600_000L); + plugin.chunkLoaders().addTime(loader.id(), millis); + ChunkLoader updated = plugin.chunkLoaders().byId(loader.id()); + String time = (updated != null) ? updated.timeLeft() : "Permanente"; + Msg.ok(sender, "Adicionado " + hours + "h à âncora " + loader.displayName() + ". Tempo restante: " + time); plugin.blueMap().syncChunkLoaders(); } catch (NumberFormatException e) { Msg.error(sender, "Horas inválidas: " + args[1]); diff --git a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaderListener.java b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaderListener.java index b729f2e..f4b0eac 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaderListener.java +++ b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaderListener.java @@ -209,6 +209,10 @@ final class ChunkLoaderListener implements Listener { } if (player.isSneaking() && (isOwner || isAdmin)) { + if (!loader.enabled() && loader.isExpired()) { + Msg.error(player, "A âncora " + loader.displayName() + " está expirada! Clique com combustível (Pérola do End, Blaze, Diamante, etc.) ou adicione tempo antes de reativar."); + return; + } boolean newState = !loader.enabled(); plugin.chunkLoaders().setEnabled(loader.id(), newState); Location l = block.getLocation().add(0.5, 0.5, 0.5); diff --git a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java index 4d5b380..4d97871 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java +++ b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java @@ -191,20 +191,23 @@ final class ChunkLoaders { } /** - * Finds a chunk loader by its numeric ID (with or without '#') or custom name. + * Finds a chunk loader by its numeric ID (with '#' prefix or plain ID) or custom name. + * Custom names take precedence over raw numeric IDs to avoid shadowing named loaders. */ ChunkLoader find(String query) { if (query == null || query.isBlank()) { return null; } String trimmed = query.trim(); - try { - long id = Long.parseLong(trimmed.startsWith("#") ? trimmed.substring(1) : trimmed); - ChunkLoader loader = byId(id); - if (loader != null) { - return loader; + if (trimmed.startsWith("#")) { + try { + long id = Long.parseLong(trimmed.substring(1)); + ChunkLoader loader = byId(id); + if (loader != null) { + return loader; + } + } catch (NumberFormatException ignored) { } - } catch (NumberFormatException ignored) { } synchronized (loaders) { @@ -214,6 +217,15 @@ final class ChunkLoaders { } } } + + try { + long id = Long.parseLong(trimmed); + ChunkLoader loader = byId(id); + if (loader != null) { + return loader; + } + } catch (NumberFormatException ignored) { + } return null; } @@ -503,6 +515,15 @@ final class ChunkLoaders { if (w != null) { w.removePluginChunkTicket(loader.chunkX(), loader.chunkZ(), plugin); w.setChunkForceLoaded(loader.chunkX(), loader.chunkZ(), false); + if (w.isChunkLoaded(loader.chunkX(), loader.chunkZ())) { + Chunk chunk = w.getChunkAt(loader.chunkX(), loader.chunkZ()); + for (BlockState state : chunk.getTileEntities()) { + if (state instanceof CreatureSpawner spawner && spawner.getRequiredPlayerRange() > 16) { + spawner.setRequiredPlayerRange(16); + spawner.update(true, false); + } + } + } } } catch (Exception ignored) { } diff --git a/src/main/java/dev/marcospaulo/canalhandia/VoidProtection.java b/src/main/java/dev/marcospaulo/canalhandia/VoidProtection.java index 6191934..eb06207 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/VoidProtection.java +++ b/src/main/java/dev/marcospaulo/canalhandia/VoidProtection.java @@ -129,6 +129,31 @@ public final class VoidProtection { || mat == Material.SNOW || mat == Material.FERN || mat == Material.LARGE_FERN; } + /** + * Calculates the estimated number of inventory slots needed to store the given items. + */ + public static int calculateRequiredSlots(List items) { + if (items == null || items.isEmpty()) { + return 0; + } + int slots = 0; + for (ItemStack item : items) { + if (item != null && item.getType() != Material.AIR && item.getAmount() > 0) { + int maxStack = Math.max(1, item.getMaxStackSize()); + slots += (int) Math.ceil((double) item.getAmount() / maxStack); + } + } + return slots; + } + + public static boolean canFitInSingleChest(List items) { + return calculateRequiredSlots(items) <= 27; + } + + public static boolean canFitInDoubleChest(List items) { + return calculateRequiredSlots(items) <= 54; + } + /** * Stores items into a chest (and an adjacent chest if needed) at the target location. * All items must be stored without overflow; on any failure, blocks are rolled back diff --git a/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java b/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java index 3b812c8..9201a86 100644 --- a/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java +++ b/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java @@ -122,6 +122,12 @@ class ChunkLoaderTest { assertEquals(renamed, loaders.find("#1")); assertEquals(renamed, loaders.find("1")); + // Loader 2 named "1" should be prioritized over ID 1 when querying by plain "1" + ChunkLoader loader2 = loaders.add("uuid-marcos", "Marcos", "world", 200, 64, 300, "1", 0L); + assertEquals(loader2, loaders.find("1")); + assertEquals(renamed, loaders.find("#1")); + assertEquals(loader2, loaders.find("#2")); + assertTrue(loaders.setEnabled(loader.id(), false)); ChunkLoader disabled = loaders.byId(loader.id()); assertNotNull(disabled); diff --git a/src/test/java/dev/marcospaulo/canalhandia/VoidProtectionTest.java b/src/test/java/dev/marcospaulo/canalhandia/VoidProtectionTest.java index 2c0bfdb..8e8f639 100644 --- a/src/test/java/dev/marcospaulo/canalhandia/VoidProtectionTest.java +++ b/src/test/java/dev/marcospaulo/canalhandia/VoidProtectionTest.java @@ -37,4 +37,12 @@ class VoidProtectionTest { assertFalse(VoidProtection.isSafeGround(null)); assertFalse(VoidProtection.isReplaceable(null)); } + + @Test + void calculatesRequiredSlotsAndChestFit() { + assertEquals(0, VoidProtection.calculateRequiredSlots(null)); + assertEquals(0, VoidProtection.calculateRequiredSlots(List.of())); + assertTrue(VoidProtection.canFitInSingleChest(List.of())); + assertTrue(VoidProtection.canFitInDoubleChest(List.of())); + } }