diff --git a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java index 4d97871..2dadafb 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java +++ b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java @@ -144,6 +144,29 @@ final class ChunkLoaders { return false; } + /** + * Sets a specific expiration timestamp (or 0 for permanent) for a chunk loader. + */ + boolean setExpiresAt(long id, long expiresAt) { + ChunkLoader updated = null; + synchronized (loaders) { + for (int i = 0; i < loaders.size(); i++) { + ChunkLoader curr = loaders.get(i); + if (curr.id() == id) { + updated = curr.withExpiresAt(expiresAt).withEnabled(true); + loaders.set(i, updated); + break; + } + } + } + if (updated != null) { + addTicket(updated); + save(); + return true; + } + return false; + } + /** * Renames an existing chunk loader. */ @@ -459,15 +482,21 @@ final class ChunkLoaders { } static EntityType pickEntityType(World w, Block space, int light) { - World.Environment env = w.getEnvironment(); - ThreadLocalRandom rnd = ThreadLocalRandom.current(); + boolean isSlime = false; + try { + isSlime = space.getChunk().isSlimeChunk(); + } catch (Exception ignored) { + } + return pickEntityType(w.getEnvironment(), isSlime, space.getY(), light, ThreadLocalRandom.current().nextInt(100)); + } + static EntityType pickEntityType(World.Environment env, boolean isSlimeChunk, int y, int light, int roll) { if (env == World.Environment.NETHER) { if (light > 11) return null; - int roll = rnd.nextInt(100); - if (roll < 45) return EntityType.ZOMBIFIED_PIGLIN; - if (roll < 65) return EntityType.WITHER_SKELETON; - if (roll < 85) return EntityType.BLAZE; + int r = Math.floorMod(roll, 100); + if (r < 45) return EntityType.ZOMBIFIED_PIGLIN; + if (r < 65) return EntityType.WITHER_SKELETON; + if (r < 85) return EntityType.BLAZE; return EntityType.MAGMA_CUBE; } @@ -475,8 +504,7 @@ final class ChunkLoaders { return EntityType.ENDERMAN; } - boolean isSlimeChunk = space.getChunk().isSlimeChunk() && space.getY() < 40; - if (isSlimeChunk && light <= 7 && rnd.nextInt(3) == 0) { + if (isSlimeChunk && y < 40 && light <= 7 && roll % 3 == 0) { return EntityType.SLIME; } @@ -484,11 +512,11 @@ final class ChunkLoaders { return null; } - int roll = rnd.nextInt(100); - if (roll < 35) return EntityType.ZOMBIE; - if (roll < 65) return EntityType.SKELETON; - if (roll < 85) return EntityType.CREEPER; - if (roll < 95) return EntityType.SPIDER; + int r = Math.floorMod(roll, 100); + if (r < 35) return EntityType.ZOMBIE; + if (r < 65) return EntityType.SKELETON; + if (r < 85) return EntityType.CREEPER; + if (r < 95) return EntityType.SPIDER; return EntityType.WITCH; } diff --git a/src/main/java/dev/marcospaulo/canalhandia/VoidProtection.java b/src/main/java/dev/marcospaulo/canalhandia/VoidProtection.java index eb06207..600d6bc 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/VoidProtection.java +++ b/src/main/java/dev/marcospaulo/canalhandia/VoidProtection.java @@ -108,24 +108,35 @@ public final class VoidProtection { } static boolean isSafeGround(Block block) { - if (block == null) { + return block != null && isSafeGroundMaterial(block.getType()); + } + + public static boolean isSafeGroundMaterial(Material mat) { + if (mat == null || mat == Material.AIR || mat == Material.CAVE_AIR || mat == Material.VOID_AIR) { return false; } - Material mat = block.getType(); - if (mat.isAir() || !mat.isSolid()) { + if (mat == Material.LAVA || mat == Material.WATER || mat == Material.FIRE || mat == Material.SOUL_FIRE + || mat == Material.CACTUS || mat == Material.MAGMA_BLOCK || mat == Material.SWEET_BERRY_BUSH + || mat == Material.WITHER_ROSE || mat == Material.POWDER_SNOW) { return false; } - return mat != Material.LAVA && mat != Material.FIRE && mat != Material.SOUL_FIRE - && mat != Material.CACTUS && mat != Material.MAGMA_BLOCK && mat != Material.SWEET_BERRY_BUSH - && mat != Material.WITHER_ROSE && mat != Material.POWDER_SNOW; + try { + return mat.isSolid(); + } catch (Exception | LinkageError e) { + return true; + } } static boolean isReplaceable(Block block) { - if (block == null) { + return block != null && isReplaceableMaterial(block.getType()); + } + + public static boolean isReplaceableMaterial(Material mat) { + if (mat == null) { return false; } - Material mat = block.getType(); - return mat.isAir() || mat == Material.SHORT_GRASS || mat == Material.TALL_GRASS + return mat == Material.AIR || mat == Material.CAVE_AIR || mat == Material.VOID_AIR + || mat == Material.SHORT_GRASS || mat == Material.TALL_GRASS || mat == Material.SNOW || mat == Material.FERN || mat == Material.LARGE_FERN; } diff --git a/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java b/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java index 9201a86..cbd8ef8 100644 --- a/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java +++ b/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java @@ -157,6 +157,50 @@ class ChunkLoaderTest { ChunkLoader expired = timed.withExpiresAt(System.currentTimeMillis() - 1000L); assertTrue(expired.isExpired()); assertEquals("Expirado", expired.timeLeft()); + + assertTrue(loaders.setExpiresAt(loader.id(), 0L)); + ChunkLoader permanent = loaders.byId(loader.id()); + assertNotNull(permanent); + assertEquals(0L, permanent.expiresAt()); + assertEquals("Permanente", permanent.timeLeft()); + assertFalse(permanent.isExpired()); + } + + @Test + void pickEntityTypeReturnsExpectedEntitiesByEnvironment() { + // Nether + assertEquals(org.bukkit.entity.EntityType.ZOMBIFIED_PIGLIN, + ChunkLoaders.pickEntityType(org.bukkit.World.Environment.NETHER, false, 64, 0, 10)); + assertEquals(org.bukkit.entity.EntityType.WITHER_SKELETON, + ChunkLoaders.pickEntityType(org.bukkit.World.Environment.NETHER, false, 64, 0, 50)); + assertEquals(org.bukkit.entity.EntityType.BLAZE, + ChunkLoaders.pickEntityType(org.bukkit.World.Environment.NETHER, false, 64, 0, 75)); + assertEquals(org.bukkit.entity.EntityType.MAGMA_CUBE, + ChunkLoaders.pickEntityType(org.bukkit.World.Environment.NETHER, false, 64, 0, 90)); + assertNull(ChunkLoaders.pickEntityType(org.bukkit.World.Environment.NETHER, false, 64, 12, 10)); + + // End + assertEquals(org.bukkit.entity.EntityType.ENDERMAN, + ChunkLoaders.pickEntityType(org.bukkit.World.Environment.THE_END, false, 64, 15, 50)); + + // Overworld Slime Chunk + assertEquals(org.bukkit.entity.EntityType.SLIME, + ChunkLoaders.pickEntityType(org.bukkit.World.Environment.NORMAL, true, 30, 5, 0)); + + // Overworld Normal Darkness + assertEquals(org.bukkit.entity.EntityType.ZOMBIE, + ChunkLoaders.pickEntityType(org.bukkit.World.Environment.NORMAL, false, 64, 0, 10)); + assertEquals(org.bukkit.entity.EntityType.SKELETON, + ChunkLoaders.pickEntityType(org.bukkit.World.Environment.NORMAL, false, 64, 0, 45)); + assertEquals(org.bukkit.entity.EntityType.CREEPER, + ChunkLoaders.pickEntityType(org.bukkit.World.Environment.NORMAL, false, 64, 0, 75)); + assertEquals(org.bukkit.entity.EntityType.SPIDER, + ChunkLoaders.pickEntityType(org.bukkit.World.Environment.NORMAL, false, 64, 0, 90)); + assertEquals(org.bukkit.entity.EntityType.WITCH, + ChunkLoaders.pickEntityType(org.bukkit.World.Environment.NORMAL, false, 64, 0, 99)); + + // Overworld in Light -> Null + assertNull(ChunkLoaders.pickEntityType(org.bukkit.World.Environment.NORMAL, false, 64, 5, 10)); } @Test diff --git a/src/test/java/dev/marcospaulo/canalhandia/VoidProtectionTest.java b/src/test/java/dev/marcospaulo/canalhandia/VoidProtectionTest.java index 8e8f639..5e97fa5 100644 --- a/src/test/java/dev/marcospaulo/canalhandia/VoidProtectionTest.java +++ b/src/test/java/dev/marcospaulo/canalhandia/VoidProtectionTest.java @@ -32,6 +32,42 @@ class VoidProtectionTest { assertFalse(VoidProtection.rescueToChest(List.of(), null)); } + @Test + void validatesSafeGroundMaterials() { + assertTrue(VoidProtection.isSafeGroundMaterial(org.bukkit.Material.STONE)); + assertTrue(VoidProtection.isSafeGroundMaterial(org.bukkit.Material.GRASS_BLOCK)); + assertTrue(VoidProtection.isSafeGroundMaterial(org.bukkit.Material.DIRT)); + assertTrue(VoidProtection.isSafeGroundMaterial(org.bukkit.Material.OBSIDIAN)); + + assertFalse(VoidProtection.isSafeGroundMaterial(null)); + assertFalse(VoidProtection.isSafeGroundMaterial(org.bukkit.Material.AIR)); + assertFalse(VoidProtection.isSafeGroundMaterial(org.bukkit.Material.LAVA)); + assertFalse(VoidProtection.isSafeGroundMaterial(org.bukkit.Material.FIRE)); + assertFalse(VoidProtection.isSafeGroundMaterial(org.bukkit.Material.SOUL_FIRE)); + assertFalse(VoidProtection.isSafeGroundMaterial(org.bukkit.Material.CACTUS)); + assertFalse(VoidProtection.isSafeGroundMaterial(org.bukkit.Material.MAGMA_BLOCK)); + assertFalse(VoidProtection.isSafeGroundMaterial(org.bukkit.Material.SWEET_BERRY_BUSH)); + assertFalse(VoidProtection.isSafeGroundMaterial(org.bukkit.Material.WITHER_ROSE)); + assertFalse(VoidProtection.isSafeGroundMaterial(org.bukkit.Material.POWDER_SNOW)); + } + + @Test + void validatesReplaceableMaterials() { + assertTrue(VoidProtection.isReplaceableMaterial(org.bukkit.Material.AIR)); + assertTrue(VoidProtection.isReplaceableMaterial(org.bukkit.Material.CAVE_AIR)); + assertTrue(VoidProtection.isReplaceableMaterial(org.bukkit.Material.VOID_AIR)); + assertTrue(VoidProtection.isReplaceableMaterial(org.bukkit.Material.SHORT_GRASS)); + assertTrue(VoidProtection.isReplaceableMaterial(org.bukkit.Material.TALL_GRASS)); + assertTrue(VoidProtection.isReplaceableMaterial(org.bukkit.Material.SNOW)); + assertTrue(VoidProtection.isReplaceableMaterial(org.bukkit.Material.FERN)); + assertTrue(VoidProtection.isReplaceableMaterial(org.bukkit.Material.LARGE_FERN)); + + assertFalse(VoidProtection.isReplaceableMaterial(null)); + assertFalse(VoidProtection.isReplaceableMaterial(org.bukkit.Material.STONE)); + assertFalse(VoidProtection.isReplaceableMaterial(org.bukkit.Material.CHEST)); + assertFalse(VoidProtection.isReplaceableMaterial(org.bukkit.Material.OBSIDIAN)); + } + @Test void nullBlocksAreNeitherSafeNorReplaceable() { assertFalse(VoidProtection.isSafeGround(null));