Skip to content

Commit 8ad4bbb

Browse files
authored
Merge pull request #2890 from BentoBoxWorld/copilot/fix-players-fall-into-void
Add safe-spot checking to island-based homeTeleportAsync
2 parents ad0474b + d8424de commit 8ad4bbb

File tree

2 files changed

+104
-12
lines changed

2 files changed

+104
-12
lines changed

src/main/java/world/bentobox/bentobox/managers/IslandsManager.java

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,17 +1885,41 @@ public CompletableFuture<Void> homeTeleportAsync(Island island, User user, boole
18851885
Location loc = island.getHome("");
18861886
user.sendMessage("commands.island.go.teleport");
18871887
goingHome.add(user.getUniqueId());
1888-
readyPlayer(user.getPlayer());
1889-
return Util.teleportAsync(Objects.requireNonNull(user.getPlayer()), loc).thenAccept(b -> {
1890-
// Only run the commands if the player is successfully teleported
1891-
if (b != null && b) {
1892-
teleported(island.getWorld(), user, "", newIsland, island);
1893-
this.setPrimaryIsland(user.getUniqueId(), island);
1888+
Player player = Objects.requireNonNull(user.getPlayer());
1889+
readyPlayer(player);
1890+
CompletableFuture<Void> result = new CompletableFuture<>();
1891+
isSafeLocationAsync(loc).thenAccept(safe -> {
1892+
if (Boolean.TRUE.equals(safe)) {
1893+
Util.teleportAsync(player, loc).thenAccept(b -> {
1894+
if (b != null && b) {
1895+
teleported(island.getWorld(), user, "", newIsland, island);
1896+
this.setPrimaryIsland(user.getUniqueId(), island);
1897+
} else {
1898+
goingHome.remove(user.getUniqueId());
1899+
}
1900+
result.complete(null);
1901+
});
18941902
} else {
1895-
// Remove from mid-teleport set
1896-
goingHome.remove(user.getUniqueId());
1903+
// Location is not safe, use SafeSpotTeleport to find a safe spot
1904+
new SafeSpotTeleport.Builder(plugin).entity(player).island(island)
1905+
.thenRun(() -> {
1906+
teleported(island.getWorld(), user, "", newIsland, island);
1907+
this.setPrimaryIsland(user.getUniqueId(), island);
1908+
})
1909+
.ifFail(() -> {
1910+
plugin.logError(user.getName()
1911+
+ " could not be teleported to home on island "
1912+
+ island.getCenter());
1913+
goingHome.remove(user.getUniqueId());
1914+
}).buildFuture().thenAccept(b -> result.complete(null));
18971915
}
1916+
}).exceptionally(e -> {
1917+
plugin.logError("Error checking safe location for " + user.getName() + ": " + e.getMessage());
1918+
goingHome.remove(user.getUniqueId());
1919+
result.complete(null);
1920+
return null;
18981921
});
1922+
return result;
18991923
}
19001924

19011925
}

src/test/java/world/bentobox/bentobox/managers/IslandsManagerTest.java

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,6 +1504,29 @@ void testsetMaxHomes() {
15041504
verify(island).setMaxHomes(40);
15051505
}
15061506

1507+
/**
1508+
* Helper to set up a mock home location with safe blocks for homeTeleportAsync tests.
1509+
* Returns the mocked home Location with block/chunk/world configured.
1510+
*/
1511+
private Location setupSafeHomeLoc() {
1512+
Location homeLoc = mock(Location.class);
1513+
when(homeLoc.getWorld()).thenReturn(world);
1514+
Block homeBlock = mock(Block.class);
1515+
Block homeGround = mock(Block.class);
1516+
Block homeSpace2 = mock(Block.class);
1517+
when(homeLoc.getBlock()).thenReturn(homeBlock);
1518+
when(homeBlock.getRelative(BlockFace.DOWN)).thenReturn(homeGround);
1519+
when(homeBlock.getRelative(BlockFace.UP)).thenReturn(homeSpace2);
1520+
// Safe location: solid ground, air above
1521+
when(homeGround.getType()).thenReturn(Material.STONE);
1522+
when(homeBlock.getType()).thenReturn(Material.AIR);
1523+
when(homeSpace2.getType()).thenReturn(Material.AIR);
1524+
// Mock chunk loading
1525+
Chunk homeChunk = mock(Chunk.class);
1526+
mockedUtil.when(() -> Util.getChunkAtAsync(homeLoc)).thenReturn(CompletableFuture.completedFuture(homeChunk));
1527+
return homeLoc;
1528+
}
1529+
15071530
/**
15081531
* Test method for
15091532
* {@link world.bentobox.bentobox.managers.IslandsManager#homeTeleportAsync(Island, User)}.
@@ -1512,7 +1535,7 @@ void testsetMaxHomes() {
15121535
void testHomeTeleportAsyncIslandUser() throws Exception {
15131536
// Setup
15141537
Island island = mock(Island.class);
1515-
Location homeLoc = mock(Location.class);
1538+
Location homeLoc = setupSafeHomeLoc();
15161539
when(island.getHome("")).thenReturn(homeLoc);
15171540
when(island.getWorld()).thenReturn(world);
15181541
when(user.getPlayer()).thenReturn(player);
@@ -1547,7 +1570,7 @@ void testHomeTeleportAsyncIslandUser() throws Exception {
15471570
void testHomeTeleportAsyncIslandUserBooleanWithDefaultHome() throws Exception {
15481571
// Setup
15491572
Island island = mock(Island.class);
1550-
Location homeLoc = mock(Location.class);
1573+
Location homeLoc = setupSafeHomeLoc();
15511574
when(island.getHome("")).thenReturn(homeLoc);
15521575
when(island.getWorld()).thenReturn(world);
15531576
when(user.getPlayer()).thenReturn(player);
@@ -1584,7 +1607,7 @@ void testHomeTeleportAsyncIslandUserBooleanWithDefaultHome() throws Exception {
15841607
void testHomeTeleportAsyncIslandUserBooleanNewIsland() throws Exception {
15851608
// Setup
15861609
Island island = mock(Island.class);
1587-
Location homeLoc = mock(Location.class);
1610+
Location homeLoc = setupSafeHomeLoc();
15881611
when(island.getHome("")).thenReturn(homeLoc);
15891612
when(island.getWorld()).thenReturn(world);
15901613
when(user.getPlayer()).thenReturn(player);
@@ -1621,7 +1644,7 @@ void testHomeTeleportAsyncIslandUserBooleanNewIsland() throws Exception {
16211644
void testHomeTeleportAsyncIslandUserBooleanFailedTeleport() throws Exception {
16221645
// Setup
16231646
Island island = mock(Island.class);
1624-
Location homeLoc = mock(Location.class);
1647+
Location homeLoc = setupSafeHomeLoc();
16251648
when(island.getHome("")).thenReturn(homeLoc);
16261649
when(island.getWorld()).thenReturn(world);
16271650
when(user.getPlayer()).thenReturn(player);
@@ -1646,4 +1669,49 @@ void testHomeTeleportAsyncIslandUserBooleanFailedTeleport() throws Exception {
16461669
verify(user).sendMessage("commands.island.go.teleport");
16471670
verify(island).getHome("");
16481671
}
1672+
1673+
/**
1674+
* Test method for
1675+
* {@link world.bentobox.bentobox.managers.IslandsManager#homeTeleportAsync(Island, User, boolean)}.
1676+
* Test with unsafe home location - should use SafeSpotTeleport fallback instead of direct teleport.
1677+
*/
1678+
@Test
1679+
void testHomeTeleportAsyncIslandUserBooleanUnsafeLocation() throws Exception {
1680+
// Setup
1681+
Island island = mock(Island.class);
1682+
Location homeLoc = mock(Location.class);
1683+
when(homeLoc.getWorld()).thenReturn(world);
1684+
Block homeBlock = mock(Block.class);
1685+
Block homeGround = mock(Block.class);
1686+
Block homeSpace2 = mock(Block.class);
1687+
when(homeLoc.getBlock()).thenReturn(homeBlock);
1688+
when(homeBlock.getRelative(BlockFace.DOWN)).thenReturn(homeGround);
1689+
when(homeBlock.getRelative(BlockFace.UP)).thenReturn(homeSpace2);
1690+
// Unsafe location: no solid ground (all AIR)
1691+
when(homeGround.getType()).thenReturn(Material.AIR);
1692+
when(homeBlock.getType()).thenReturn(Material.AIR);
1693+
when(homeSpace2.getType()).thenReturn(Material.AIR);
1694+
// Mock chunk loading
1695+
Chunk homeChunk = mock(Chunk.class);
1696+
mockedUtil.when(() -> Util.getChunkAtAsync(homeLoc)).thenReturn(CompletableFuture.completedFuture(homeChunk));
1697+
1698+
when(island.getHome("")).thenReturn(homeLoc);
1699+
when(island.getWorld()).thenReturn(world);
1700+
when(island.getProtectionCenter()).thenReturn(location);
1701+
when(user.getPlayer()).thenReturn(player);
1702+
when(user.getUniqueId()).thenReturn(uuid);
1703+
1704+
// Mock player methods called by readyPlayer
1705+
when(player.isInsideVehicle()).thenReturn(false);
1706+
1707+
// Test
1708+
IslandsManager localIM = new IslandsManager(plugin);
1709+
// homeTeleportAsync should NOT call Util.teleportAsync directly for unsafe locations
1710+
// It should fall back to SafeSpotTeleport
1711+
localIM.homeTeleportAsync(island, user, true);
1712+
1713+
// Verify that direct teleportAsync was NOT called (SafeSpotTeleport handles it instead)
1714+
mockedUtil.verify(() -> Util.teleportAsync(eq(player), eq(homeLoc)), never());
1715+
verify(user).sendMessage("commands.island.go.teleport");
1716+
}
16491717
}

0 commit comments

Comments
 (0)