From cc7dc0d593b03fa7670a2549e69666210ecc70a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:15:08 +0000 Subject: [PATCH 01/14] Initial plan From d36840dfa3939fdf7b2767ff3013d8b90fb88e9a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:16:19 +0000 Subject: [PATCH 02/14] Initial plan From b34ca83a796ccc2fa95613ef631829fdf2e11b27 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:16:30 +0000 Subject: [PATCH 03/14] Initial plan From c38c376a8daacdf72dfcab3a12c1fc25c0d78187 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:18:38 +0000 Subject: [PATCH 04/14] Add test cases for nether world border behavior Co-authored-by: tastybento <4407265+tastybento@users.noreply.github.com> --- .../listeners/ShowVirtualWorldBorderTest.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/test/java/world/bentobox/border/listeners/ShowVirtualWorldBorderTest.java b/src/test/java/world/bentobox/border/listeners/ShowVirtualWorldBorderTest.java index c8872e7..131b32e 100644 --- a/src/test/java/world/bentobox/border/listeners/ShowVirtualWorldBorderTest.java +++ b/src/test/java/world/bentobox/border/listeners/ShowVirtualWorldBorderTest.java @@ -2,10 +2,12 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.bukkit.Bukkit; +import org.bukkit.World.Environment; import org.bukkit.WorldBorder; import org.bukkit.entity.Player; import org.bukkit.util.Vector; @@ -123,4 +125,39 @@ public void testHideBorder() { verify(mockPlayer).setWorldBorder(null); } + /** + * Test method for {@link world.bentobox.border.listeners.ShowVirtualWorldBorder#showBorder(org.bukkit.entity.Player, world.bentobox.bentobox.database.objects.Island)}. + * Tests that border is shown when player is in an island nether world. + */ + @Test + public void testShowBorderInIslandNetherWorld() { + // Setup: Player is in a nether environment that IS an island nether world + when(world.getEnvironment()).thenReturn(Environment.NETHER); + when(iwm.isIslandNether(world)).thenReturn(true); + when(addon.getPlugin()).thenReturn(plugin); + + svwb.showBorder(mockPlayer, island); + + // Verify that the border was set (border should show in island nether worlds) + verify(mockPlayer).setWorldBorder(wb); + verify(wb).setSize(200.0D); + } + + /** + * Test method for {@link world.bentobox.border.listeners.ShowVirtualWorldBorder#showBorder(org.bukkit.entity.Player, world.bentobox.bentobox.database.objects.Island)}. + * Tests that border is NOT shown when player is in a non-island nether world. + */ + @Test + public void testShowBorderInNonIslandNetherWorld() { + // Setup: Player is in a nether environment that is NOT an island nether world + when(world.getEnvironment()).thenReturn(Environment.NETHER); + when(iwm.isIslandNether(world)).thenReturn(false); + when(addon.getPlugin()).thenReturn(plugin); + + svwb.showBorder(mockPlayer, island); + + // Verify that the border was NOT set (border should not show in non-island nether worlds) + verify(mockPlayer, never()).setWorldBorder(any()); + } + } From 570d16dd85dd610adb09cf4ebe311ac9f7e87d94 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:19:02 +0000 Subject: [PATCH 05/14] Fix MockedStatic resource leak in ShowBarrierTest Co-authored-by: tastybento <4407265+tastybento@users.noreply.github.com> --- .../world/bentobox/border/listeners/ShowBarrierTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/test/java/world/bentobox/border/listeners/ShowBarrierTest.java b/src/test/java/world/bentobox/border/listeners/ShowBarrierTest.java index 392c851..814284e 100644 --- a/src/test/java/world/bentobox/border/listeners/ShowBarrierTest.java +++ b/src/test/java/world/bentobox/border/listeners/ShowBarrierTest.java @@ -47,6 +47,8 @@ public class ShowBarrierTest extends CommonTestSetup { @Mock private @NonNull Location center; + private MockedStatic mockedUser; + /** * @throws java.lang.Exception */ @@ -77,7 +79,7 @@ public void setUp() throws Exception { when(im.getIslandAt(any(Location.class))).thenReturn(Optional.of(island)); // User - MockedStatic mockedUser = Mockito.mockStatic(User.class, Mockito.RETURNS_MOCKS); + mockedUser = Mockito.mockStatic(User.class, Mockito.RETURNS_MOCKS); mockedUser.when(() -> User.getInstance(any(Player.class))).thenReturn(user); when(user.getMetaData(anyString())).thenReturn(Optional.empty()); when(user.getPlayer()).thenReturn(player); @@ -98,6 +100,9 @@ public void setUp() throws Exception { @Override @AfterEach public void tearDown() throws Exception { + if (mockedUser != null) { + mockedUser.close(); + } super.tearDown(); } From e9763051059499c54653d62e4a4d6dc935940d00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:19:47 +0000 Subject: [PATCH 06/14] Fix MockedStatic resource leak in PlayerListenerTest Co-authored-by: tastybento <4407265+tastybento@users.noreply.github.com> --- .../world/bentobox/border/listeners/PlayerListenerTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java b/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java index 794c024..8b14f04 100644 --- a/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java +++ b/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java @@ -72,6 +72,8 @@ public class PlayerListenerTest extends CommonTestSetup { private Vehicle vehicle; @Mock private GameModeAddon gma; + + private MockedStatic mockedUser; /** @@ -81,7 +83,7 @@ public class PlayerListenerTest extends CommonTestSetup { @BeforeEach public void setUp() throws Exception { super.setUp(); - MockedStatic mockedUser = Mockito.mockStatic(User.class, Mockito.RETURNS_MOCKS); + mockedUser = Mockito.mockStatic(User.class, Mockito.RETURNS_MOCKS); mockedUser.when(() -> User.getInstance(any(Player.class))).thenReturn(user); // Border Shower @@ -142,6 +144,7 @@ public void setUp() throws Exception { @Override @AfterEach public void tearDown() throws Exception { + mockedUser.closeOnDemand(); super.tearDown(); } From 45c0edfa7b613adda23a99686fd3afbe0a570be0 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 17 Jan 2026 07:58:10 -0800 Subject: [PATCH 07/14] Version 4.7.0 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 04c7c0c..318306a 100644 --- a/pom.xml +++ b/pom.xml @@ -46,12 +46,12 @@ 5.10.2 5.11.0 - 1.21.10-R0.1-SNAPSHOT + 1.21.11-R0.1-SNAPSHOT 3.10.0 ${build.version}-SNAPSHOT - 4.6.0 + 4.7.0 -LOCAL BentoBoxWorld_Border From a3fa0090fff92d8b9a5bf45d69886607f2ef2504 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 17 Jan 2026 09:14:44 -0800 Subject: [PATCH 08/14] Bounce items back into the border if they a thrown outside. --- .../java/world/bentobox/border/Border.java | 5 +- .../bentobox/border/PerPlayerBorderProxy.java | 12 +++-- .../java/world/bentobox/border/Settings.java | 20 ++++++++ .../border/listeners/BorderShower.java | 22 ++++++-- .../border/listeners/PlayerListener.java | 51 +++++++++++++++++-- .../border/listeners/ShowBarrier.java | 20 ++++---- ...lWorldBorder.java => ShowWorldBorder.java} | 21 ++++---- src/main/resources/config.yml | 6 ++- .../listeners/ShowVirtualWorldBorderTest.java | 18 +++---- 9 files changed, 131 insertions(+), 44 deletions(-) rename src/main/java/world/bentobox/border/listeners/{ShowVirtualWorldBorder.java => ShowWorldBorder.java} (76%) diff --git a/src/main/java/world/bentobox/border/Border.java b/src/main/java/world/bentobox/border/Border.java index 78d7d65..7c0e7d5 100644 --- a/src/main/java/world/bentobox/border/Border.java +++ b/src/main/java/world/bentobox/border/Border.java @@ -19,7 +19,7 @@ import world.bentobox.border.listeners.BorderShower; import world.bentobox.border.listeners.PlayerListener; import world.bentobox.border.listeners.ShowBarrier; -import world.bentobox.border.listeners.ShowVirtualWorldBorder; +import world.bentobox.border.listeners.ShowWorldBorder; public class Border extends Addon { @@ -71,7 +71,7 @@ public void onDisable() { private BorderShower createBorder() { BorderShower customBorder = new ShowBarrier(this); - BorderShower wbapiBorder = new ShowVirtualWorldBorder(this); + BorderShower wbapiBorder = new ShowWorldBorder(this); return new PerPlayerBorderProxy(this, customBorder, wbapiBorder); } @@ -131,4 +131,5 @@ private void registerPlaceholders() orElse(getSettings().getType()). getCommandLabel()); } + } diff --git a/src/main/java/world/bentobox/border/PerPlayerBorderProxy.java b/src/main/java/world/bentobox/border/PerPlayerBorderProxy.java index f5b2901..b53bfee 100644 --- a/src/main/java/world/bentobox/border/PerPlayerBorderProxy.java +++ b/src/main/java/world/bentobox/border/PerPlayerBorderProxy.java @@ -1,13 +1,15 @@ package world.bentobox.border; +import java.util.Optional; + +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; + import world.bentobox.bentobox.api.metadata.MetaDataValue; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; import world.bentobox.border.listeners.BorderShower; -import java.util.Optional; - public final class PerPlayerBorderProxy implements BorderShower { public static final String BORDER_BORDERTYPE_META_DATA = "Border_bordertype"; @@ -76,11 +78,11 @@ private BorderType getDefaultBorderType() { } @Override - public void teleportPlayer(Player player) { + public void teleportEntity(Border addon, Entity player) { if (getBorderType(User.getInstance(player)) == BorderType.BARRIER) { - customBorder.teleportPlayer(player); + customBorder.teleportEntity(addon, player); } else { - vanillaBorder.teleportPlayer(player); + vanillaBorder.teleportEntity(addon, player); } } diff --git a/src/main/java/world/bentobox/border/Settings.java b/src/main/java/world/bentobox/border/Settings.java index 5fd7ed8..c9d03b5 100644 --- a/src/main/java/world/bentobox/border/Settings.java +++ b/src/main/java/world/bentobox/border/Settings.java @@ -29,6 +29,12 @@ public class Settings implements ConfigObject { @ConfigEntry(path = "type") private BorderType type = BorderType.VANILLA; + @ConfigComment("") + @ConfigComment("Bounce items back inside the border if they are thrown by a player.") + @ConfigComment("Without this, items can be thrown outside the border.") + @ConfigEntry(path = "bounce-back") + private boolean bounceBack = true; + @ConfigComment("") @ConfigComment("Teleport players back inside the border if they somehow get outside.") @ConfigComment("This will teleport players back inside if they toggle the border with a command.") @@ -192,4 +198,18 @@ public boolean isReturnTeleportBlock() { public void setReturnTeleportBlock(boolean returnTeleportBlock) { this.returnTeleportBlock = returnTeleportBlock; } + + /** + * @return the bounceBack + */ + public boolean isBounceBack() { + return bounceBack; + } + + /** + * @param bounceBack the bounceBack to set + */ + public void setBounceBack(boolean bounceBack) { + this.bounceBack = bounceBack; + } } diff --git a/src/main/java/world/bentobox/border/listeners/BorderShower.java b/src/main/java/world/bentobox/border/listeners/BorderShower.java index e42d5da..6c25d47 100644 --- a/src/main/java/world/bentobox/border/listeners/BorderShower.java +++ b/src/main/java/world/bentobox/border/listeners/BorderShower.java @@ -1,9 +1,15 @@ package world.bentobox.border.listeners; +import org.bukkit.Location; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import org.bukkit.util.Vector; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.util.Util; +import world.bentobox.border.Border; /** * A border shower class @@ -44,9 +50,19 @@ public default void refreshView(User user, Island island){ } /** - * Teleports player back within the island space they are in - * @param player player + * Teleports an entity, typically a player back within the island space they are in + * @param entity entity */ - public void teleportPlayer(Player player); + public default void teleportEntity(Border addon, Entity entity) { + addon.getIslands().getIslandAt(entity.getLocation()).ifPresent(i -> { + Vector unitVector = i.getCenter().toVector().subtract(entity.getLocation().toVector()).normalize() + .multiply(new Vector(1, 0, 1)); + // Get distance from border + Location to = entity.getLocation().toVector().add(unitVector).toLocation(entity.getWorld()); + to.setPitch(entity.getLocation().getPitch()); + to.setYaw(entity.getLocation().getYaw()); + Util.teleportAsync(entity, to, TeleportCause.PLUGIN); + }); + } } diff --git a/src/main/java/world/bentobox/border/listeners/PlayerListener.java b/src/main/java/world/bentobox/border/listeners/PlayerListener.java index ebcc91f..dc6cb1d 100644 --- a/src/main/java/world/bentobox/border/listeners/PlayerListener.java +++ b/src/main/java/world/bentobox/border/listeners/PlayerListener.java @@ -14,6 +14,7 @@ import org.bukkit.Material; import org.bukkit.block.BlockFace; import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -22,6 +23,7 @@ import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.bukkit.event.entity.EntityDismountEvent; import org.bukkit.event.entity.EntityMountEvent; +import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerMoveEvent; import org.bukkit.event.player.PlayerQuitEvent; @@ -29,16 +31,17 @@ import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.event.vehicle.VehicleMoveEvent; +import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; import org.bukkit.util.NumberConversions; import org.bukkit.util.RayTraceResult; import org.bukkit.util.Vector; -import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.events.island.IslandProtectionRangeChangeEvent; import world.bentobox.bentobox.api.flags.Flag; import world.bentobox.bentobox.api.metadata.MetaDataValue; import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.util.Util; import world.bentobox.border.Border; import world.bentobox.border.BorderType; @@ -111,7 +114,7 @@ public void onPlayerDamage(EntityDamageEvent e) { } Material type = p.getLocation().getBlock().getRelative(BlockFace.DOWN).getType(); if (type == Material.AIR) { - ((BorderShower) show).teleportPlayer(p); + ((BorderShower) show).teleportEntity(addon, p); e.setCancelled(true); } } @@ -338,8 +341,8 @@ public void onVehicleMove(VehicleMoveEvent e) { // Remove head movement if (!e.getFrom().toVector().equals(e.getTo().toVector())) { e.getVehicle().getPassengers().stream().filter(Player.class::isInstance).map(Player.class::cast) - .filter(this::isOn).forEach(p -> addon.getIslands().getIslandAt(p.getLocation()) - .ifPresent(i -> show.refreshView(User.getInstance(p), i))); + .filter(this::isOn).forEach(p -> addon.getIslands().getIslandAt(p.getLocation()) + .ifPresent(i -> show.refreshView(User.getInstance(p), i))); } } @@ -357,4 +360,44 @@ public void onProtectionRangeChange(IslandProtectionRangeChangeEvent e) { } }); } + + /** + * Bounces items back to inside the barrier if thrown by a player + * @param event event + */ + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onItemDrop(PlayerDropItemEvent event) { + if (addon.getSettings().isBounceBack() + && addon.inGameWorld(event.getPlayer().getWorld()) + && isOn(event.getPlayer()) + ) { + // Get this island + addon.getIslands().getIslandAt(event.getPlayer().getLocation()).ifPresent(is -> trackItem(event.getItemDrop(), is)); + } + } + + private void trackItem(Item item, Island island) { + new BukkitRunnable() { + int ticksActive = 0; + + @Override + public void run() { + // Stop tracking if the item is picked up, despawned, or 20 seconds have passed + if (!item.isValid() || ticksActive > 400) { + this.cancel(); + return; + } + + Location loc = item.getLocation(); + // Check if the item is going outside the border + if (!island.onIsland(loc)) { + // Reverse the direction + item.setVelocity(item.getVelocity().multiply(-0.5)); + this.cancel(); + } + ticksActive++; + } + }.runTaskTimer(addon.getPlugin(), 1L, 2L); // Check every 2 ticks (0.1 seconds) + } + } diff --git a/src/main/java/world/bentobox/border/listeners/ShowBarrier.java b/src/main/java/world/bentobox/border/listeners/ShowBarrier.java index f235c4d..05673fb 100644 --- a/src/main/java/world/bentobox/border/listeners/ShowBarrier.java +++ b/src/main/java/world/bentobox/border/listeners/ShowBarrier.java @@ -12,13 +12,13 @@ import org.bukkit.Material; import org.bukkit.Particle; import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.util.Vector; import com.google.common.base.Enums; -import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.metadata.MetaDataValue; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; @@ -136,7 +136,7 @@ private void showPlayer(Player player, int i, int j, int k, boolean max) { if (addon.getSettings().isUseBarrierBlocks() && player.getLocation().getBlockX() == i && player.getLocation().getBlockZ() == k) { - teleportPlayer(player); + teleportEntity(player); } Location l = new Location(player.getWorld(), i, j, k); @@ -157,17 +157,17 @@ private void showPlayer(Player player, int i, int j, int k, boolean max) { /** * Teleport player back within the island space they are in - * @param p player + * @param entity player or entity */ - public void teleportPlayer(Player p) { - addon.getIslands().getIslandAt(p.getLocation()).ifPresent(i -> { - Vector unitVector = i.getCenter().toVector().subtract(p.getLocation().toVector()).normalize() + public void teleportEntity(Entity entity) { + addon.getIslands().getIslandAt(entity.getLocation()).ifPresent(i -> { + Vector unitVector = i.getCenter().toVector().subtract(entity.getLocation().toVector()).normalize() .multiply(new Vector(1, 0, 1)); // Get distance from border - Location to = p.getLocation().toVector().add(unitVector).toLocation(p.getWorld()); - to.setPitch(p.getLocation().getPitch()); - to.setYaw(p.getLocation().getYaw()); - Util.teleportAsync(p, to, TeleportCause.PLUGIN); + Location to = entity.getLocation().toVector().add(unitVector).toLocation(entity.getWorld()); + to.setPitch(entity.getLocation().getPitch()); + to.setYaw(entity.getLocation().getYaw()); + Util.teleportAsync(entity, to, TeleportCause.PLUGIN); }); } diff --git a/src/main/java/world/bentobox/border/listeners/ShowVirtualWorldBorder.java b/src/main/java/world/bentobox/border/listeners/ShowWorldBorder.java similarity index 76% rename from src/main/java/world/bentobox/border/listeners/ShowVirtualWorldBorder.java rename to src/main/java/world/bentobox/border/listeners/ShowWorldBorder.java index 45f9359..8d95853 100644 --- a/src/main/java/world/bentobox/border/listeners/ShowVirtualWorldBorder.java +++ b/src/main/java/world/bentobox/border/listeners/ShowWorldBorder.java @@ -6,6 +6,7 @@ import org.bukkit.Location; import org.bukkit.World.Environment; import org.bukkit.WorldBorder; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.util.Vector; @@ -21,11 +22,11 @@ * @author tastybento * */ -public class ShowVirtualWorldBorder implements BorderShower { +public class ShowWorldBorder implements BorderShower { private final Border addon; - public ShowVirtualWorldBorder(Border addon) { + public ShowWorldBorder(Border addon) { this.addon = addon; } @@ -55,17 +56,17 @@ public void hideBorder(User user) { /** * Teleport player back within the island space they are in - * @param p player + * @param entity player */ - public void teleportPlayer(Player p) { - addon.getIslands().getIslandAt(p.getLocation()).ifPresent(i -> { - Vector unitVector = i.getCenter().toVector().subtract(p.getLocation().toVector()).normalize() + public void teleportEntity(Entity entity) { + addon.getIslands().getIslandAt(entity.getLocation()).ifPresent(i -> { + Vector unitVector = i.getCenter().toVector().subtract(entity.getLocation().toVector()).normalize() .multiply(new Vector(1, 0, 1)); // Get distance from border - Location to = p.getLocation().toVector().add(unitVector).toLocation(p.getWorld()); - to.setPitch(p.getLocation().getPitch()); - to.setYaw(p.getLocation().getYaw()); - Util.teleportAsync(p, to, TeleportCause.PLUGIN); + Location to = entity.getLocation().toVector().add(unitVector).toLocation(entity.getWorld()); + to.setPitch(entity.getLocation().getPitch()); + to.setYaw(entity.getLocation().getYaw()); + Util.teleportAsync(entity, to, TeleportCause.PLUGIN); }); } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index b19ac3b..7e4a657 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,4 +1,4 @@ -# Border addon configuration file {$version} +# Border addon configuration file # See the documentation at https://docs.bentobox.world/en/latest/addons/Border/ # # This list stores GameModes in which Border addon should not work. @@ -13,6 +13,10 @@ disabled-gamemodes: [] # then this setting will be used. type: VANILLA # +# Bounce items back inside the border if they are thrown by a player. +# Without this, items can be thrown outside the border. +bounce-back: true +# # Teleport players back inside the border if they somehow get outside. # This will teleport players back inside if they toggle the border with a command. return-teleport: true diff --git a/src/test/java/world/bentobox/border/listeners/ShowVirtualWorldBorderTest.java b/src/test/java/world/bentobox/border/listeners/ShowVirtualWorldBorderTest.java index 131b32e..57b5d63 100644 --- a/src/test/java/world/bentobox/border/listeners/ShowVirtualWorldBorderTest.java +++ b/src/test/java/world/bentobox/border/listeners/ShowVirtualWorldBorderTest.java @@ -32,7 +32,7 @@ public class ShowVirtualWorldBorderTest extends CommonTestSetup { @Mock private Border addon; private Settings settings; - private ShowVirtualWorldBorder svwb; + private ShowWorldBorder svwb; @Mock private @NonNull User user; @Mock @@ -66,7 +66,7 @@ public void setUp() throws Exception { // Bukkit mockedBukkit.when(() -> Bukkit.createWorldBorder()).thenReturn(wb); - svwb = new ShowVirtualWorldBorder(addon); + svwb = new ShowWorldBorder(addon); } @Override @@ -76,7 +76,7 @@ public void tearDown() throws Exception { } /** - * Test method for {@link world.bentobox.border.listeners.ShowVirtualWorldBorder#ShowVirtualWorldBorder(world.bentobox.border.Border)}. + * Test method for {@link world.bentobox.border.listeners.ShowWorldBorder#ShowVirtualWorldBorder(world.bentobox.border.Border)}. */ @Test public void testShowVirtualWorldBorder() { @@ -84,7 +84,7 @@ public void testShowVirtualWorldBorder() { } /** - * Test method for {@link world.bentobox.border.listeners.ShowVirtualWorldBorder#showBorder(org.bukkit.entity.Player, world.bentobox.bentobox.database.objects.Island)}. + * Test method for {@link world.bentobox.border.listeners.ShowWorldBorder#showBorder(org.bukkit.entity.Player, world.bentobox.bentobox.database.objects.Island)}. */ @Test public void testShowBorder() { @@ -94,7 +94,7 @@ public void testShowBorder() { } /** - * Test method for {@link world.bentobox.border.listeners.ShowVirtualWorldBorder#showBorder(org.bukkit.entity.Player, world.bentobox.bentobox.database.objects.Island)}. + * Test method for {@link world.bentobox.border.listeners.ShowWorldBorder#showBorder(org.bukkit.entity.Player, world.bentobox.bentobox.database.objects.Island)}. */ @Test public void testShowBorderWithOffset() { @@ -105,7 +105,7 @@ public void testShowBorderWithOffset() { } /** - * Test method for {@link world.bentobox.border.listeners.ShowVirtualWorldBorder#showBorder(org.bukkit.entity.Player, world.bentobox.bentobox.database.objects.Island)}. + * Test method for {@link world.bentobox.border.listeners.ShowWorldBorder#showBorder(org.bukkit.entity.Player, world.bentobox.bentobox.database.objects.Island)}. */ @Test public void testShowBorderWithLargeOffset() { @@ -116,7 +116,7 @@ public void testShowBorderWithLargeOffset() { } /** - * Test method for {@link world.bentobox.border.listeners.ShowVirtualWorldBorder#hideBorder(world.bentobox.bentobox.api.user.User)}. + * Test method for {@link world.bentobox.border.listeners.ShowWorldBorder#hideBorder(world.bentobox.bentobox.api.user.User)}. */ @Test public void testHideBorder() { @@ -126,7 +126,7 @@ public void testHideBorder() { } /** - * Test method for {@link world.bentobox.border.listeners.ShowVirtualWorldBorder#showBorder(org.bukkit.entity.Player, world.bentobox.bentobox.database.objects.Island)}. + * Test method for {@link world.bentobox.border.listeners.ShowWorldBorder#showBorder(org.bukkit.entity.Player, world.bentobox.bentobox.database.objects.Island)}. * Tests that border is shown when player is in an island nether world. */ @Test @@ -144,7 +144,7 @@ public void testShowBorderInIslandNetherWorld() { } /** - * Test method for {@link world.bentobox.border.listeners.ShowVirtualWorldBorder#showBorder(org.bukkit.entity.Player, world.bentobox.bentobox.database.objects.Island)}. + * Test method for {@link world.bentobox.border.listeners.ShowWorldBorder#showBorder(org.bukkit.entity.Player, world.bentobox.bentobox.database.objects.Island)}. * Tests that border is NOT shown when player is in a non-island nether world. */ @Test From 8d88d9af301418d095b26d6ceb13b20ff81deae9 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 21 Jan 2026 19:17:55 -0800 Subject: [PATCH 09/14] Add death drop protection. --- .../border/listeners/PlayerListener.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/world/bentobox/border/listeners/PlayerListener.java b/src/main/java/world/bentobox/border/listeners/PlayerListener.java index dc6cb1d..f4e31f7 100644 --- a/src/main/java/world/bentobox/border/listeners/PlayerListener.java +++ b/src/main/java/world/bentobox/border/listeners/PlayerListener.java @@ -23,6 +23,7 @@ import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import org.bukkit.event.entity.EntityDismountEvent; import org.bukkit.event.entity.EntityMountEvent; +import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerMoveEvent; @@ -376,6 +377,23 @@ && isOn(event.getPlayer()) } } + /** + * Bounces items back to inside the barrier if dropped when a player dies + * @param event event + */ + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onPlayerDeath(PlayerDeathEvent event) { + if (addon.getSettings().isBounceBack() + && addon.inGameWorld(event.getPlayer().getWorld()) + && isOn(event.getPlayer())) { + // Get this island + addon.getIslands().getIslandAt(event.getPlayer().getLocation()).ifPresent(is -> { + event.getDrops().forEach(item -> trackItem(event.getPlayer().getWorld().dropItemNaturally(event.getPlayer().getLocation(), item), is)); + event.getDrops().clear(); // We handled them + }); + } + } + private void trackItem(Item item, Island island) { new BukkitRunnable() { int ticksActive = 0; From a2cee10726fce97bd4aac80b1729c37470032841 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 16:55:23 +0000 Subject: [PATCH 10/14] Initial plan From c5e323e001a52191068858c48ef4f03f63f9b295 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 17:01:50 +0000 Subject: [PATCH 11/14] Fix border teleportation bypass when player is pushed completely outside island When a player was pushed by a piston over the barrier and fell below -64 height, getIslandAt() returned Optional.empty() because the player was completely outside any island space. This caused outsideCheck() to return false (skipping the border check) and the backtrack logic to never execute. Fix: 1. outsideCheck() now also returns true when getIslandAt returns empty (player not on any island but in game world) 2. Backtrack logic falls back to getIsland(World, User) to find the player's own island when getIslandAt returns empty Co-authored-by: tastybento <4407265+tastybento@users.noreply.github.com> --- .../border/listeners/PlayerListener.java | 14 ++++++++++--- .../border/listeners/PlayerListenerTest.java | 21 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/main/java/world/bentobox/border/listeners/PlayerListener.java b/src/main/java/world/bentobox/border/listeners/PlayerListener.java index f4e31f7..8b4f3d1 100644 --- a/src/main/java/world/bentobox/border/listeners/PlayerListener.java +++ b/src/main/java/world/bentobox/border/listeners/PlayerListener.java @@ -208,8 +208,13 @@ public void onPlayerLeaveIsland(PlayerMoveEvent e) { Util.teleportAsync(p, from).thenRun(() -> inTeleport.remove(p.getUniqueId())); return; } - // Backtrack - addon.getIslands().getIslandAt(p.getLocation()).ifPresent(i -> { + // Backtrack - try to find island at current location, or fall back to the player's own island + Optional optionalIsland = addon.getIslands().getIslandAt(p.getLocation()); + if (optionalIsland.isEmpty()) { + optionalIsland = Optional + .ofNullable(addon.getIslands().getIsland(p.getWorld(), User.getInstance(p))); + } + optionalIsland.ifPresent(i -> { Vector unitVector = i.getProtectionCenter().toVector().subtract(p.getLocation().toVector()).normalize() .multiply(new Vector(1,0,1)); if (unitVector.lengthSquared() <= 0D) { @@ -263,7 +268,10 @@ private boolean outsideCheck(Player player, Location from, Location to) { || !user.getMetaData(BorderShower.BORDER_STATE_META_DATA).map(MetaDataValue::asBoolean).orElse(addon.getSettings().isShowByDefault())) { return false; } - return addon.getIslands().getIslandAt(to).filter(i -> !i.onIsland(to)).isPresent(); + Optional islandAt = addon.getIslands().getIslandAt(to); + // Player is outside if they are on an island but not within its protection zone, + // or if they are not on any island at all (e.g., pushed out by piston) + return islandAt.isEmpty() || islandAt.filter(i -> !i.onIsland(to)).isPresent(); } /** diff --git a/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java b/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java index 8b14f04..225a8bb 100644 --- a/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java +++ b/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java @@ -317,6 +317,27 @@ public void testOnPlayerLeaveIslandReturnTeleportWaaayOutsideIsland() { mockedUtil.verify(() -> Util.teleportAsync(any(), any()), times(2)); } + /** + * Test method for {@link world.bentobox.border.listeners.PlayerListener#onPlayerLeaveIsland(org.bukkit.event.player.PlayerMoveEvent)}. + * Tests the scenario where a player is pushed completely outside any island (e.g., by a piston) + * and getIslandAt returns empty. The plugin should fall back to the player's own island. + */ + @Test + public void testOnPlayerLeaveIslandReturnTeleportCompletelyOutsideAnyIsland() { + // Player is completely outside any island - getIslandAt returns empty + when(im.getIslandAt(any())).thenReturn(Optional.empty()); + when(im.getProtectedIslandAt(any())).thenReturn(Optional.empty()); + // But the player has their own island + when(im.getIsland(any(), any(User.class))).thenReturn(island); + when(island.onIsland(any())).thenReturn(false); + settings.setReturnTeleport(true); + PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); + pl.onPlayerLeaveIsland(event); + assertFalse(event.isCancelled()); + // Verify teleportAsync was called to teleport the player back + mockedUtil.verify(() -> Util.teleportAsync(any(), any()), times(2)); + } + /** * Test method for {@link world.bentobox.border.listeners.PlayerListener#onPlayerMove(org.bukkit.event.player.PlayerMoveEvent)}. */ From 8d3bbc6d11f1f6c052c041b3482eed6ce95fd72e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Feb 2026 17:03:57 +0000 Subject: [PATCH 12/14] Rename test method for clarity Co-authored-by: tastybento <4407265+tastybento@users.noreply.github.com> --- .../world/bentobox/border/listeners/PlayerListenerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java b/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java index 225a8bb..e505e1f 100644 --- a/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java +++ b/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java @@ -323,7 +323,7 @@ public void testOnPlayerLeaveIslandReturnTeleportWaaayOutsideIsland() { * and getIslandAt returns empty. The plugin should fall back to the player's own island. */ @Test - public void testOnPlayerLeaveIslandReturnTeleportCompletelyOutsideAnyIsland() { + public void testOnPlayerLeaveIslandTeleportsWhenCompletelyOutsideIsland() { // Player is completely outside any island - getIslandAt returns empty when(im.getIslandAt(any())).thenReturn(Optional.empty()); when(im.getProtectedIslandAt(any())).thenReturn(Optional.empty()); From ad06c23e7b9012aa70e8975cfd4bc9209d976963 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 16 Feb 2026 08:56:49 -0800 Subject: [PATCH 13/14] Add border color configuration and adjust world border size based on color --- pom.xml | 5 ++++ .../java/world/bentobox/border/Settings.java | 25 ++++++++++++++++++- .../border/listeners/ShowWorldBorder.java | 12 +++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 318306a..1b7e25d 100644 --- a/pom.xml +++ b/pom.xml @@ -210,6 +210,11 @@ 3.14.1 ${java.version} + true + + -J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + -J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + diff --git a/src/main/java/world/bentobox/border/Settings.java b/src/main/java/world/bentobox/border/Settings.java index c9d03b5..c53a802 100644 --- a/src/main/java/world/bentobox/border/Settings.java +++ b/src/main/java/world/bentobox/border/Settings.java @@ -22,13 +22,22 @@ public class Settings implements ConfigObject { private Set disabledGameModes = new HashSet<>(); @ConfigComment("") - @ConfigComment("Border type. Options are VANILLA, which uses the vanillia-style board or BARRIER,") + @ConfigComment("Border type. Options are VANILLA, which uses the vanilla-style boarder or BARRIER,") @ConfigComment("which uses particles and barrier blocks. If players have permission to use the barrier type") @ConfigComment("they may override this option. If they do not have permission or lose the permission") @ConfigComment("then this setting will be used.") @ConfigEntry(path = "type") private BorderType type = BorderType.VANILLA; + public enum BorderColor { + RED, GREEN, BLUE + } + @ConfigComment("") + @ConfigComment("Vanilla border color. Only applies if the border type is VANILLA.") + @ConfigComment("Selection is RED, GREEN, BLUE.") + @ConfigEntry(path = "color") + private BorderColor color = BorderColor.BLUE; + @ConfigComment("") @ConfigComment("Bounce items back inside the border if they are thrown by a player.") @ConfigComment("Without this, items can be thrown outside the border.") @@ -212,4 +221,18 @@ public boolean isBounceBack() { public void setBounceBack(boolean bounceBack) { this.bounceBack = bounceBack; } + + /** + * @return the color + */ + public BorderColor getColor() { + return color; + } + + /* * + * @param color the color to set + */ + public void setColor(BorderColor color) { + this.color = color; + } } diff --git a/src/main/java/world/bentobox/border/listeners/ShowWorldBorder.java b/src/main/java/world/bentobox/border/listeners/ShowWorldBorder.java index 8d95853..88204b9 100644 --- a/src/main/java/world/bentobox/border/listeners/ShowWorldBorder.java +++ b/src/main/java/world/bentobox/border/listeners/ShowWorldBorder.java @@ -11,6 +11,7 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.util.Vector; +import org.jetbrains.annotations.Range; import world.bentobox.bentobox.api.metadata.MetaDataValue; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; @@ -24,6 +25,7 @@ */ public class ShowWorldBorder implements BorderShower { + private static final long MAX_TICKS = 107374182; private final Border addon; public ShowWorldBorder(Border addon) { @@ -46,6 +48,16 @@ public void showBorder(Player player, Island island) { double size = Math.min(island.getRange() * 2D, (island.getProtectionRange() + addon.getSettings().getBarrierOffset()) * 2D); wb.setSize(size); wb.setWarningDistance(0); + switch(addon.getSettings().getColor()) { + case RED: + wb.changeSize(wb.getSize() - 0.1, MAX_TICKS); + break; + case GREEN: + wb.changeSize(wb.getSize() + 0.1, MAX_TICKS); + break; + case BLUE: + break; + } player.setWorldBorder(wb); } From 282b0c9576100aa016761a057370cfc3b66bb3c8 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 16 Feb 2026 08:57:44 -0800 Subject: [PATCH 14/14] Add border color configuration and adjust world border size based on color --- .../java/world/bentobox/border/listeners/ShowWorldBorder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/world/bentobox/border/listeners/ShowWorldBorder.java b/src/main/java/world/bentobox/border/listeners/ShowWorldBorder.java index 88204b9..4ac93e9 100644 --- a/src/main/java/world/bentobox/border/listeners/ShowWorldBorder.java +++ b/src/main/java/world/bentobox/border/listeners/ShowWorldBorder.java @@ -11,7 +11,6 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.util.Vector; -import org.jetbrains.annotations.Range; import world.bentobox.bentobox.api.metadata.MetaDataValue; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island;