Upload folder via GUI - src
This commit is contained in:
@@ -5,6 +5,14 @@ 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.
|
||||||
@@ -22,12 +30,15 @@ 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<>();
|
||||||
|
|
||||||
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,6 +47,199 @@ public class BungeeMessenger implements PluginMessageListener {
|
|||||||
plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin);
|
plugin.getServer().getMessenger().unregisterIncomingPluginChannel(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendPlayerToPlayerPayload(requester, requester.getName(), targetPlayerName, server);
|
||||||
|
connectPlayerOnly(requester, server);
|
||||||
|
requester.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendPlayerToPlayerPayload(requester, moverPlayerName, localTargetPlayerName, localServer);
|
||||||
|
connectOtherPlayer(requester, moverPlayerName, localServer);
|
||||||
|
requester.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendPlayerToPlayerPayload(requester, moverPlayerName, targetPlayerName, targetServer);
|
||||||
|
if (!moverServer.equalsIgnoreCase(targetServer)) {
|
||||||
|
connectOtherPlayer(requester, moverPlayerName, targetServer);
|
||||||
|
}
|
||||||
|
requester.sendMessage(plugin.getConfigManager().getMessage("teleport-success"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect a player to another BungeeCord server.
|
* Connect a player to another BungeeCord server.
|
||||||
* Also sends a plugin message so the target server knows where to teleport the player.
|
* Also sends a plugin message so the target server knows where to teleport the player.
|
||||||
@@ -45,6 +249,10 @@ public class BungeeMessenger implements PluginMessageListener {
|
|||||||
sendTeleportPayload(player, player.getName(), server, world, x, y, z, yaw, pitch);
|
sendTeleportPayload(player, player.getName(), server, world, x, y, z, yaw, pitch);
|
||||||
|
|
||||||
// 2) Switch server via BungeeCord
|
// 2) Switch server via BungeeCord
|
||||||
|
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,6 +263,18 @@ public class BungeeMessenger implements PluginMessageListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void connectOtherPlayer(Player anchor, String playerName, String server) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void sendTeleportPayload(Player sender, String targetPlayer, String server, String world,
|
private void sendTeleportPayload(Player sender, String targetPlayer, String server, String world,
|
||||||
double x, double y, double z, float yaw, float pitch) {
|
double x, double y, double z, float yaw, float pitch) {
|
||||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
@@ -65,6 +285,7 @@ public class BungeeMessenger implements PluginMessageListener {
|
|||||||
// Sub-payload
|
// 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);
|
||||||
@@ -81,17 +302,196 @@ public class BungeeMessenger implements PluginMessageListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@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(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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.getLogger().warning("Fehler beim Lesen der Bungee-Nachricht: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTeleportSuiteMessage(byte[] message) {
|
||||||
|
try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(message))) {
|
||||||
|
String payloadType = in.readUTF();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("TP_TO_PLAYER".equals(payloadType)) {
|
||||||
|
String moverName = in.readUTF();
|
||||||
|
String targetName = in.readUTF();
|
||||||
|
schedulePlayerToPlayerTeleport(moverName, targetName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 with old payload format
|
||||||
|
String targetName = payloadType;
|
||||||
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,
|
||||||
@@ -102,4 +502,9 @@ public class BungeeMessenger implements PluginMessageListener {
|
|||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -122,6 +125,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 +228,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 +261,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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user