From 34cdd85f97b3d037e0b7ef203d65a426a9f87e56 Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 19 Feb 2026 22:38:12 -0800 Subject: [PATCH] Fix NETHER_PORTAL flag bypass when paper misc.enable-nether is false (#2796) When the server nether (or end) is disabled via paper-global.yml, Paper does not fire PlayerPortalEvent, so BentoBox's EntityPortalEnterEvent handler manually created one and passed it directly to portalProcess(). This bypassed the Bukkit event bus entirely, meaning PortalListener (which checks the NETHER_PORTAL / END_PORTAL protection flags) never ran and /bsb why returned nothing. Fix: post the manually-constructed PlayerPortalEvent through Bukkit.getPluginManager().callEvent() instead. This lets PortalListener (LOW priority) inspect and potentially cancel the event before PlayerTeleportListener.onPlayerPortalEvent (HIGH priority) processes the teleport, restoring correct flag behaviour for both nether and end portals when the server dimension is disabled. Co-Authored-By: Claude Sonnet 4.6 --- .../teleports/PlayerTeleportListener.java | 12 ++++-- .../teleports/PlayerTeleportListenerTest.java | 42 +++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java b/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java index 58a91ded1..b8e6a1817 100644 --- a/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java +++ b/src/main/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListener.java @@ -93,7 +93,9 @@ public void onPlayerPortal(EntityPortalEnterEvent event) // Check again if still in portal if (this.inPortal.contains(uuid)) { - // Create new PlayerPortalEvent + // Create new PlayerPortalEvent and post it through the event bus so that + // protection flag listeners (e.g. PortalListener) can inspect and cancel it + // before BentoBox processes the actual teleportation. PlayerPortalEvent en = new PlayerPortalEvent((Player) entity, event.getLocation(), null, @@ -102,7 +104,7 @@ public void onPlayerPortal(EntityPortalEnterEvent event) false, 0); - this.portalProcess(en, World.Environment.NETHER); + Bukkit.getPluginManager().callEvent(en); } }, 40); return; @@ -110,7 +112,9 @@ public void onPlayerPortal(EntityPortalEnterEvent event) // End portals are instant transfer if (!Bukkit.getAllowEnd() && (type.equals(Material.END_PORTAL) || type.equals(Material.END_GATEWAY))) { - // Create new PlayerPortalEvent + // Create new PlayerPortalEvent and post it through the event bus so that + // protection flag listeners (e.g. PortalListener) can inspect and cancel it + // before BentoBox processes the actual teleportation. PlayerPortalEvent en = new PlayerPortalEvent((Player) entity, event.getLocation(), null, @@ -119,7 +123,7 @@ public void onPlayerPortal(EntityPortalEnterEvent event) false, 0); - this.portalProcess(en, World.Environment.THE_END); + Bukkit.getPluginManager().callEvent(en); } } diff --git a/src/test/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListenerTest.java b/src/test/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListenerTest.java index d70d33edf..472133610 100644 --- a/src/test/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListenerTest.java +++ b/src/test/java/world/bentobox/bentobox/listeners/teleports/PlayerTeleportListenerTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -24,6 +25,7 @@ import org.bukkit.block.Block; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; +import org.bukkit.event.Event; import org.bukkit.event.entity.EntityPortalEnterEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerPortalEvent; @@ -34,6 +36,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import world.bentobox.bentobox.CommonTestSetup; @@ -301,6 +304,33 @@ public void testOnPlayerPortalNetherPortalDisabled() { verify(Bukkit.getScheduler(), times(1)).runTaskLater(eq(plugin), any(Runnable.class), eq(40L)); } + /** + * Test that when nether is disabled on the server, the scheduled task fires a PlayerPortalEvent + * through the Bukkit event bus (so PortalListener flag checks run) instead of calling + * portalProcess() directly. + */ + @Test + public void testOnPlayerPortalNetherPortalDisabledCallsEvent() { + when(Bukkit.getAllowNether()).thenReturn(false); + when(Util.getWorld(location.getWorld())).thenReturn(world); + when(plugin.getIWM().inWorld(world)).thenReturn(true); + when(block.getType()).thenReturn(Material.NETHER_PORTAL); + + EntityPortalEnterEvent e = new EntityPortalEnterEvent(mockPlayer, location); + ptl.onPlayerPortal(e); + + // Capture and execute the scheduled Runnable + ArgumentCaptor runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(Bukkit.getScheduler()).runTaskLater(eq(plugin), runnableCaptor.capture(), eq(40L)); + runnableCaptor.getValue().run(); + + // Verify callEvent was called with a PlayerPortalEvent with NETHER_PORTAL cause + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + verify(pim).callEvent(eventCaptor.capture()); + assertInstanceOf(PlayerPortalEvent.class, eventCaptor.getValue()); + assertEquals(TeleportCause.NETHER_PORTAL, ((PlayerPortalEvent) eventCaptor.getValue()).getCause()); + } + @Test public void testOnPlayerPortalEndPortalDisabled() { // Mock configuration for End disabled @@ -320,6 +350,12 @@ public void testOnPlayerPortalEndPortalDisabled() { // Verify the event behavior indirectly by confirming the origin world was stored assertEquals(location.getWorld(), ptl.getTeleportOrigin().get(mockPlayer.getUniqueId())); + + // Verify callEvent was called with a PlayerPortalEvent with END_PORTAL cause + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + verify(pim).callEvent(eventCaptor.capture()); + assertInstanceOf(PlayerPortalEvent.class, eventCaptor.getValue()); + assertEquals(TeleportCause.END_PORTAL, ((PlayerPortalEvent) eventCaptor.getValue()).getCause()); } @Test @@ -341,6 +377,12 @@ public void testOnPlayerPortalEndGatewayDisabled() { // Verify the event behavior indirectly by confirming the origin world was stored assertEquals(location.getWorld(), ptl.getTeleportOrigin().get(mockPlayer.getUniqueId())); + + // Verify callEvent was called with a PlayerPortalEvent with END_GATEWAY cause + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + verify(pim).callEvent(eventCaptor.capture()); + assertInstanceOf(PlayerPortalEvent.class, eventCaptor.getValue()); + assertEquals(TeleportCause.END_GATEWAY, ((PlayerPortalEvent) eventCaptor.getValue()).getCause()); } @Test