diff --git a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java index 2dadafb..8845ab5 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java +++ b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java @@ -36,7 +36,7 @@ final class ChunkLoaders { private static final String PERM_LIMIT_PREFIX = "canalhandia.chunkloader.limite."; - private final Plugin plugin; + private final Canalhandia plugin; private final File file; private final List loaders = new ArrayList<>(); private final AtomicLong nextId = new AtomicLong(1); @@ -48,7 +48,7 @@ final class ChunkLoaders { return t; }); - ChunkLoaders(Plugin plugin, File file) { + ChunkLoaders(Canalhandia plugin, File file) { this.plugin = plugin; this.file = file; load(); @@ -97,11 +97,21 @@ final class ChunkLoaders { * Registers a new chunk loader and activates the ticket in the world. */ ChunkLoader add(String ownerUuid, String ownerName, String world, int x, int y, int z) { - return add(ownerUuid, ownerName, world, x, y, z, "", 0L); + return add(ownerUuid, ownerName, world, x, y, z, "", defaultInitialExpiration()); } ChunkLoader add(String ownerUuid, String ownerName, String world, int x, int y, int z, String name) { - return add(ownerUuid, ownerName, world, x, y, z, name, 0L); + return add(ownerUuid, ownerName, world, x, y, z, name, defaultInitialExpiration()); + } + + private long defaultInitialExpiration() { + if (plugin != null && plugin.settings() != null && plugin.settings().chunkLoaderRequiresTime()) { + double hours = plugin.settings().chunkLoaderInitialHours(); + if (hours > 0) { + return System.currentTimeMillis() + (long) (hours * 3600_000L); + } + } + return 0L; } ChunkLoader add(String ownerUuid, String ownerName, String world, int x, int y, int z, String name, long expiresAt) { @@ -417,28 +427,34 @@ final class ChunkLoaders { } Chunk chunk = w.getChunkAt(loader.chunkX(), loader.chunkZ()); - // 1. Keep mob spawners (dungeon / blaze / skeleton cages) active - for (BlockState state : chunk.getTileEntities()) { - if (state instanceof CreatureSpawner spawner) { - if (spawner.getRequiredPlayerRange() < 1024) { - spawner.setRequiredPlayerRange(2048); - spawner.update(true, false); + boolean spawnersActive = plugin.settings() == null || plugin.settings().chunkLoaderSpawnersActive(); + boolean monsterSpawning = plugin.settings() == null || plugin.settings().chunkLoaderMonsterSpawning(); + int monsterCap = plugin.settings() != null ? plugin.settings().chunkLoaderMonsterCap() : 20; + + // 1. Keep mob spawners (dungeon / blaze / skeleton cages) active if enabled + if (spawnersActive) { + for (BlockState state : chunk.getTileEntities()) { + if (state instanceof CreatureSpawner spawner) { + if (spawner.getRequiredPlayerRange() < 1024) { + spawner.setRequiredPlayerRange(2048); + spawner.update(true, false); + } } } } - // 2. Simulate natural mob spawning (dark platforms / slime / nether farms) - long mobCount = 0; - for (Entity entity : chunk.getEntities()) { - if (entity instanceof Mob) { - mobCount++; + // 2. Simulate natural mob spawning (dark platforms / slime / nether farms) if enabled + if (monsterSpawning) { + long mobCount = 0; + for (Entity entity : chunk.getEntities()) { + if (entity instanceof Mob) { + mobCount++; + } + } + if (mobCount < monsterCap) { + simulateNaturalSpawning(w, loader, chunk); } } - if (mobCount >= 20) { - continue; // Respect chunk mob cap - } - - simulateNaturalSpawning(w, loader, chunk); } catch (Exception ignored) { } } diff --git a/src/main/java/dev/marcospaulo/canalhandia/Settings.java b/src/main/java/dev/marcospaulo/canalhandia/Settings.java index c431ef2..4c1f4ad 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/Settings.java +++ b/src/main/java/dev/marcospaulo/canalhandia/Settings.java @@ -597,6 +597,54 @@ final class Settings { set("chunkloader.bluemap", enabled); } + boolean chunkLoaderSpawnersActive() { + return plugin.getConfig().getBoolean("chunkloader.simulacao.gaiolas-ativas", true); + } + + void chunkLoaderSpawnersActive(boolean active) { + set("chunkloader.simulacao.gaiolas-ativas", active); + } + + boolean chunkLoaderMonsterSpawning() { + return plugin.getConfig().getBoolean("chunkloader.simulacao.spawn-monstros", true); + } + + void chunkLoaderMonsterSpawning(boolean spawning) { + set("chunkloader.simulacao.spawn-monstros", spawning); + } + + int chunkLoaderMonsterCap() { + return Math.max(1, plugin.getConfig().getInt("chunkloader.simulacao.limite-monstros-chunk", 20)); + } + + void chunkLoaderMonsterCap(int cap) { + set("chunkloader.simulacao.limite-monstros-chunk", Math.max(1, cap)); + } + + boolean chunkLoaderPassiveEntities() { + return plugin.getConfig().getBoolean("chunkloader.simulacao.tick-entidades-passivas", true); + } + + void chunkLoaderPassiveEntities(boolean active) { + set("chunkloader.simulacao.tick-entidades-passivas", active); + } + + boolean chunkLoaderRequiresTime() { + return plugin.getConfig().getBoolean("chunkloader.combustivel.requer-tempo", true); + } + + void chunkLoaderRequiresTime(boolean required) { + set("chunkloader.combustivel.requer-tempo", required); + } + + double chunkLoaderInitialHours() { + return Math.max(0.0, plugin.getConfig().getDouble("chunkloader.combustivel.tempo-inicial-horas", 24.0)); + } + + void chunkLoaderInitialHours(double hours) { + set("chunkloader.combustivel.tempo-inicial-horas", Math.max(0.0, hours)); + } + // --- salvavoid ---------------------------------------------------------- int voidProtectionRadius() { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 5d47b64..698b5dd 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -344,3 +344,43 @@ mortes: - "BONE | Osso da Sorte | Um ossinho pra você, campeão." - "COOKIE | Cookie de Consolação | Cookie de consolação. Vai que melhora." - "ROTTEN_FLESH | Carne Podre | É o que tinha sobrado na despensa." + +# --- Âncoras de Chunk (Chunk Loaders) ---------------------------------------- +chunkloader: + # Limite máximo de âncoras ativas por jogador por padrão. + # Pode ser alterado por jogador com /chunkloader limite . + limite-padrao: 1 + + # Exibe os marcadores de âncoras ativas no BlueMap. + bluemap: true + + # Configurações de simulação de entidades na chunk carregada: + simulacao: + # Se true, mantém ativas gaiolas de monstros (spawners de blaze, esqueletos, etc.) + # mesmo sem jogadores por perto. + gaiolas-ativas: true + + # Se true, simula o surgimento natural de monstros em plataformas escuras, + # slime chunks e fortalezas do nether. Se false, apenas entidades existentes funcionam. + spawn-monstros: true + + # Teto máximo de monstros na chunk antes de pausar o spawn natural (evita acúmulo de lag). + limite-monstros-chunk: 20 + + # Se true, mantém animais, aldeões, ferro-golems e colheitas sendo processados na chunk. + tick-entidades-passivas: true + + # Sistema de combustível e tempo de expiração: + combustivel: + # Se true, âncoras requerem abastecimento com itens e têm tempo de expiração. + # Se false, todas as âncoras colocadas são permanentes por padrão. + requer-tempo: true + + # Tempo inicial padrão dado ao criar e colocar uma nova âncora (em horas). + tempo-inicial-horas: 24 + +# --- Salva-Void (Resgate de Morte no Vazio) ----------------------------------- +salvavoid: + # Raio máximo de busca em blocos a partir de onde o jogador caiu para achar + # terreno seguro e colocar o baú com os itens resgatados. + raio-busca: 32 diff --git a/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java b/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java index cbd8ef8..f2902a6 100644 --- a/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java +++ b/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java @@ -203,6 +203,15 @@ class ChunkLoaderTest { assertNull(ChunkLoaders.pickEntityType(org.bukkit.World.Environment.NORMAL, false, 64, 5, 10)); } + @Test + void createsLoaderWithInitialExpiration() { + long targetExpiry = System.currentTimeMillis() + 86400_000L; + ChunkLoader loader = loaders.add("uuid-1", "Marcos", "world", 100, 64, 200, "Base", targetExpiry); + assertEquals(targetExpiry, loader.expiresAt()); + assertFalse(loader.isExpired()); + assertTrue(loader.timeLeft().contains("h")); + } + @Test void simulationSafelyNoopsWithNullPlugin() { loaders.startSimulation();