diff --git a/README.md b/README.md index 007a062..e71a362 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_Border&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_Border) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_Border&metric=security_rating)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_Border) -**Border** can create and show a border around islands which players cannot pass. +**Border** creates and shows a world border around islands which players cannot pass. **See the full documentation [here](https://docs.bentobox.world/en/latest/addons/Border/).** @@ -12,4 +12,4 @@ [Sponsor tastybento](https://github.com/sponsors/tastybento) to get more addons like this and make this one better! ## Are you a coder? -This is one of the easier addons from a code perspective. Maybe you could make it better! Border is open source and we love Pull Requests. Become a BentoBox co-author today! \ No newline at end of file +This is one of the easier addons from a code perspective. Maybe you could make it better! Border is open source and we love Pull Requests. Become a BentoBox co-author today! diff --git a/pom.xml b/pom.xml index d4e04e9..4ce85e6 100644 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,7 @@ <!-- Revision variable removes warning about dynamic version --> <revision>${build.version}-SNAPSHOT</revision> <!-- This allows to change between versions and snapshots. --> - <build.version>4.2.1</build.version> + <build.version>4.2.2</build.version> <build.number>-LOCAL</build.number> <!-- Sonar Cloud --> <sonar.projectKey>BentoBoxWorld_Border</sonar.projectKey> diff --git a/src/main/java/world/bentobox/border/listeners/PlayerListener.java b/src/main/java/world/bentobox/border/listeners/PlayerListener.java index 3568f86..0e839fa 100644 --- a/src/main/java/world/bentobox/border/listeners/PlayerListener.java +++ b/src/main/java/world/bentobox/border/listeners/PlayerListener.java @@ -1,6 +1,8 @@ package world.bentobox.border.listeners; import java.util.HashSet; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -11,6 +13,7 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -22,9 +25,13 @@ import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; import org.bukkit.event.vehicle.VehicleMoveEvent; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.util.NumberConversions; import org.bukkit.util.RayTraceResult; import org.bukkit.util.Vector; +import org.spigotmc.event.entity.EntityDismountEvent; +import org.spigotmc.event.entity.EntityMountEvent; import world.bentobox.bentobox.api.events.island.IslandProtectionRangeChangeEvent; import world.bentobox.bentobox.api.flags.Flag; import world.bentobox.bentobox.api.metadata.MetaDataValue; @@ -44,6 +51,7 @@ public class PlayerListener implements Listener { private final Border addon; private Set<UUID> inTeleport; private final BorderShower show; + private Map<Player, BukkitTask> mountedPlayers = new HashMap<>(); public PlayerListener(Border addon) { this.addon = addon; @@ -64,17 +72,19 @@ protected void processEvent(PlayerJoinEvent e) { // Just for sure, disable world Border user.getPlayer().setWorldBorder(null); - // Check player perms and return to defaults if players don't have them - if (!e.getPlayer().hasPermission(addon.getPermissionPrefix() + IslandBorderCommand.BORDER_COMMAND_PERM)) { - // Restore barrier on/off to default - user.putMetaData(BorderShower.BORDER_STATE_META_DATA, new MetaDataValue(addon.getSettings().isShowByDefault())); - - if (!e.getPlayer().hasPermission(addon.getPermissionPrefix() + BorderTypeCommand.BORDER_TYPE_COMMAND_PERM)) { + // Get the game mode that this player is in + addon.getPlugin().getIWM().getAddon(e.getPlayer().getWorld()).map(gma -> gma.getPermissionPrefix()).filter( + permPrefix -> !e.getPlayer().hasPermission(permPrefix + IslandBorderCommand.BORDER_COMMAND_PERM)) + .ifPresent(permPrefix -> { + // Restore barrier on/off to default + user.putMetaData(BorderShower.BORDER_STATE_META_DATA, + new MetaDataValue(addon.getSettings().isShowByDefault())); + if (!e.getPlayer().hasPermission(permPrefix + BorderTypeCommand.BORDER_TYPE_COMMAND_PERM)) { // Restore default barrier type to player MetaDataValue metaDataValue = new MetaDataValue(addon.getSettings().getType().getId()); user.putMetaData(PerPlayerBorderProxy.BORDER_BORDERTYPE_META_DATA, metaDataValue); } - } + }); // Show the border if required one tick after Bukkit.getScheduler().runTask(addon.getPlugin(), () -> addon.getIslands().getIslandAt(e.getPlayer().getLocation()).ifPresent(i -> @@ -151,10 +161,15 @@ public void onPlayerLeaveIsland(PlayerMoveEvent e) { addon.getIslands().getIslandAt(p.getLocation()).ifPresent(i -> { Vector unitVector = i.getProtectionCenter().toVector().subtract(p.getLocation().toVector()).normalize() .multiply(new Vector(1,0,1)); + if (unitVector.lengthSquared() <= 0D) { + // Direction is zero, so nothing to do; cannot move. + return; + } RayTraceResult r = i.getProtectionBoundingBox().rayTrace(p.getLocation().toVector(), unitVector, i.getRange()); - if (r != null) { + if (r != null && checkFinite(r.getHitPosition())) { inTeleport.add(p.getUniqueId()); Location targetPos = r.getHitPosition().toLocation(p.getWorld(), p.getLocation().getYaw(), p.getLocation().getPitch()); + if (!e.getPlayer().isFlying() && addon.getSettings().isReturnTeleportBlock() && !addon.getIslands().isSafeLocation(targetPos)) { switch (targetPos.getWorld().getEnvironment()) { @@ -174,6 +189,11 @@ public void onPlayerLeaveIsland(PlayerMoveEvent e) { }); } + public boolean checkFinite(Vector toCheck) { + return NumberConversions.isFinite(toCheck.getX()) && NumberConversions.isFinite(toCheck.getY()) + && NumberConversions.isFinite(toCheck.getZ()); + } + /** * Check if the player is outside the island protection zone that they are supposed to be in. * @param player - player moving @@ -195,6 +215,56 @@ private boolean outsideCheck(Player player, Location from, Location to) { return addon.getIslands().getIslandAt(to).filter(i -> !i.onIsland(to)).isPresent(); } + /** + * Runs a task while the player is mounting an entity and eject + * if the entity went outside the protection range + * @param event - event + */ + @EventHandler + public void onEntityMount(EntityMountEvent event) { + Entity entity = event.getEntity(); + if (!(entity instanceof Player player)) { + return; + } + + mountedPlayers.put(player, Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), () -> { + Location loc = player.getLocation(); + + if (!addon.inGameWorld(loc.getWorld())) { + return; + } + // Eject from mount if outside the protection range + if (addon.getIslands().getProtectedIslandAt(loc).isEmpty()) { + // Force the dismount event for custom entities + if (!event.getMount().eject()) { + var dismountEvent = new EntityDismountEvent(player, event.getMount()); + Bukkit.getPluginManager().callEvent(dismountEvent); + } + } + }, 1, 20)); + } + + /** + * Cancel the running task if the player was mounting an entity + * @param event - event + */ + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onEntityDismount(EntityDismountEvent event) { + Entity entity = event.getEntity(); + if (!(entity instanceof Player player)) { + return; + } + + BukkitTask task = mountedPlayers.get(player); + if (task == null) { + return; + } + + task.cancel(); + mountedPlayers.remove(player); + } + + /** * Refreshes the barrier view when the player moves (more than just moving their head) * @param e event diff --git a/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java b/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java index 3a62045..632369f 100644 --- a/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java +++ b/src/test/java/world/bentobox/border/listeners/PlayerListenerTest.java @@ -40,9 +40,11 @@ import org.powermock.modules.junit4.PowerMockRunner; import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.events.island.IslandProtectionRangeChangeEvent; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; +import world.bentobox.bentobox.managers.IslandWorldManager; import world.bentobox.bentobox.managers.IslandsManager; import world.bentobox.bentobox.util.Util; import world.bentobox.border.Border; @@ -82,6 +84,10 @@ public class PlayerListenerTest { private Island island; @Mock private Vehicle vehicle; + @Mock + private IslandWorldManager iwm; + @Mock + private GameModeAddon gma; /** @@ -135,6 +141,15 @@ public void setUp() throws Exception { // Util PowerMockito.mockStatic(Util.class, Mockito.RETURNS_MOCKS); + // Plugin + when(addon.getPlugin()).thenReturn(plugin); + + // IWM + when(gma.getPermissionPrefix()).thenReturn("bskyblock."); + when(iwm.getAddon(world)).thenReturn(Optional.of(gma)); + when(plugin.getIWM()).thenReturn(iwm); + + pl = new PlayerListener(addon); } @@ -178,7 +193,7 @@ public void testOnPlayerQuit() { */ @Test public void testOnPlayerRespawn() { - PlayerRespawnEvent event = new PlayerRespawnEvent(player, null, false, false); + PlayerRespawnEvent event = new PlayerRespawnEvent(player, from, false, false, null); pl.onPlayerRespawn(event); PowerMockito.verifyStatic(Bukkit.class); Bukkit.getScheduler();