diff --git a/Bukkit/src/main/java/de/btegermany/teleportation/TeleportationBukkit/message/executor/TeleportExecutor.java b/Bukkit/src/main/java/de/btegermany/teleportation/TeleportationBukkit/message/executor/TeleportExecutor.java index 08c89ec..bbfc9cd 100644 --- a/Bukkit/src/main/java/de/btegermany/teleportation/TeleportationBukkit/message/executor/TeleportExecutor.java +++ b/Bukkit/src/main/java/de/btegermany/teleportation/TeleportationBukkit/message/executor/TeleportExecutor.java @@ -56,8 +56,14 @@ public void execute(ByteArrayDataInput dataInput) { Float pitch = Optional.of(dataInput.readUTF()).filter(str -> !str.equals("null")).map(Float::parseFloat).orElse(null); String worldName = Optional.of(dataInput.readUTF()).filter(str -> !str.equals("null")).orElse(null); String originServerName = dataInput.readUTF(); - - this.teleportationHandler.handle(new PendingTpLocation(playerUUID, x, y, z, yaw, pitch, worldName, originServerName, this.plugin)); + boolean prepareRegion = false; + try { + prepareRegion = Boolean.parseBoolean(dataInput.readUTF()); + } catch (IllegalStateException ignored) { + // Older Velocity messages do not include the optional preparation flag. + } + + this.teleportationHandler.handle(new PendingTpLocation(playerUUID, x, y, z, yaw, pitch, worldName, originServerName, prepareRegion, this.plugin)); } } diff --git a/Bukkit/src/main/java/de/btegermany/teleportation/TeleportationBukkit/tp/PendingTpLocation.java b/Bukkit/src/main/java/de/btegermany/teleportation/TeleportationBukkit/tp/PendingTpLocation.java index 9f84023..353afa9 100644 --- a/Bukkit/src/main/java/de/btegermany/teleportation/TeleportationBukkit/tp/PendingTpLocation.java +++ b/Bukkit/src/main/java/de/btegermany/teleportation/TeleportationBukkit/tp/PendingTpLocation.java @@ -17,9 +17,14 @@ public class PendingTpLocation extends PendingTeleportationAbstract { private final Float yaw; private final Float pitch; private final String worldName; + private final boolean prepareRegion; private final TeleportationBukkit plugin; public PendingTpLocation(UUID playerUUID, double x, double y, double z, Float yaw, Float pitch, String worldName, String originServerName, TeleportationBukkit plugin) { + this(playerUUID, x, y, z, yaw, pitch, worldName, originServerName, false, plugin); + } + + public PendingTpLocation(UUID playerUUID, double x, double y, double z, Float yaw, Float pitch, String worldName, String originServerName, boolean prepareRegion, TeleportationBukkit plugin) { super(playerUUID, originServerName); this.x = x; this.y = y; @@ -27,6 +32,7 @@ public PendingTpLocation(UUID playerUUID, double x, double y, double z, Float ya this.yaw = yaw; this.pitch = pitch; this.worldName = worldName; + this.prepareRegion = prepareRegion; this.plugin = plugin; } @@ -38,11 +44,41 @@ public boolean canTeleport() { @Override public void teleport() { + Bukkit.getScheduler().runTask(this.plugin, () -> { + Player player = Bukkit.getPlayer(this.playerUUID); + if (player == null || !player.isOnline()) { + return; + } + + World world = (worldName == null) ? player.getWorld() : Bukkit.getWorld(worldName); + if (world == null) { + world = Bukkit.getWorld("world"); + } + if (world == null) { + player.sendMessage(TeleportationBukkit.getFormattedErrorMessage("Target world not found.")); + return; + } + + World targetWorld = world; + if (!this.prepareRegion) { + this.teleportPrepared(targetWorld); + return; + } + + TerraplusminusRegionPreparer.prepare(this.plugin, player, targetWorld, x, z) + .thenAccept(prepared -> { + if (!Boolean.TRUE.equals(prepared)) { + return; + } + Bukkit.getScheduler().runTask(this.plugin, () -> this.teleportPrepared(targetWorld)); + }); + }); + } + + private void teleportPrepared(World world) { Player player = Bukkit.getPlayer(this.playerUUID); - assert player != null; // canTeleport() checked - World world = (worldName == null) ? player.getWorld() : Bukkit.getWorld(worldName); - if (world == null) { - world = Bukkit.getWorld("world"); + if (player == null || !player.isOnline()) { + return; } double yWorld = y; @@ -53,7 +89,7 @@ public void teleport() { location.setYaw(yaw != null ? yaw : player.getLocation().getYaw()); location.setPitch(pitch != null ? pitch : player.getLocation().getPitch()); - Bukkit.getScheduler().runTask(this.plugin, () -> player.teleport(location)); + player.teleport(location); } } diff --git a/Bukkit/src/main/java/de/btegermany/teleportation/TeleportationBukkit/tp/TerraplusminusRegionPreparer.java b/Bukkit/src/main/java/de/btegermany/teleportation/TeleportationBukkit/tp/TerraplusminusRegionPreparer.java new file mode 100644 index 0000000..66989d3 --- /dev/null +++ b/Bukkit/src/main/java/de/btegermany/teleportation/TeleportationBukkit/tp/TerraplusminusRegionPreparer.java @@ -0,0 +1,87 @@ +package de.btegermany.teleportation.TeleportationBukkit.tp; + +import de.btegermany.teleportation.TeleportationBukkit.TeleportationBukkit; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +final class TerraplusminusRegionPreparer { + + private static final String PLUGIN_NAME = "Terraplusminus"; + private static final int TELEPORT_PREP_RADIUS_CHUNKS = 16; + + private TerraplusminusRegionPreparer() { + } + + static CompletableFuture prepare(TeleportationBukkit owner, Player player, World world, double x, double z) { + Plugin terraplusminus = Bukkit.getPluginManager().getPlugin(PLUGIN_NAME); + if (terraplusminus == null || !terraplusminus.isEnabled()) { + return CompletableFuture.completedFuture(true); + } + + try { + Method isTeleportRegionGenerated = terraplusminus.getClass().getMethod( + "isTeleportRegionGenerated", + World.class, + double.class, + double.class, + int.class + ); + boolean alreadyGenerated = (boolean) isTeleportRegionGenerated.invoke( + terraplusminus, + world, + x, + z, + TELEPORT_PREP_RADIUS_CHUNKS + ); + if (alreadyGenerated) { + return CompletableFuture.completedFuture(true); + } + + player.sendMessage(TeleportationBukkit.getFormattedMessage("Die Zielregion wird gerade generiert. Du wirst teleportiert, sobald sie bereit ist.")); + Method primeTeleportRegionCache = terraplusminus.getClass().getMethod( + "primeTeleportRegionCache", + World.class, + double.class, + double.class, + int.class + ); + Object result = primeTeleportRegionCache.invoke( + terraplusminus, + world, + x, + z, + TELEPORT_PREP_RADIUS_CHUNKS + ); + + if (!(result instanceof CompletionStage completionStage)) { + owner.getLogger().warning("Terraplusminus primeTeleportRegionCache did not return a CompletionStage."); + return CompletableFuture.completedFuture(false); + } + + return completionStage + .thenApply(ignored -> true) + .exceptionally(ex -> { + owner.getLogger().warning("Terraplusminus failed to prepare the target teleport region: " + ex.getMessage()); + Bukkit.getScheduler().runTask(owner, () -> + player.sendMessage(TeleportationBukkit.getFormattedErrorMessage("Die Zielregion konnte nicht generiert werden. Bitte versuche es spaeter erneut."))); + return false; + }) + .toCompletableFuture(); + } catch (NoSuchMethodException e) { + owner.getLogger().fine("Terraplusminus is installed without teleport region preparation API; continuing without cache priming."); + return CompletableFuture.completedFuture(true); + } catch (IllegalAccessException | InvocationTargetException | ClassCastException e) { + owner.getLogger().warning("Could not call Terraplusminus teleport region preparation API: " + e.getMessage()); + Bukkit.getScheduler().runTask(owner, () -> + player.sendMessage(TeleportationBukkit.getFormattedErrorMessage("Die Zielregion konnte nicht generiert werden. Bitte versuche es spaeter erneut."))); + return CompletableFuture.completedFuture(false); + } + } +} diff --git a/Velocity/src/main/java/de/btegermany/teleportation/TeleportationVelocity/command/TpllCommand.java b/Velocity/src/main/java/de/btegermany/teleportation/TeleportationVelocity/command/TpllCommand.java index cb50862..8bd00b4 100644 --- a/Velocity/src/main/java/de/btegermany/teleportation/TeleportationVelocity/command/TpllCommand.java +++ b/Velocity/src/main/java/de/btegermany/teleportation/TeleportationVelocity/command/TpllCommand.java @@ -136,7 +136,7 @@ public void execute(Invocation invocation) { // send teleport data and the player to the target server RequestLastLocationMessage requestLastLocationMessage = new RequestLastLocationMessage(player, this.registriesProvider, () -> { sendMessage(player, Component.text("Teleporting to " + coordinates.getLat() + ", " + coordinates.getLng() + ".", NamedTextColor.GOLD)); - this.pluginMessenger.teleportToCoords(player, targetServer, mcCoordinatesFinal[0], mcCoordinatesY, mcCoordinatesFinal[1], yawFinal, pitchFinal, worldFinal); + this.pluginMessenger.teleportToCoords(player, targetServer, mcCoordinatesFinal[0], mcCoordinatesY, mcCoordinatesFinal[1], yawFinal, pitchFinal, worldFinal, true); }); if (player.getCurrentServer().isEmpty()) { return; diff --git a/Velocity/src/main/java/de/btegermany/teleportation/TeleportationVelocity/message/PluginMessenger.java b/Velocity/src/main/java/de/btegermany/teleportation/TeleportationVelocity/message/PluginMessenger.java index 83a5521..85c2803 100644 --- a/Velocity/src/main/java/de/btegermany/teleportation/TeleportationVelocity/message/PluginMessenger.java +++ b/Velocity/src/main/java/de/btegermany/teleportation/TeleportationVelocity/message/PluginMessenger.java @@ -69,6 +69,11 @@ public void teleportToCoords(Player player, RegisteredServer server, double x, d this.sendAndConnect(player, server, new TeleportToCoordsMessage(player, x, y, z, yaw, pitch, world)); } + // teleports a player to the specified coordinates after the backend prepared the target region if supported + public void teleportToCoords(Player player, RegisteredServer server, double x, double y, double z, Float yaw, Float pitch, String world, boolean prepareRegion) { + this.sendAndConnect(player, server, new TeleportToCoordsMessage(player, x, y, z, yaw, pitch, world, prepareRegion)); + } + // teleports a player to the normen world on the specified server public void teleportToNormen(Player player, RegisteredServer server, String normenWorld, float yaw, float pitch) { this.sendAndConnect(player, server, new TeleportToNormenMessage(player, normenWorld, yaw, pitch)); diff --git a/Velocity/src/main/java/de/btegermany/teleportation/TeleportationVelocity/message/TeleportToCoordsMessage.java b/Velocity/src/main/java/de/btegermany/teleportation/TeleportationVelocity/message/TeleportToCoordsMessage.java index b60eab5..849be9a 100644 --- a/Velocity/src/main/java/de/btegermany/teleportation/TeleportationVelocity/message/TeleportToCoordsMessage.java +++ b/Velocity/src/main/java/de/btegermany/teleportation/TeleportationVelocity/message/TeleportToCoordsMessage.java @@ -8,6 +8,10 @@ public class TeleportToCoordsMessage extends PluginMessage { public TeleportToCoordsMessage(Player player, double x, double y, double z, Float yaw, Float pitch, String world) { + this(player, x, y, z, yaw, pitch, world, false); + } + + public TeleportToCoordsMessage(Player player, double x, double y, double z, Float yaw, Float pitch, String world, boolean prepareRegion) { super("teleport_coords", MessageType.NORMAL); if (player.getCurrentServer().isEmpty()) { return; @@ -20,7 +24,8 @@ public TeleportToCoordsMessage(Player player, double x, double y, double z, Floa pitch == null ? "null" : pitch.toString(), // null to use the player's current world world == null ? "null" : world, - player.getCurrentServer().get().getServerInfo().getName())); + player.getCurrentServer().get().getServerInfo().getName(), + Boolean.toString(prepareRegion))); } }