From 2a63a04e4fbb4fb9d6c04da44d936a2cc437df69 Mon Sep 17 00:00:00 2001 From: Marcos Paulo Date: Thu, 20 Aug 2026 20:20:59 -0300 Subject: [PATCH] fix(chunkloader): support trapdoor ceilings and creeper-specific spawning on simulated platforms --- .../marcospaulo/canalhandia/ChunkLoaders.java | 28 ++++++++++++++++--- .../canalhandia/ChunkLoaderTest.java | 10 +++++++ 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java index 8845ab5..d451573 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java +++ b/src/main/java/dev/marcospaulo/canalhandia/ChunkLoaders.java @@ -465,7 +465,7 @@ final class ChunkLoaders { int baseZ = loader.chunkZ() << 4; ThreadLocalRandom rnd = ThreadLocalRandom.current(); - for (int i = 0; i < 2; i++) { + for (int i = 0; i < 4; i++) { int rx = baseX + rnd.nextInt(16); int rz = baseZ + rnd.nextInt(16); int topY = Math.max(w.getMinHeight() + 2, Math.min(w.getMaxHeight() - 2, loader.y() + rnd.nextInt(-24, 25))); @@ -474,15 +474,30 @@ final class ChunkLoaders { Block space = w.getBlockAt(rx, topY, rz); Block spaceAbove = w.getBlockAt(rx, topY + 1, rz); - if (!ground.getType().isSolid() || !space.getType().isAir() || !spaceAbove.getType().isAir()) { + if (!ground.getType().isSolid() || ground.isLiquid() || ground.getType() == Material.LAVA || ground.getType() == Material.WATER) { continue; } - if (ground.isLiquid() || ground.getType() == Material.LAVA || ground.getType() == Material.WATER) { + if (!space.getType().isAir()) { + continue; + } + + boolean isAirAbove = spaceAbove.getType().isAir(); + boolean isTrapdoorAbove = isTrapdoor(spaceAbove.getType()); + if (!isAirAbove && !isTrapdoorAbove) { continue; } int light = space.getLightLevel(); - EntityType toSpawn = pickEntityType(w, space, light); + EntityType toSpawn; + if (isTrapdoorAbove) { + if (light > 0) { + continue; + } + toSpawn = EntityType.CREEPER; + } else { + toSpawn = pickEntityType(w, space, light); + } + if (toSpawn == null) { continue; } @@ -497,6 +512,11 @@ final class ChunkLoaders { } } + static boolean isTrapdoor(Material mat) { + if (mat == null) return false; + return mat.name().endsWith("_TRAPDOOR"); + } + static EntityType pickEntityType(World w, Block space, int light) { boolean isSlime = false; try { diff --git a/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java b/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java index f2902a6..3786dc9 100644 --- a/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java +++ b/src/test/java/dev/marcospaulo/canalhandia/ChunkLoaderTest.java @@ -212,6 +212,16 @@ class ChunkLoaderTest { assertTrue(loader.timeLeft().contains("h")); } + @Test + void recognizesTrapdoorsCorrectly() { + assertTrue(ChunkLoaders.isTrapdoor(org.bukkit.Material.OAK_TRAPDOOR)); + assertTrue(ChunkLoaders.isTrapdoor(org.bukkit.Material.IRON_TRAPDOOR)); + assertTrue(ChunkLoaders.isTrapdoor(org.bukkit.Material.SPRUCE_TRAPDOOR)); + assertFalse(ChunkLoaders.isTrapdoor(org.bukkit.Material.AIR)); + assertFalse(ChunkLoaders.isTrapdoor(org.bukkit.Material.STONE)); + assertFalse(ChunkLoaders.isTrapdoor(null)); + } + @Test void simulationSafelyNoopsWithNullPlugin() { loaders.startSimulation();