diff --git a/src/main/java/world/bentobox/caveblock/CaveBlock.java b/src/main/java/world/bentobox/caveblock/CaveBlock.java index 7fa825f..3f813b7 100644 --- a/src/main/java/world/bentobox/caveblock/CaveBlock.java +++ b/src/main/java/world/bentobox/caveblock/CaveBlock.java @@ -70,7 +70,8 @@ public void onEnable() // Register listeners this.registerListener(new CustomHeightLimitations(this)); - this.registerListener(new StructureGenerationListener(this)); + // StructureGenerationListener is registered earlier, in createWorlds(), so it is + // active before the first chunks (including the spawn area) are generated. } @@ -138,6 +139,11 @@ public void allLoaded() { @Override public void createWorlds() { + // Register the structure listener before any world is created. createWorlds() runs + // before onEnable(), and generating a world also generates its spawn-area chunks, so a + // listener registered in onEnable() would miss those first structures (issue #116). + this.registerListener(new StructureGenerationListener(this)); + String worldName = this.settings.getWorldName().toLowerCase(); if (this.getServer().getWorld(worldName) == null) diff --git a/src/main/java/world/bentobox/caveblock/listeners/StructureGenerationListener.java b/src/main/java/world/bentobox/caveblock/listeners/StructureGenerationListener.java index 4d7adc3..0a51aba 100644 --- a/src/main/java/world/bentobox/caveblock/listeners/StructureGenerationListener.java +++ b/src/main/java/world/bentobox/caveblock/listeners/StructureGenerationListener.java @@ -57,9 +57,8 @@ public StructureGenerationListener(CaveBlock addon) { */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onStructureSpawn(AsyncStructureSpawnEvent event) { - World world = event.getWorld(); // Only the CaveBlock overworld uses vanilla structures; nether/end do not. - if (world.getEnvironment() != World.Environment.NORMAL || !addon.inWorld(world)) { + if (!isCaveOverworld(event.getWorld())) { return; } String structureKey = event.getStructure().getKey().getKey(); @@ -81,7 +80,7 @@ public void onStructureSpawn(AsyncStructureSpawnEvent event) { */ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onStructuresLocate(StructuresLocateEvent event) { - if (!addon.inWorld(event.getWorld())) { + if (!isCaveOverworld(event.getWorld())) { return; } List targets = event.getStructures(); @@ -100,6 +99,24 @@ public void onStructuresLocate(StructuresLocateEvent event) { } } + /** + * @param world a world + * @return {@code true} if {@code world} is the CaveBlock overworld. + * + *

Matches on the configured world name rather than {@link CaveBlock#inWorld(World)} + * because structures in the spawn area are generated during {@code createWorlds()}, + * before the addon's island-world reference is assigned. At that point + * {@code inWorld()} would compare against a null world and fail, so the first + * structures would slip through (issue #116).

+ */ + private boolean isCaveOverworld(World world) { + if (world.getEnvironment() != World.Environment.NORMAL) { + return false; + } + String configuredName = addon.getSettings().getWorldName(); + return configuredName != null && world.getName().equalsIgnoreCase(configuredName); + } + /** * @param structureKey the vanilla structure key path, e.g. {@code ancient_city} * @return {@code true} if the config explicitly disables this structure diff --git a/src/test/java/world/bentobox/caveblock/listeners/StructureGenerationListenerTest.java b/src/test/java/world/bentobox/caveblock/listeners/StructureGenerationListenerTest.java index 417070c..1642dbe 100644 --- a/src/test/java/world/bentobox/caveblock/listeners/StructureGenerationListenerTest.java +++ b/src/test/java/world/bentobox/caveblock/listeners/StructureGenerationListenerTest.java @@ -50,7 +50,8 @@ public void setUp() { lenient().when(addon.getSettings()).thenReturn(settings); lenient().when(settings.getGenerateStructures()) .thenReturn(Map.of("ancient_city", false, "trial_chambers", false, "mineshaft", true)); - lenient().when(addon.inWorld(world)).thenReturn(true); + lenient().when(settings.getWorldName()).thenReturn("caveblock_world"); + lenient().when(world.getName()).thenReturn("caveblock_world"); lenient().when(world.getEnvironment()).thenReturn(World.Environment.NORMAL); listener = new StructureGenerationListener(addon); } @@ -92,7 +93,7 @@ void testUnlistedStructureGeneratesNormally() { @Test void testStructureOutsideCaveBlockWorldIsIgnored() { - when(addon.inWorld(world)).thenReturn(false); + when(world.getName()).thenReturn("some_other_world"); AsyncStructureSpawnEvent e = event("ancient_city"); listener.onStructureSpawn(e); verify(e, never()).setCancelled(true); @@ -156,7 +157,7 @@ void testLocateAllEnabledIsUntouched() { @Test void testLocateOutsideCaveBlockWorldIsIgnored() { - when(addon.inWorld(world)).thenReturn(false); + when(world.getName()).thenReturn("some_other_world"); StructuresLocateEvent e = locateEvent("trial_chambers"); listener.onStructuresLocate(e); verify(e, never()).setCancelled(true);