diff --git a/Essentials/src/main/java/com/earth2me/essentials/ISettings.java b/Essentials/src/main/java/com/earth2me/essentials/ISettings.java index 11566ca0459..7542e2b39f8 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/ISettings.java +++ b/Essentials/src/main/java/com/earth2me/essentials/ISettings.java @@ -136,6 +136,8 @@ public interface ISettings extends IConf { boolean isAlwaysTeleportSafety(); + boolean isConsiderWorldHeightForTeleportSafety(); + boolean isTeleportPassengerDismount(); boolean isForcePassengerTeleport(); diff --git a/Essentials/src/main/java/com/earth2me/essentials/Settings.java b/Essentials/src/main/java/com/earth2me/essentials/Settings.java index fbec7ea904a..69c8a0b3e17 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Settings.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Settings.java @@ -297,6 +297,11 @@ public boolean isAlwaysTeleportSafety() { return config.getBoolean("force-safe-teleport-location", false); } + @Override + public boolean isConsiderWorldHeightForTeleportSafety() { + return config.getBoolean("consider-world-height-for-teleport-safety", false); + } + @Override public boolean isTeleportPassengerDismount() { return config.getBoolean("teleport-passenger-dismount", true); diff --git a/Essentials/src/main/java/com/earth2me/essentials/utils/LocationUtil.java b/Essentials/src/main/java/com/earth2me/essentials/utils/LocationUtil.java index f9eb69c20cd..18faec97f30 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/utils/LocationUtil.java +++ b/Essentials/src/main/java/com/earth2me/essentials/utils/LocationUtil.java @@ -167,6 +167,10 @@ public static boolean isBlockUnsafe(IEssentials ess, final World world, final in return isBlockDamaging(world, x, y, z) || isBlockAboveAir(ess, world, x, y, z); } + private static boolean isBlockUnsafe(IEssentials ess, final World world, final int x, final int y, final int z, final int maxY) { + return y >= maxY || isBlockUnsafe(ess, world, x, y, z); + } + public static boolean isBlockDamaging(final World world, final int x, final int y, final int z) { final Material block = world.getBlockAt(x, y, z).getType(); final Material below = world.getBlockAt(x, y - 1, z).getType(); @@ -224,7 +228,9 @@ public static Location getSafeDestination(IEssentials ess, final Location loc) t final World world = loc.getWorld(); final int worldMinY = worldInfoProvider.getMinHeight(world); final int worldLogicalY = worldInfoProvider.getLogicalHeight(world); - final int worldMaxY = loc.getBlockY() < worldLogicalY ? worldLogicalY : worldInfoProvider.getMaxHeight(world); + final int worldMaxY = ess.getSettings().isConsiderWorldHeightForTeleportSafety() && loc.getBlockY() < worldLogicalY + ? worldLogicalY + : worldInfoProvider.getMaxHeight(world); int x = loc.getBlockX(); int y = (int) Math.round(loc.getY()); int z = loc.getBlockZ(); @@ -242,12 +248,12 @@ public static Location getSafeDestination(IEssentials ess, final Location loc) t break; } } - if (isBlockUnsafe(ess, world, x, y, z)) { + if (isBlockUnsafe(ess, world, x, y, z, worldMaxY)) { x = Math.round(loc.getX()) == origX ? x - 1 : x + 1; z = Math.round(loc.getZ()) == origZ ? z - 1 : z + 1; } int i = 0; - while (isBlockUnsafe(ess, world, x, y, z)) { + while (isBlockUnsafe(ess, world, x, y, z, worldMaxY)) { i++; if (i >= VOLUME.length) { x = origX; @@ -259,14 +265,14 @@ public static Location getSafeDestination(IEssentials ess, final Location loc) t y = NumberUtil.constrainToRange(origY + VOLUME[i].y, worldMinY, worldMaxY); z = origZ + VOLUME[i].z; } - while (isBlockUnsafe(ess, world, x, y, z)) { + while (isBlockUnsafe(ess, world, x, y, z, worldMaxY)) { y += 1; if (y >= worldMaxY) { x += 1; break; } } - while (isBlockUnsafe(ess, world, x, y, z)) { + while (isBlockUnsafe(ess, world, x, y, z, worldMaxY)) { y -= 1; if (y <= worldMinY + 1) { x += 1; diff --git a/Essentials/src/main/resources/config.yml b/Essentials/src/main/resources/config.yml index eb441235b6b..5b176ffb807 100644 --- a/Essentials/src/main/resources/config.yml +++ b/Essentials/src/main/resources/config.yml @@ -99,6 +99,10 @@ force-disable-teleport-safety: false # safe location. If you'd like players to be teleported to a safe location all of the time, set this option to true. force-safe-teleport-location: false +# When teleporting to an unsafe location, should Essentials consider the world's logical height when finding a safe destination? +# This prevents safety checks from moving players above the Nether roof unless the requested destination is already above it. +consider-world-height-for-teleport-safety: true + # Consider water blocks as "safe", therefore allowing players to teleport # using commands such as /home or /spawn to a location that is occupied by water blocks. is-water-safe: false diff --git a/Essentials/src/test/java/com/earth2me/essentials/UtilTest.java b/Essentials/src/test/java/com/earth2me/essentials/UtilTest.java index 33c88ea4485..2922e56481b 100644 --- a/Essentials/src/test/java/com/earth2me/essentials/UtilTest.java +++ b/Essentials/src/test/java/com/earth2me/essentials/UtilTest.java @@ -3,6 +3,12 @@ import com.earth2me.essentials.utils.DateUtil; import com.earth2me.essentials.utils.LocationUtil; import com.earth2me.essentials.utils.VersionUtil; +import net.ess3.provider.WorldInfoProvider; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.WorldBorder; +import org.bukkit.block.Block; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -18,6 +24,9 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class UtilTest { @@ -66,6 +75,52 @@ public void testSafeLocation() { assertEquals(diameter * diameter * diameter, count); } + @Test + public void testSafeLocationRespectsLogicalHeight() throws Exception { + final Location result = getSafeDestinationWithLogicalHeightSetting(true); + + assertEquals(1, result.getBlockX()); + assertEquals(100, result.getBlockY()); + } + + @Test + public void testSafeLocationIgnoresLogicalHeightWhenDisabled() throws Exception { + final Location result = getSafeDestinationWithLogicalHeightSetting(false); + + assertEquals(0, result.getBlockX()); + assertEquals(128, result.getBlockY()); + } + + private Location getSafeDestinationWithLogicalHeightSetting(final boolean considerWorldHeight) throws Exception { + final IEssentials essentials = mock(IEssentials.class); + final ISettings settings = mock(ISettings.class); + final WorldInfoProvider worldInfoProvider = mock(WorldInfoProvider.class); + final World world = mock(World.class); + final WorldBorder worldBorder = mock(WorldBorder.class); + final Block solid = mock(Block.class); + final Block hollow = mock(Block.class); + + when(essentials.provider(WorldInfoProvider.class)).thenReturn(worldInfoProvider); + when(essentials.getSettings()).thenReturn(settings); + when(settings.isConsiderWorldHeightForTeleportSafety()).thenReturn(considerWorldHeight); + when(worldInfoProvider.getMinHeight(world)).thenReturn(0); + when(worldInfoProvider.getLogicalHeight(world)).thenReturn(128); + when(worldInfoProvider.getMaxHeight(world)).thenReturn(256); + when(world.getWorldBorder()).thenReturn(worldBorder); + when(worldBorder.getCenter()).thenReturn(new Location(world, 0, 0, 0)); + when(worldBorder.getSize()).thenReturn(60_000_000D); + when(solid.getType()).thenReturn(Material.BEDROCK); + when(hollow.getType()).thenReturn(Material.LIGHT); + when(world.getBlockAt(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> { + final int x = invocation.getArgument(0); + final int y = invocation.getArgument(1); + final int z = invocation.getArgument(2); + return y >= 128 || x == 1 && z == 0 && (y == 100 || y == 101) ? hollow : solid; + }); + + return LocationUtil.getSafeDestination(essentials, new Location(world, 0, 64, 0)); + } + @Test public void testFDDnow() { final Calendar c = new GregorianCalendar();