diff --git a/pom.xml b/pom.xml index 34f1fc7..92442e1 100644 --- a/pom.xml +++ b/pom.xml @@ -33,13 +33,9 @@ - - codemc-snapshots - https://repo.codemc.org/repository/maven-snapshots - - codemc-releases - https://repo.codemc.org/repository/maven-releases + bentoboxworld + https://repo.codemc.org/repository/bentoboxworld/ @@ -49,12 +45,12 @@ 17 2.0.9 - 1.20.4-R0.1-SNAPSHOT - 2.5.0-SNAPSHOT + 1.21.3-R0.1-SNAPSHOT + 2.7.1-SNAPSHOT ${build.version}-SNAPSHOT - 4.3.1 + 4.4.0 -LOCAL BentoBoxWorld_Border @@ -109,6 +105,10 @@ spigot-repo https://hub.spigotmc.org/nexus/content/repositories/snapshots + + bentoboxworld + https://repo.codemc.org/repository/bentoboxworld/ + codemc https://repo.codemc.org/repository/maven-snapshots/ diff --git a/src/main/java/world/bentobox/border/listeners/BlockListener.java b/src/main/java/world/bentobox/border/listeners/BlockListener.java new file mode 100644 index 0000000..a7d187d --- /dev/null +++ b/src/main/java/world/bentobox/border/listeners/BlockListener.java @@ -0,0 +1,325 @@ +package world.bentobox.border.listeners; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +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; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDismountEvent; +import org.bukkit.event.entity.EntityMountEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerRespawnEvent; +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 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.util.Util; +import world.bentobox.border.Border; +import world.bentobox.border.PerPlayerBorderProxy; +import world.bentobox.border.commands.IslandBorderCommand; + +/** + * @author tastybento + */ +public class BlockListener implements Listener { + + private static final Vector XZ = new Vector(1,0,1); + private final Border addon; + private Set inTeleport; + private final BorderShower show; + private Map mountedPlayers = new HashMap<>(); + + public BlockListener(Border addon) { + this.addon = addon; + inTeleport = new HashSet<>(); + this.show = addon.getBorderShower(); + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onPlayerJoin(PlayerJoinEvent e) { + // Run one-tick after joining because meta data cannot be set otherwise + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> processEvent(e)); + } + + protected void processEvent(PlayerJoinEvent e) { + User user = User.getInstance(e.getPlayer()); + + show.hideBorder(user); + // Just for sure, disable world Border + user.getPlayer().setWorldBorder(null); + + // 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 + "border.type") && !e.getPlayer().hasPermission(permPrefix + "border.bordertype")) { + // 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 -> + show.showBorder(e.getPlayer(), i))); + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onPlayerQuit(PlayerQuitEvent e) { + show.clearUser(User.getInstance(e.getPlayer())); + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onPlayerRespawn(PlayerRespawnEvent e) { + show.clearUser(User.getInstance(e.getPlayer())); + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> addon.getIslands().getIslandAt(e.getPlayer().getLocation()).ifPresent(i -> + show.showBorder(e.getPlayer(), i))); + } + + private boolean isOn(Player player) { + // Check if border is off + User user = User.getInstance(player); + return user.getMetaData(BorderShower.BORDER_STATE_META_DATA).map(MetaDataValue::asBoolean) + .orElse(addon.getSettings().isShowByDefault()); + + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onPlayerTeleport(PlayerTeleportEvent e) { + Player player = e.getPlayer(); + if (!isOn(player)) { + return; + } + Location to = e.getTo(); + + show.clearUser(User.getInstance(player)); + + if (to == null || !addon.inGameWorld(to.getWorld())) { + return; + } + + TeleportCause cause = e.getCause(); + boolean isBlacklistedCause = cause == TeleportCause.ENDER_PEARL || cause == TeleportCause.CHORUS_FRUIT; + + Bukkit.getScheduler().runTask(addon.getPlugin(), () -> + addon.getIslands().getIslandAt(to).ifPresentOrElse(i -> { + Optional boxedEnderPearlFlag = i.getPlugin().getFlagsManager().getFlag("ALLOW_MOVE_BOX"); + + if (isBlacklistedCause + && (!i.getProtectionBoundingBox().contains(to.toVector()) + || !i.onIsland(player.getLocation()))) { + e.setCancelled(true); + } + + if (boxedEnderPearlFlag.isPresent() + && boxedEnderPearlFlag.get().isSetForWorld(to.getWorld()) + && cause == TeleportCause.ENDER_PEARL) { + e.setCancelled(false); + } + + show.showBorder(player, i); + }, () -> { + if (isBlacklistedCause) { + e.setCancelled(true); + return; + } + }) + ); + } + + @EventHandler(priority = EventPriority.NORMAL) + public void onPlayerLeaveIsland(PlayerMoveEvent e) { + Player p = e.getPlayer(); + if (!isOn(p)) { + return; + } + Location from = e.getFrom(); + if (!addon.getSettings().isReturnTeleport() || !outsideCheck(e.getPlayer(), from, e.getTo())) { + return; + } + // Move the player back inside the border + if (addon.getIslands().getProtectedIslandAt(from).isPresent()) { + e.setCancelled(true); + inTeleport.add(p.getUniqueId()); + Util.teleportAsync(p, from).thenRun(() -> inTeleport.remove(p.getUniqueId())); + return; + } + // Backtrack + 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 && 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()) { + case NETHER: + targetPos.getBlock().getRelative(BlockFace.DOWN).setType(Material.NETHERRACK); + break; + case THE_END: + targetPos.getBlock().getRelative(BlockFace.DOWN).setType(Material.END_STONE); + break; + default: + targetPos.getBlock().getRelative(BlockFace.DOWN).setType(Material.STONE); + break; + } + } + Util.teleportAsync(p, targetPos).thenRun(() -> inTeleport.remove(p.getUniqueId())); + } + }); + } + + 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 + * @param from - from location + * @param to - to location + * @return true if outside the island protection zone + */ + private boolean outsideCheck(Player player, Location from, Location to) { + User user = Objects.requireNonNull(User.getInstance(player)); + + if ((from.getWorld() != null && from.getWorld().equals(to.getWorld()) + && from.toVector().multiply(XZ).equals(to.toVector().multiply(XZ))) + || !addon.inGameWorld(player.getWorld()) + || user.getPlayer().getGameMode() == GameMode.SPECTATOR + // || !addon.getIslands().getIslandAt(to).filter(i -> addon.getIslands().locationIsOnIsland(player, i.getProtectionCenter())).isPresent() + || !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(); + } + + /** + * 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 + */ + @EventHandler(priority = EventPriority.NORMAL) + public void onPlayerMove(PlayerMoveEvent e) { + // Remove head movement + if (!e.getFrom().toVector().equals(e.getTo().toVector())) { + addon.getIslands() + .getIslandAt(e.getPlayer().getLocation()) + .ifPresent(i -> show.refreshView(User.getInstance(e.getPlayer()), i)); + } + } + + /** + * Refresh the view when riding in a vehicle + * @param e event + */ + @EventHandler(priority = EventPriority.NORMAL) + 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) + .forEach(p -> addon + .getIslands() + .getIslandAt(p.getLocation()) + .ifPresent(i -> show.refreshView(User.getInstance(p), i))); + } + } + + /** + * Hide and then show the border to react to the change in protection area + * @param e + */ + @EventHandler(priority = EventPriority.NORMAL) + public void onProtectionRangeChange(IslandProtectionRangeChangeEvent e) { + // Hide and show again + e.getIsland().getPlayersOnIsland().forEach(player -> { + show.hideBorder(User.getInstance(player)); + show.showBorder(player, e.getIsland()); + }); + } +}