4 Commits
1.0.0 ... 1.0.2

Author SHA1 Message Date
Git Manager GUI
71534e7e06 Upload folder via GUI - src 2026-05-23 15:56:58 +02:00
Git Manager GUI
d703187fae Upload via Git Manager GUI 2026-05-23 15:56:53 +02:00
Git Manager GUI
90cca7c559 Upload folder via GUI - src 2026-05-22 22:11:35 +02:00
7c8bf27628 README.md aktualisiert 2026-05-22 19:07:12 +00:00
9 changed files with 843 additions and 55 deletions

View File

@@ -183,7 +183,3 @@ Alle Nachrichten unterstützen `&`-Farbcodes und Platzhalter wie `{player}`, `{n
## Support ## Support
Wenn du Probleme, Wünsche oder Fehlerberichte hast, eröffne ein Issue auf GitHub und füge wenn möglich den vollständigen Server-Log sowie deine `config.yml` bei. Wenn du Probleme, Wünsche oder Fehlerberichte hast, eröffne ein Issue auf GitHub und füge wenn möglich den vollständigen Server-Log sowie deine `config.yml` bei.
## Lizenz
Falls du eine Lizenz verwenden möchtest, ergänze sie bitte in diesem Abschnitt oder als separate LICENSE-Datei im Repository.

View File

@@ -7,7 +7,7 @@
<groupId>de.teleportsuite</groupId> <groupId>de.teleportsuite</groupId>
<artifactId>TeleportSuite</artifactId> <artifactId>TeleportSuite</artifactId>
<version>1.0.0</version> <version>1.0.1</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>

View File

@@ -5,29 +5,44 @@ import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener; import org.bukkit.plugin.messaging.PluginMessageListener;
import java.io.*; import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
/** /**
* Handles BungeeCord Plugin Messaging. * Handles BungeeCord Plugin Messaging.
* Sends players to other servers and transmits target coordinates
* so a receiving TeleportSuite instance can teleport them on arrival.
* *
* Channel "teleportsuite:tp" payload format (DataOutputStream): * Cross-server teleport flow (fixed):
* String targetPlayer * 1. Query the target server for the exact coordinates of the target player (QUERY_LOCATION).
* String world * 2. Target server replies with LOCATION_RESPONSE (world, x, y, z, yaw, pitch).
* Double x, y, z * 3. We send a LOCATION payload to the target server so it teleports the mover on arrival.
* Float yaw, pitch * 4. We send the BungeeCord Connect/ConnectOther to actually switch the server.
*
* Step 3 happens *before* step 4 to avoid the race condition where the player
* arrives on the target server before the teleport payload does.
* A 1-second timeout falls back to the TP_TO_PLAYER polling mechanism.
*/ */
public class BungeeMessenger implements PluginMessageListener { public class BungeeMessenger implements PluginMessageListener {
private static final String BUNGEE_CHANNEL = "BungeeCord"; private static final String BUNGEE_CHANNEL = "BungeeCord";
private static final String TS_CHANNEL = "teleportsuite:tp"; private static final String TS_CHANNEL = "teleportsuite:tp";
private final TeleportSuite plugin; private final TeleportSuite plugin;
private final Map<String, List<Consumer<String>>> pendingServerLookups = new HashMap<>();
private final Map<String, List<Consumer<List<String>>>> pendingPlayerListLookups = new HashMap<>();
// key = targetPlayer.toLowerCase()
private final Map<String, List<Consumer<org.bukkit.Location>>> pendingLocationLookups = new HashMap<>();
public BungeeMessenger(TeleportSuite plugin) { this.plugin = plugin; } public BungeeMessenger(TeleportSuite plugin) { this.plugin = plugin; }
public void register() { public void register() {
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, BUNGEE_CHANNEL); plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, BUNGEE_CHANNEL);
plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, TS_CHANNEL); plugin.getServer().getMessenger().registerOutgoingPluginChannel(plugin, TS_CHANNEL);
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, BUNGEE_CHANNEL, this);
plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, TS_CHANNEL, this); plugin.getServer().getMessenger().registerIncomingPluginChannel(plugin, TS_CHANNEL, this);
} }
@@ -36,15 +51,345 @@ public class BungeeMessenger implements PluginMessageListener {
plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin); plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin);
} }
/** // -------------------------------------------------------------------------
* Connect a player to another BungeeCord server. // Player-server & player-list lookups (unchanged)
* Also sends a plugin message so the target server knows where to teleport the player. // -------------------------------------------------------------------------
*/
public void connectToServer(Player player, String server, String world, double x, double y, double z, float yaw, float pitch) {
// 1) Notify the target server about the pending teleport
sendTeleportPayload(player, player.getName(), server, world, x, y, z, yaw, pitch);
// 2) Switch server via BungeeCord public void requestPlayerServer(Player sender, String targetPlayer, Consumer<String> callback) {
if (sender == null || !sender.isOnline()) {
callback.accept(null);
return;
}
String key = targetPlayer.toLowerCase(Locale.ROOT);
pendingServerLookups.computeIfAbsent(key, k -> new ArrayList<>()).add(callback);
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bos)) {
out.writeUTF("GetPlayerServer");
out.writeUTF(targetPlayer);
sender.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
} catch (IOException e) {
plugin.getLogger().warning("GetPlayerServer Fehler: " + e.getMessage());
flushServerLookupCallbacks(key, null);
}
}
public void requestPlayerList(Player sender, String serverScope, Consumer<List<String>> callback) {
if (sender == null || !sender.isOnline()) {
callback.accept(List.of());
return;
}
String scope = serverScope == null || serverScope.isBlank() ? "ALL" : serverScope;
String key = scope.toUpperCase(Locale.ROOT);
pendingPlayerListLookups.computeIfAbsent(key, k -> new ArrayList<>()).add(callback);
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bos)) {
out.writeUTF("PlayerList");
out.writeUTF(scope);
sender.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
} catch (IOException e) {
plugin.getLogger().warning("PlayerList Fehler: " + e.getMessage());
flushPlayerListCallbacks(key, List.of());
}
}
// -------------------------------------------------------------------------
// Cross-server location query
// -------------------------------------------------------------------------
/**
* Sends a QUERY_LOCATION to {@code targetServer} asking for the exact
* coordinates of {@code targetPlayer}. The callback receives a Bukkit
* Location on success, or null if the player is not found / timeout fires.
*/
private void requestPlayerLocationOnServer(Player anchor, String targetPlayer,
String targetServer,
Consumer<org.bukkit.Location> callback) {
String key = targetPlayer.toLowerCase(Locale.ROOT);
pendingLocationLookups.computeIfAbsent(key, k -> new ArrayList<>()).add(callback);
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bos)) {
out.writeUTF("Forward");
out.writeUTF(targetServer);
out.writeUTF(TS_CHANNEL);
ByteArrayOutputStream sub = new ByteArrayOutputStream();
DataOutputStream subOut = new DataOutputStream(sub);
subOut.writeUTF("QUERY_LOCATION");
subOut.writeUTF(targetPlayer);
subOut.writeUTF(plugin.getConfigManager().getServerName()); // reply-to
byte[] subBytes = sub.toByteArray();
out.writeShort(subBytes.length);
out.write(subBytes);
anchor.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
} catch (IOException e) {
plugin.getLogger().warning("QUERY_LOCATION Fehler: " + e.getMessage());
flushLocationLookupCallbacks(key, null);
return;
}
// 1-second timeout → fall back to TP_TO_PLAYER polling
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
if (pendingLocationLookups.containsKey(key)) {
flushLocationLookupCallbacks(key, null);
}
}, 20L);
}
private void flushLocationLookupCallbacks(String key, org.bukkit.Location loc) {
List<Consumer<org.bukkit.Location>> callbacks = pendingLocationLookups.remove(key);
if (callbacks == null) return;
for (Consumer<org.bukkit.Location> cb : callbacks) cb.accept(loc);
}
// -------------------------------------------------------------------------
// Public teleport helpers
// -------------------------------------------------------------------------
public void teleportAllPlayersToLocalPlayer(Player requester) {
String requesterName = requester.getName();
requestPlayerList(requester, "ALL", players -> {
if (players == null || players.isEmpty()) {
requester.sendMessage("§cKeine Spieler gefunden.");
return;
}
Set<String> seen = new HashSet<>();
for (String playerName : players) {
if (playerName == null || playerName.isBlank()) continue;
if (playerName.equalsIgnoreCase(requesterName)) continue;
if (!seen.add(playerName.toLowerCase(Locale.ROOT))) continue;
teleportPlayerToLocalPlayer(requester, playerName, requesterName);
}
requester.sendMessage("§aAlle verfügbaren Spieler im Netzwerk werden zu dir teleportiert.");
});
}
/**
* Teleports {@code requester} directly to {@code targetPlayerName}.
*
* If the target is on another server:
* 1. Ask that server for the target's exact position (QUERY_LOCATION).
* 2. Send a LOCATION payload → player spawns at the right spot immediately.
* 3. Switch the requester via BungeeCord Connect.
* Fallback: if the query times out, send TP_TO_PLAYER (polling mechanism).
*/
public void teleportToPlayer(Player requester, String targetPlayerName) {
requestPlayerServer(requester, targetPlayerName, server -> {
if (server == null || server.isBlank()) {
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetPlayerName));
return;
}
String localServer = plugin.getConfigManager().getServerName();
if (server.equalsIgnoreCase(localServer)) {
// Same server — direct teleport
Player target = plugin.getServer().getPlayerExact(targetPlayerName);
if (target == null || !target.isOnline()) {
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetPlayerName));
return;
}
plugin.getTeleportManager().teleport(
requester,
new de.teleportsuite.models.TeleportLocation(target.getLocation(), localServer)
);
return;
}
// Cross-server: get exact coordinates first, then switch
requestPlayerLocationOnServer(requester, targetPlayerName, server, loc -> {
if (loc != null) {
// Precise LOCATION payload → no intermediate spawn
sendTeleportPayload(requester, requester.getName(), server,
loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ(),
loc.getYaw(), loc.getPitch());
} else {
// Fallback: target server will poll for both players
sendPlayerToPlayerPayload(requester, requester.getName(), targetPlayerName, server);
}
connectPlayerOnly(requester, server);
requester.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
});
});
}
/**
* Teleports {@code moverPlayerName} (on a remote server) to the local player
* {@code localTargetPlayerName}. The local player's current coordinates are
* known, so we can send a precise LOCATION payload immediately.
*/
public void teleportPlayerToLocalPlayer(Player requester, String moverPlayerName, String localTargetPlayerName) {
requestPlayerServer(requester, moverPlayerName, moverServer -> {
if (moverServer == null || moverServer.isBlank()) {
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", moverPlayerName));
return;
}
String localServer = plugin.getConfigManager().getServerName();
if (moverServer.equalsIgnoreCase(localServer)) {
// Same server — direct teleport
Player mover = plugin.getServer().getPlayerExact(moverPlayerName);
Player target = plugin.getServer().getPlayerExact(localTargetPlayerName);
if (mover == null || !mover.isOnline()) {
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", moverPlayerName));
return;
}
if (target == null || !target.isOnline()) {
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", localTargetPlayerName));
return;
}
plugin.getTeleportManager().teleport(
mover,
new de.teleportsuite.models.TeleportLocation(target.getLocation(), localServer)
);
return;
}
// Cross-server: the *target* is local, so we already know their position
Player localTarget = plugin.getServer().getPlayerExact(localTargetPlayerName);
if (localTarget == null || !localTarget.isOnline()) {
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", localTargetPlayerName));
return;
}
org.bukkit.Location dest = localTarget.getLocation();
// Send LOCATION payload to mover's current server first, then switch them
sendTeleportPayload(requester, moverPlayerName, localServer,
dest.getWorld().getName(), dest.getX(), dest.getY(), dest.getZ(),
dest.getYaw(), dest.getPitch());
connectOtherPlayer(requester, moverPlayerName, localServer);
requester.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
});
}
/**
* Teleports {@code moverPlayerName} to {@code targetPlayerName} when both
* may be on different servers.
*/
public void teleportAnyPlayerToAnyPlayer(Player requester, String moverPlayerName, String targetPlayerName) {
requestPlayerServer(requester, targetPlayerName, targetServer -> {
if (targetServer == null || targetServer.isBlank()) {
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetPlayerName));
return;
}
requestPlayerServer(requester, moverPlayerName, moverServer -> {
if (moverServer == null || moverServer.isBlank()) {
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", moverPlayerName));
return;
}
String localServer = plugin.getConfigManager().getServerName();
// Both on the same local server
if (moverServer.equalsIgnoreCase(localServer) && targetServer.equalsIgnoreCase(localServer)) {
Player mover = plugin.getServer().getPlayerExact(moverPlayerName);
Player target = plugin.getServer().getPlayerExact(targetPlayerName);
if (mover == null || !mover.isOnline()) {
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", moverPlayerName));
return;
}
if (target == null || !target.isOnline()) {
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetPlayerName));
return;
}
plugin.getTeleportManager().teleport(
mover,
new de.teleportsuite.models.TeleportLocation(target.getLocation(), localServer)
);
return;
}
// Target is on this local server — we know the coordinates
if (targetServer.equalsIgnoreCase(localServer)) {
Player localTarget = plugin.getServer().getPlayerExact(targetPlayerName);
if (localTarget == null || !localTarget.isOnline()) {
requester.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetPlayerName));
return;
}
org.bukkit.Location dest = localTarget.getLocation();
sendTeleportPayload(requester, moverPlayerName, localServer,
dest.getWorld().getName(), dest.getX(), dest.getY(), dest.getZ(),
dest.getYaw(), dest.getPitch());
if (!moverServer.equalsIgnoreCase(localServer)) {
connectOtherPlayer(requester, moverPlayerName, localServer);
}
requester.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
return;
}
// Target is on a third (remote) server — query their location first
requestPlayerLocationOnServer(requester, targetPlayerName, targetServer, loc -> {
if (loc != null) {
sendTeleportPayload(requester, moverPlayerName, targetServer,
loc.getWorld().getName(), loc.getX(), loc.getY(), loc.getZ(),
loc.getYaw(), loc.getPitch());
} else {
sendPlayerToPlayerPayload(requester, moverPlayerName, targetPlayerName, targetServer);
}
if (!moverServer.equalsIgnoreCase(targetServer)) {
connectOtherPlayer(requester, moverPlayerName, targetServer);
}
requester.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
});
});
});
}
// -------------------------------------------------------------------------
// TPA forwarding (unchanged)
// -------------------------------------------------------------------------
public void sendTpaRequestToPlayer(Player sender, String targetPlayerName) {
sendForwardToPlayer(sender, targetPlayerName, out -> {
out.writeUTF("TPA_REQUEST");
out.writeUTF(sender.getName());
out.writeUTF(targetPlayerName);
});
}
public void sendTpaAcceptToPlayer(Player sender, String requesterName) {
sendForwardToPlayer(sender, requesterName, out -> {
out.writeUTF("TPA_ACCEPT");
out.writeUTF(requesterName);
out.writeUTF(sender.getName());
});
}
public void sendTpaDenyToPlayer(Player sender, String requesterName) {
sendForwardToPlayer(sender, requesterName, out -> {
out.writeUTF("TPA_DENY");
out.writeUTF(requesterName);
out.writeUTF(sender.getName());
});
}
public void sendTpaExpiredToPlayer(Player sender, String targetPlayerName) {
sendForwardToPlayer(sender, targetPlayerName, out -> {
out.writeUTF("TPA_EXPIRED");
out.writeUTF(sender.getName());
out.writeUTF(targetPlayerName);
});
}
// -------------------------------------------------------------------------
// Low-level BungeeCord helpers
// -------------------------------------------------------------------------
/** Send player to another server AND pre-register their destination coordinates. */
public void connectToServer(Player player, String server, String world,
double x, double y, double z, float yaw, float pitch) {
sendTeleportPayload(player, player.getName(), server, world, x, y, z, yaw, pitch);
connectPlayerOnly(player, server);
}
private void connectPlayerOnly(Player player, String server) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bos)) { DataOutputStream out = new DataOutputStream(bos)) {
out.writeUTF("Connect"); out.writeUTF("Connect");
@@ -55,16 +400,31 @@ public class BungeeMessenger implements PluginMessageListener {
} }
} }
private void sendTeleportPayload(Player sender, String targetPlayer, String server, String world, private void connectOtherPlayer(Player anchor, String playerName, String server) {
double x, double y, double z, float yaw, float pitch) { try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bos)) {
out.writeUTF("ConnectOther");
out.writeUTF(playerName);
out.writeUTF(server);
anchor.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
} catch (IOException e) {
plugin.getLogger().warning("BungeeCord ConnectOther Fehler: " + e.getMessage());
}
}
/** Forward a LOCATION payload to {@code server} for {@code targetPlayer}. */
private void sendTeleportPayload(Player sender, String targetPlayer, String server,
String world, double x, double y, double z,
float yaw, float pitch) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bos)) { DataOutputStream out = new DataOutputStream(bos)) {
out.writeUTF("Forward"); out.writeUTF("Forward");
out.writeUTF(server); out.writeUTF(server);
out.writeUTF(TS_CHANNEL); out.writeUTF(TS_CHANNEL);
// Sub-payload
ByteArrayOutputStream sub = new ByteArrayOutputStream(); ByteArrayOutputStream sub = new ByteArrayOutputStream();
DataOutputStream subOut = new DataOutputStream(sub); DataOutputStream subOut = new DataOutputStream(sub);
subOut.writeUTF("LOCATION");
subOut.writeUTF(targetPlayer); subOut.writeUTF(targetPlayer);
subOut.writeUTF(world); subOut.writeUTF(world);
subOut.writeDouble(x); subOut.writeDouble(x);
@@ -72,6 +432,7 @@ public class BungeeMessenger implements PluginMessageListener {
subOut.writeDouble(z); subOut.writeDouble(z);
subOut.writeFloat(yaw); subOut.writeFloat(yaw);
subOut.writeFloat(pitch); subOut.writeFloat(pitch);
byte[] subBytes = sub.toByteArray(); byte[] subBytes = sub.toByteArray();
out.writeShort(subBytes.length); out.writeShort(subBytes.length);
out.write(subBytes); out.write(subBytes);
@@ -81,25 +442,279 @@ public class BungeeMessenger implements PluginMessageListener {
} }
} }
/**
* Fallback: forward a TP_TO_PLAYER payload.
* The target server will poll until both players are online.
*/
private void sendPlayerToPlayerPayload(Player sender, String moverPlayer,
String targetPlayer, String targetServer) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bos)) {
out.writeUTF("Forward");
out.writeUTF(targetServer);
out.writeUTF(TS_CHANNEL);
ByteArrayOutputStream sub = new ByteArrayOutputStream();
DataOutputStream subOut = new DataOutputStream(sub);
subOut.writeUTF("TP_TO_PLAYER");
subOut.writeUTF(moverPlayer);
subOut.writeUTF(targetPlayer);
byte[] subBytes = sub.toByteArray();
out.writeShort(subBytes.length);
out.write(subBytes);
sender.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
} catch (IOException e) {
plugin.getLogger().warning("TS Player->Player Forward Fehler: " + e.getMessage());
}
}
private void sendForwardToPlayer(Player sender, String targetPlayer,
IOConsumer<DataOutputStream> payloadWriter) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bos)) {
out.writeUTF("ForwardToPlayer");
out.writeUTF(targetPlayer);
out.writeUTF(TS_CHANNEL);
ByteArrayOutputStream sub = new ByteArrayOutputStream();
DataOutputStream subOut = new DataOutputStream(sub);
payloadWriter.accept(subOut);
byte[] subBytes = sub.toByteArray();
out.writeShort(subBytes.length);
out.write(subBytes);
sender.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
} catch (IOException e) {
plugin.getLogger().warning("TS ForwardToPlayer Fehler: " + e.getMessage());
}
}
// -------------------------------------------------------------------------
// Polling fallback (kept for TP_TO_PLAYER fallback path)
// -------------------------------------------------------------------------
private void schedulePlayerToPlayerTeleport(String moverName, String targetName) {
int maxAttempts = 100;
final int[] attempts = {0};
final int[] taskId = {0};
taskId[0] = plugin.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, () -> {
attempts[0]++;
Player mover = plugin.getServer().getPlayerExact(moverName);
Player target = plugin.getServer().getPlayerExact(targetName);
if (mover != null && mover.isOnline() && target != null && target.isOnline()) {
plugin.getTeleportManager().teleport(
mover,
new de.teleportsuite.models.TeleportLocation(
target.getLocation(), plugin.getConfigManager().getServerName()),
false
);
plugin.getServer().getScheduler().cancelTask(taskId[0]);
return;
}
if (attempts[0] >= maxAttempts) {
plugin.getServer().getScheduler().cancelTask(taskId[0]);
}
}, 10L, 10L);
}
// -------------------------------------------------------------------------
// Callback flush helpers
// -------------------------------------------------------------------------
private void flushServerLookupCallbacks(String key, String server) {
List<Consumer<String>> callbacks = pendingServerLookups.remove(key);
if (callbacks == null) return;
for (Consumer<String> callback : callbacks) callback.accept(server);
}
private void flushPlayerListCallbacks(String key, List<String> players) {
List<Consumer<List<String>>> callbacks = pendingPlayerListLookups.remove(key);
if (callbacks == null) return;
for (Consumer<List<String>> callback : callbacks) callback.accept(players);
}
// -------------------------------------------------------------------------
// Incoming message handler
// -------------------------------------------------------------------------
@Override @Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) { public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!channel.equals(TS_CHANNEL)) return; if (channel.equals(BUNGEE_CHANNEL)) {
handleBungeeMessage(message);
return;
}
if (channel.equals(TS_CHANNEL)) {
handleTeleportSuiteMessage(player, message);
}
}
private void handleBungeeMessage(byte[] message) {
try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(message))) { try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(message))) {
String targetName = in.readUTF(); String subChannel = in.readUTF();
if ("GetPlayerServer".equals(subChannel)) {
String playerName = in.readUTF();
String serverName = in.readUTF();
if ("null".equalsIgnoreCase(serverName) || serverName.isBlank()) serverName = null;
flushServerLookupCallbacks(playerName.toLowerCase(Locale.ROOT), serverName);
return;
}
if ("PlayerList".equals(subChannel)) {
String scope = in.readUTF();
String playerCsv = in.readUTF();
List<String> players = new ArrayList<>();
if (playerCsv != null && !playerCsv.isBlank()) {
for (String name : playerCsv.split(",")) {
String trimmed = name.trim();
if (!trimmed.isEmpty()) players.add(trimmed);
}
}
flushPlayerListCallbacks(scope.toUpperCase(Locale.ROOT), players);
}
} catch (IOException e) {
plugin.getLogger().warning("Fehler beim Lesen der Bungee-Nachricht: " + e.getMessage());
}
}
private void handleTeleportSuiteMessage(Player channelPlayer, byte[] message) {
try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(message))) {
String payloadType = in.readUTF();
// ── LOCATION: teleport a named player to fixed coordinates ──────────
if ("LOCATION".equals(payloadType)) {
String targetName = in.readUTF();
String world = in.readUTF();
double x = in.readDouble(), y = in.readDouble(), z = in.readDouble();
float yaw = in.readFloat(), pitch = in.readFloat();
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
Player target = plugin.getServer().getPlayerExact(targetName);
if (target == null || !target.isOnline()) return;
de.teleportsuite.models.TeleportLocation loc =
new de.teleportsuite.models.TeleportLocation(world, x, y, z, yaw, pitch,
plugin.getConfigManager().getServerName());
plugin.getTeleportManager().teleport(target, loc, false);
}, 20L);
return;
}
// ── QUERY_LOCATION: remote server asks for a player's coordinates ───
if ("QUERY_LOCATION".equals(payloadType)) {
String targetName = in.readUTF();
String replyServer = in.readUTF();
Player target = plugin.getServer().getPlayerExact(targetName);
if (target == null || !target.isOnline()) return; // can't answer
// We need a live player to send the reply message through
Player anchor = plugin.getServer().getOnlinePlayers().stream().findFirst().orElse(null);
if (anchor == null) return;
org.bukkit.Location loc = target.getLocation();
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bos)) {
out.writeUTF("Forward");
out.writeUTF(replyServer);
out.writeUTF(TS_CHANNEL);
ByteArrayOutputStream sub = new ByteArrayOutputStream();
DataOutputStream subOut = new DataOutputStream(sub);
subOut.writeUTF("LOCATION_RESPONSE");
subOut.writeUTF(targetName);
subOut.writeUTF(loc.getWorld().getName());
subOut.writeDouble(loc.getX());
subOut.writeDouble(loc.getY());
subOut.writeDouble(loc.getZ());
subOut.writeFloat(loc.getYaw());
subOut.writeFloat(loc.getPitch());
byte[] subBytes = sub.toByteArray();
out.writeShort(subBytes.length);
out.write(subBytes);
anchor.sendPluginMessage(plugin, BUNGEE_CHANNEL, bos.toByteArray());
} catch (IOException e) {
plugin.getLogger().warning("LOCATION_RESPONSE Fehler: " + e.getMessage());
}
return;
}
// ── LOCATION_RESPONSE: answer to our QUERY_LOCATION ─────────────────
if ("LOCATION_RESPONSE".equals(payloadType)) {
String targetName = in.readUTF();
String world = in.readUTF();
double x = in.readDouble(), y = in.readDouble(), z = in.readDouble();
float yaw = in.readFloat(), pitch = in.readFloat();
org.bukkit.World w = plugin.getServer().getWorld(world);
// w may be null here (different server), that's fine — callers only need coords
org.bukkit.Location loc = new org.bukkit.Location(w, x, y, z, yaw, pitch);
flushLocationLookupCallbacks(targetName.toLowerCase(Locale.ROOT), loc);
return;
}
// ── TP_TO_PLAYER: fallback polling mechanism ─────────────────────────
if ("TP_TO_PLAYER".equals(payloadType)) {
String moverName = in.readUTF();
String targetName = in.readUTF();
schedulePlayerToPlayerTeleport(moverName, targetName);
return;
}
// ── TPA messages ─────────────────────────────────────────────────────
if ("TPA_REQUEST".equals(payloadType)) {
String requesterName = in.readUTF();
String targetName = in.readUTF();
plugin.getTeleportManager().receiveCrossServerTpaRequest(requesterName, targetName);
return;
}
if ("TPA_ACCEPT".equals(payloadType)) {
String requesterName = in.readUTF();
String targetName = in.readUTF();
plugin.getTeleportManager().receiveCrossServerTpaAccepted(requesterName, targetName);
return;
}
if ("TPA_DENY".equals(payloadType)) {
String requesterName = in.readUTF();
String targetName = in.readUTF();
plugin.getTeleportManager().receiveCrossServerTpaDenied(requesterName, targetName);
return;
}
if ("TPA_EXPIRED".equals(payloadType)) {
String requesterName = in.readUTF();
String targetName = in.readUTF();
plugin.getTeleportManager().receiveCrossServerTpaExpired(requesterName, targetName);
return;
}
// ── Backward compatibility: old payload had no type prefix ────────────
String targetName = payloadType; // first field WAS the player name
String world = in.readUTF(); String world = in.readUTF();
double x = in.readDouble(), y = in.readDouble(), z = in.readDouble(); double x = in.readDouble(), y = in.readDouble(), z = in.readDouble();
float yaw = in.readFloat(), pitch = in.readFloat(); float yaw = in.readFloat(), pitch = in.readFloat();
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> { plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
Player target = plugin.getServer().getPlayer(targetName); Player target = plugin.getServer().getPlayerExact(targetName);
if (target == null || !target.isOnline()) return; if (target == null || !target.isOnline()) return;
de.teleportsuite.models.TeleportLocation loc = de.teleportsuite.models.TeleportLocation loc =
new de.teleportsuite.models.TeleportLocation(world, x, y, z, yaw, pitch, new de.teleportsuite.models.TeleportLocation(world, x, y, z, yaw, pitch,
plugin.getConfigManager().getServerName()); plugin.getConfigManager().getServerName());
plugin.getTeleportManager().teleport(target, loc, false); plugin.getTeleportManager().teleport(target, loc, false);
}, 20L); }, 20L);
} catch (IOException e) { } catch (IOException e) {
plugin.getLogger().warning("Fehler beim Lesen der TS-Nachricht: " + e.getMessage()); plugin.getLogger().warning("Fehler beim Lesen der TS-Nachricht: " + e.getMessage());
} }
} }
@FunctionalInterface
private interface IOConsumer<T> {
void accept(T value) throws IOException;
}
} }

View File

@@ -12,6 +12,12 @@ public class TpAllCommand implements CommandExecutor {
if (!(sender instanceof Player)) { sender.sendMessage("§cNur für Spieler!"); return true; } if (!(sender instanceof Player)) { sender.sendMessage("§cNur für Spieler!"); return true; }
Player p = (Player) sender; Player p = (Player) sender;
if (!p.hasPermission("teleportsuite.tpall")) { p.sendMessage(plugin.getConfigManager().getMessage("no-permission")); return true; } if (!p.hasPermission("teleportsuite.tpall")) { p.sendMessage(plugin.getConfigManager().getMessage("no-permission")); return true; }
if (plugin.getConfigManager().isBungeeEnabled() && plugin.getBungeeMessenger() != null) {
plugin.getBungeeMessenger().teleportAllPlayersToLocalPlayer(p);
return true;
}
TeleportLocation dest = new TeleportLocation(p.getLocation(), plugin.getConfigManager().getServerName()); TeleportLocation dest = new TeleportLocation(p.getLocation(), plugin.getConfigManager().getServerName());
for (Player online : Bukkit.getOnlinePlayers()) { for (Player online : Bukkit.getOnlinePlayers()) {
if (!online.equals(p)) plugin.getTeleportManager().teleport(online, dest); if (!online.equals(p)) plugin.getTeleportManager().teleport(online, dest);

View File

@@ -19,13 +19,27 @@ public class TpCommand implements CommandExecutor {
if (args.length == 1) { if (args.length == 1) {
Player target = Bukkit.getPlayer(args[0]); Player target = Bukkit.getPlayer(args[0]);
if (target == null) { p.sendMessage(plugin.getConfigManager().getMessage("player-not-found","player",args[0])); return true; } if (target == null) {
if (plugin.getConfigManager().isBungeeEnabled() && plugin.getBungeeMessenger() != null) {
plugin.getBungeeMessenger().teleportToPlayer(p, args[0]);
return true;
}
p.sendMessage(plugin.getConfigManager().getMessage("player-not-found","player",args[0]));
return true;
}
plugin.getTeleportManager().teleport(p, new TeleportLocation(target.getLocation(), plugin.getConfigManager().getServerName())); plugin.getTeleportManager().teleport(p, new TeleportLocation(target.getLocation(), plugin.getConfigManager().getServerName()));
} else { } else {
if (!p.hasPermission("teleportsuite.admin")) { p.sendMessage(plugin.getConfigManager().getMessage("no-permission")); return true; }
Player from = Bukkit.getPlayer(args[0]); Player from = Bukkit.getPlayer(args[0]);
Player to = Bukkit.getPlayer(args[1]); Player to = Bukkit.getPlayer(args[1]);
if (from == null || to == null) { p.sendMessage("§cEin Spieler nicht gefunden."); return true; } if (from == null || to == null) {
if (!p.hasPermission("teleportsuite.admin")) { p.sendMessage(plugin.getConfigManager().getMessage("no-permission")); return true; } if (plugin.getConfigManager().isBungeeEnabled() && plugin.getBungeeMessenger() != null) {
plugin.getBungeeMessenger().teleportAnyPlayerToAnyPlayer(p, args[0], args[1]);
return true;
}
p.sendMessage("§cEin Spieler nicht gefunden.");
return true;
}
plugin.getTeleportManager().teleport(from, new TeleportLocation(to.getLocation(), plugin.getConfigManager().getServerName())); plugin.getTeleportManager().teleport(from, new TeleportLocation(to.getLocation(), plugin.getConfigManager().getServerName()));
} }
return true; return true;

View File

@@ -17,7 +17,14 @@ public class TpHereCommand implements CommandExecutor {
if (!p.hasPermission("teleportsuite.tphere")) { p.sendMessage(plugin.getConfigManager().getMessage("no-permission")); return true; } if (!p.hasPermission("teleportsuite.tphere")) { p.sendMessage(plugin.getConfigManager().getMessage("no-permission")); return true; }
if (args.length < 1) { p.sendMessage("§cVerwendung: /tphere <spieler>"); return true; } if (args.length < 1) { p.sendMessage("§cVerwendung: /tphere <spieler>"); return true; }
Player target = Bukkit.getPlayer(args[0]); Player target = Bukkit.getPlayer(args[0]);
if (target == null) { p.sendMessage(plugin.getConfigManager().getMessage("player-not-found","player",args[0])); return true; } if (target == null) {
if (plugin.getConfigManager().isBungeeEnabled() && plugin.getBungeeMessenger() != null) {
plugin.getBungeeMessenger().teleportPlayerToLocalPlayer(p, args[0], p.getName());
return true;
}
p.sendMessage(plugin.getConfigManager().getMessage("player-not-found","player",args[0]));
return true;
}
plugin.getTeleportManager().teleport(target, new TeleportLocation(p.getLocation(), plugin.getConfigManager().getServerName())); plugin.getTeleportManager().teleport(target, new TeleportLocation(p.getLocation(), plugin.getConfigManager().getServerName()));
return true; return true;
} }

View File

@@ -15,9 +15,16 @@ public class TpaCommand implements CommandExecutor {
Player p = (Player) sender; Player p = (Player) sender;
if (!p.hasPermission("teleportsuite.tpa")) { p.sendMessage(plugin.getConfigManager().getMessage("no-permission")); return true; } if (!p.hasPermission("teleportsuite.tpa")) { p.sendMessage(plugin.getConfigManager().getMessage("no-permission")); return true; }
if (args.length < 1) { p.sendMessage("§cVerwendung: /tpa <spieler>"); return true; } if (args.length < 1) { p.sendMessage("§cVerwendung: /tpa <spieler>"); return true; }
if (p.getName().equalsIgnoreCase(args[0])) { p.sendMessage("§cDu kannst dir nicht selbst eine Anfrage senden."); return true; }
Player target = Bukkit.getPlayer(args[0]); Player target = Bukkit.getPlayer(args[0]);
if (target == null) { p.sendMessage(plugin.getConfigManager().getMessage("player-not-found","player",args[0])); return true; } if (target == null) {
if (target.equals(p)) { p.sendMessage("§cDu kannst dir nicht selbst eine Anfrage senden."); return true; } if (plugin.getConfigManager().isBungeeEnabled() && plugin.getBungeeMessenger() != null) {
plugin.getTeleportManager().sendTpaRequest(p, args[0]);
return true;
}
p.sendMessage(plugin.getConfigManager().getMessage("player-not-found","player",args[0]));
return true;
}
plugin.getTeleportManager().sendTpaRequest(p, target); plugin.getTeleportManager().sendTpaRequest(p, target);
return true; return true;
} }

View File

@@ -13,6 +13,9 @@ public class TeleportManager {
// Pending TPA requests: requester -> target // Pending TPA requests: requester -> target
private final Map<UUID, UUID> tpaRequests = new HashMap<>(); private final Map<UUID, UUID> tpaRequests = new HashMap<>();
private final Map<UUID, Long> requestTimestamps = new HashMap<>(); private final Map<UUID, Long> requestTimestamps = new HashMap<>();
// Pending cross-server TPA requests: requesterName -> targetName
private final Map<String, String> crossServerTpaRequests = new HashMap<>();
private final Map<String, Long> crossServerRequestTimestamps = new HashMap<>();
// Cooldowns // Cooldowns
private final Map<UUID, Long> cooldowns = new HashMap<>(); private final Map<UUID, Long> cooldowns = new HashMap<>();
// Warmup tasks // Warmup tasks
@@ -70,8 +73,15 @@ public class TeleportManager {
private void executeTeleport(Player player, TeleportLocation dest) { private void executeTeleport(Player player, TeleportLocation dest) {
String localServer = plugin.getConfigManager().getServerName(); String localServer = plugin.getConfigManager().getServerName();
// Always save last location and apply cooldown, regardless of server
plugin.getDatabaseManager().saveLastLocation(player.getUniqueId(),
new TeleportLocation(player.getLocation(), localServer));
cooldowns.put(player.getUniqueId(), System.currentTimeMillis());
if (!dest.isLocalServer(localServer)) { if (!dest.isLocalServer(localServer)) {
// BungeeCord-Teleport // BungeeCord cross-server teleport:
// connectToServer sends the LOCATION payload BEFORE the Connect message
// so the player arrives directly at the target coordinates.
if (plugin.getBungeeMessenger() != null) { if (plugin.getBungeeMessenger() != null) {
plugin.getBungeeMessenger().connectToServer(player, dest.getServer(), dest.getWorld(), plugin.getBungeeMessenger().connectToServer(player, dest.getServer(), dest.getWorld(),
dest.getX(), dest.getY(), dest.getZ(), dest.getYaw(), dest.getPitch()); dest.getX(), dest.getY(), dest.getZ(), dest.getYaw(), dest.getPitch());
@@ -82,16 +92,11 @@ public class TeleportManager {
Location loc = dest.toBukkitLocation(); Location loc = dest.toBukkitLocation();
if (loc == null || loc.getWorld() == null) { if (loc == null || loc.getWorld() == null) {
player.sendMessage("§cZielwelt nicht gefunden!"); player.sendMessage("\u00a7cZielwelt nicht gefunden!");
return; return;
} }
// Save last location to database
plugin.getDatabaseManager().saveLastLocation(player.getUniqueId(),
new TeleportLocation(player.getLocation(), localServer));
player.teleport(loc); player.teleport(loc);
cooldowns.put(player.getUniqueId(), System.currentTimeMillis());
player.sendMessage(plugin.getConfigManager().getMessage("teleport-success")); player.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
} }
@@ -122,6 +127,101 @@ public class TeleportManager {
}, timeout * 20L); }, timeout * 20L);
} }
public void sendTpaRequest(Player from, String targetName) {
if (plugin.getBungeeMessenger() == null) {
from.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetName));
return;
}
plugin.getBungeeMessenger().requestPlayerServer(from, targetName, server -> {
if (server == null || server.isBlank()) {
from.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetName));
return;
}
String localServer = plugin.getConfigManager().getServerName();
if (server.equalsIgnoreCase(localServer)) {
Player localTarget = Bukkit.getPlayerExact(targetName);
if (localTarget == null || !localTarget.isOnline()) {
from.sendMessage(plugin.getConfigManager().getMessage("player-not-found", "player", targetName));
return;
}
sendTpaRequest(from, localTarget);
return;
}
String requesterKey = from.getName();
crossServerTpaRequests.put(requesterKey, targetName);
crossServerRequestTimestamps.put(requesterKey, System.currentTimeMillis());
plugin.getBungeeMessenger().sendTpaRequestToPlayer(from, targetName);
from.sendMessage(plugin.getConfigManager().getMessage("tpa-sent", "player", targetName));
int timeout = plugin.getConfigManager().getRequestTimeout();
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> {
if (!crossServerTpaRequests.containsKey(requesterKey)) return;
crossServerTpaRequests.remove(requesterKey);
crossServerRequestTimestamps.remove(requesterKey);
Player requester = Bukkit.getPlayerExact(from.getName());
if (requester != null && requester.isOnline()) {
requester.sendMessage(plugin.getConfigManager().getMessage("tpa-expired"));
}
if (plugin.getBungeeMessenger() != null) {
plugin.getBungeeMessenger().sendTpaExpiredToPlayer(from, targetName);
}
}, timeout * 20L);
});
}
public void receiveCrossServerTpaRequest(String requesterName, String targetName) {
Player target = Bukkit.getPlayerExact(targetName);
if (target == null || !target.isOnline()) return;
crossServerTpaRequests.put(requesterName, targetName);
crossServerRequestTimestamps.put(requesterName, System.currentTimeMillis());
target.sendMessage(plugin.getConfigManager().getMessage("tpa-received", "player", requesterName));
}
public void receiveCrossServerTpaAccepted(String requesterName, String targetName) {
Player requester = Bukkit.getPlayerExact(requesterName);
if (requester == null || !requester.isOnline()) return;
String requesterKey = findRequesterKey(requesterName);
if (requesterKey != null) {
crossServerTpaRequests.remove(requesterKey);
crossServerRequestTimestamps.remove(requesterKey);
}
requester.sendMessage(plugin.getConfigManager().getMessage("tpa-accepted", "player", targetName));
if (plugin.getBungeeMessenger() != null) {
plugin.getBungeeMessenger().teleportToPlayer(requester, targetName);
}
}
public void receiveCrossServerTpaDenied(String requesterName, String targetName) {
Player requester = Bukkit.getPlayerExact(requesterName);
if (requester == null || !requester.isOnline()) return;
String requesterKey = findRequesterKey(requesterName);
if (requesterKey != null) {
crossServerTpaRequests.remove(requesterKey);
crossServerRequestTimestamps.remove(requesterKey);
}
requester.sendMessage(plugin.getConfigManager().getMessage("tpa-denied", "player", targetName));
}
public void receiveCrossServerTpaExpired(String requesterName, String targetName) {
Player target = Bukkit.getPlayerExact(targetName);
if (target == null || !target.isOnline()) return;
String requesterKey = findRequesterKey(requesterName);
if (requesterKey != null && target.getName().equalsIgnoreCase(crossServerTpaRequests.get(requesterKey))) {
crossServerTpaRequests.remove(requesterKey);
crossServerRequestTimestamps.remove(requesterKey);
}
}
public boolean acceptTpa(Player target) { public boolean acceptTpa(Player target) {
UUID requester = null; UUID requester = null;
for (Map.Entry<UUID, UUID> entry : tpaRequests.entrySet()) { for (Map.Entry<UUID, UUID> entry : tpaRequests.entrySet()) {
@@ -130,15 +230,27 @@ public class TeleportManager {
break; break;
} }
} }
if (requester == null) return false; if (requester != null) {
Player from = Bukkit.getPlayer(requester);
tpaRequests.remove(requester);
requestTimestamps.remove(requester);
Player from = Bukkit.getPlayer(requester); if (from != null && from.isOnline()) {
tpaRequests.remove(requester); from.sendMessage(plugin.getConfigManager().getMessage("tpa-accepted", "player", target.getName()));
requestTimestamps.remove(requester); teleport(from, new TeleportLocation(target.getLocation(), plugin.getConfigManager().getServerName()));
}
return true;
}
if (from != null && from.isOnline()) { String requesterName = findRequesterByTarget(target.getName());
from.sendMessage(plugin.getConfigManager().getMessage("tpa-accepted", "player", target.getName()));
teleport(from, new TeleportLocation(target.getLocation(), plugin.getConfigManager().getServerName())); if (requesterName == null) return false;
crossServerTpaRequests.remove(requesterName);
crossServerRequestTimestamps.remove(requesterName);
if (plugin.getBungeeMessenger() != null) {
plugin.getBungeeMessenger().sendTpaAcceptToPlayer(target, requesterName);
} }
return true; return true;
} }
@@ -151,14 +263,45 @@ public class TeleportManager {
break; break;
} }
} }
if (requester == null) return false; if (requester != null) {
Player from = Bukkit.getPlayer(requester);
tpaRequests.remove(requester);
requestTimestamps.remove(requester);
Player from = Bukkit.getPlayer(requester); if (from != null && from.isOnline()) {
tpaRequests.remove(requester); from.sendMessage(plugin.getConfigManager().getMessage("tpa-denied", "player", target.getName()));
requestTimestamps.remove(requester); }
return true;
}
if (from != null && from.isOnline()) String requesterName = findRequesterByTarget(target.getName());
from.sendMessage(plugin.getConfigManager().getMessage("tpa-denied", "player", target.getName()));
if (requesterName == null) return false;
crossServerTpaRequests.remove(requesterName);
crossServerRequestTimestamps.remove(requesterName);
if (plugin.getBungeeMessenger() != null) {
plugin.getBungeeMessenger().sendTpaDenyToPlayer(target, requesterName);
}
return true; return true;
} }
private String findRequesterByTarget(String targetName) {
for (Map.Entry<String, String> entry : crossServerTpaRequests.entrySet()) {
if (targetName.equalsIgnoreCase(entry.getValue())) {
return entry.getKey();
}
}
return null;
}
private String findRequesterKey(String requesterName) {
for (String key : crossServerTpaRequests.keySet()) {
if (key.equalsIgnoreCase(requesterName)) {
return key;
}
}
return null;
}
} }

View File

@@ -1,5 +1,5 @@
name: TeleportSuite name: TeleportSuite
version: 1.0.0 version: 1.0.1
main: de.teleportsuite.TeleportSuite main: de.teleportsuite.TeleportSuite
api-version: 1.20 api-version: 1.20
description: BungeeCord-fähiges Teleport-Komplettpaket description: BungeeCord-fähiges Teleport-Komplettpaket