diff --git a/gradle.properties b/gradle.properties index 9393d37..9137542 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -plugin_version=3.0.4-INDEV +plugin_version=3.0.5-INDEV velocity_api_version=3.3.0-SNAPSHOT minecraft_version=1.20.6 diff --git a/src/main/java/org/zeroBzeroT/anarchyqueue/Config.java b/src/main/java/org/zeroBzeroT/anarchyqueue/Config.java index 66a6820..c7de62f 100644 --- a/src/main/java/org/zeroBzeroT/anarchyqueue/Config.java +++ b/src/main/java/org/zeroBzeroT/anarchyqueue/Config.java @@ -9,9 +9,9 @@ import java.nio.file.Path; public class Config { - public static String serverMain = null; + public static String target = null; - public static String serverQueue = null; + public static String queue = null; public static String name = null; // TODO: not in use, implement or remove this @@ -21,15 +21,13 @@ public class Config { public static String messageConnecting = null; - public static String messageFull = null; + public static String messageFull = null; // TODO: not in use, implement or remove this - public static String messageOffline = null; + public static String messageOffline = null; // TODO: not in use, implement or remove this - public static boolean kick = true; // TODO: not in use, implement or remove this + public static boolean kick = true; - public static int waitOnKick = 0; // TODO: not in use, implement or remove this - - public static long joinDelay = 0; + public static int waitOnKick = 16; /** * Load the config from the plugin data folder @@ -57,8 +55,8 @@ static void loadConfig(Path path) throws IOException { } Toml toml = new Toml().read(file); - serverMain = toml.getString("server-main", "main"); - serverQueue = toml.getString("server-queue", "queue"); + target = toml.getString("target", "main"); + queue = toml.getString("queue", "queue"); name = toml.getString("name", "0b0t"); maxPlayers = toml.getLong("max-players", 420L).intValue(); messagePosition = toml.getString("message-position", "Position in queue: "); @@ -67,6 +65,5 @@ static void loadConfig(Path path) throws IOException { messageOffline = toml.getString("message-offline", "Server is currently down!"); kick = toml.getBoolean("kick", true); waitOnKick = toml.getLong("wait-on-kick", 16L).intValue(); - joinDelay = toml.getLong("join-delay", 0L); } } diff --git a/src/main/java/org/zeroBzeroT/anarchyqueue/Queue.java b/src/main/java/org/zeroBzeroT/anarchyqueue/Queue.java index d5cfe4c..6ab0992 100644 --- a/src/main/java/org/zeroBzeroT/anarchyqueue/Queue.java +++ b/src/main/java/org/zeroBzeroT/anarchyqueue/Queue.java @@ -3,6 +3,7 @@ import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.player.KickedFromServerEvent; import com.velocitypowered.api.event.player.ServerConnectedEvent; +import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.server.RegisteredServer; import net.kyori.adventure.text.Component; @@ -10,10 +11,7 @@ import java.time.Duration; import java.time.Instant; -import java.util.Deque; -import java.util.LinkedList; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.Semaphore; @@ -27,9 +25,11 @@ public class Queue { private final ProxyServer proxyServer; - private final Semaphore queueSemaphore = new Semaphore(1); + private final Semaphore mutex = new Semaphore(1); - private final Deque queuedPlayers = new LinkedList<>(); + private final Deque playerQueue; + + private final HashMap kickedPlayers; /** * Initializes the queue. @@ -37,6 +37,8 @@ public class Queue { public Queue(ProxyServer proxyServer) { this.log = Main.getInstance().log; this.proxyServer = proxyServer; + playerQueue = new LinkedList<>(); + kickedPlayers = new HashMap<>(); // Schedule queue flusher proxyServer.getScheduler() @@ -44,6 +46,20 @@ public Queue(ProxyServer proxyServer) { .delay(Duration.ofSeconds(1)) .repeat(Duration.ofSeconds(2)) .schedule(); + + // Schedule player notification + proxyServer.getScheduler() + .buildTask(Main.getInstance(), this::sendUpdate) + .delay(Duration.ofSeconds(1)) + .repeat(Duration.ofSeconds(10)) + .schedule(); + + // Schedule kicked players cool down + proxyServer.getScheduler() + .buildTask(Main.getInstance(), () -> kickedPlayers.entrySet().removeIf(pair -> pair.getValue() + Config.waitOnKick < Instant.now().getEpochSecond())) + .delay(Duration.ofSeconds(0)) + .repeat(Duration.ofSeconds(1)) + .schedule(); } /** @@ -52,22 +68,22 @@ public Queue(ProxyServer proxyServer) { */ @Subscribe public void onServerConnected(ServerConnectedEvent event) { - if (!event.getServer().getServerInfo().getName().equals(Config.serverQueue)) + if (!event.getServer().getServerInfo().getName().equals(Config.queue)) return; // stop adding the player several times - if (queuedPlayers.stream().anyMatch(p -> p.player() == event.getPlayer())) + if (playerQueue.contains(event.getPlayer())) return; // Add Player to queue try { - queueSemaphore.acquire(); - queuedPlayers.add(new QueuedPlayer(event.getPlayer(), System.currentTimeMillis())); - log.info(mm("" + event.getPlayer().getUsername() + " was added to the queue. Queue count is " + queuedPlayers.size() + ".")); + mutex.acquire(); + playerQueue.add(event.getPlayer()); + log.info(mm("" + event.getPlayer().getUsername() + " was added to the queue. Queue count is " + playerQueue.size() + ".")); } catch (InterruptedException e1) { e1.printStackTrace(); } finally { - queueSemaphore.release(); + mutex.release(); } } @@ -76,7 +92,31 @@ public void onServerConnected(ServerConnectedEvent event) { */ @Subscribe public void onKickedFromServer(KickedFromServerEvent event) { - log.info(mm("" + event.getPlayer().getUsername() + " was kicked from " + event.getServer().getServerInfo().getName() + " for ").append(event.getServerKickReason().isPresent() ? event.getServerKickReason().get() : mm("")).append(mm("."))); + if (event.getServer().getServerInfo().getName().equals(Config.target)) { + // kick from target server + try { + mutex.acquire(); + Player player = event.getPlayer(); + Component reason = event.getServerKickReason().isPresent() ? event.getServerKickReason().get() : mm("Kicked without a reason."); + + // - kicking is not enabled + if (!Config.kick) { + // save the disconnection time + kickedPlayers.put(player, Instant.now().getEpochSecond()); + + // send message + player.sendMessage(mm("You were sent back to the queue for: ").append(reason).append(mm(""))); + log.info(mm("" + player.getUsername() + " was sent back to server " + Config.queue + " after a disconnection (\"").append(reason).append(mm("\"). Kicked count is " + kickedPlayers.size() + "."))); + } else { + // set the disconnect reason from the target server (not the bungee message) + player.disconnect(reason); + } + } catch (InterruptedException e1) { + e1.printStackTrace(); + } finally { + mutex.release(); + } + } } /** @@ -84,14 +124,14 @@ public void onKickedFromServer(KickedFromServerEvent event) { */ public void flushQueue() { // Ignore if queue is empty - if (queuedPlayers.isEmpty()) + if (playerQueue.isEmpty()) return; // check queue server reachability final RegisteredServer serverQueue; try { - serverQueue = getServer(Config.serverQueue); + serverQueue = getServer(Config.queue); } catch (ServerNotReachableException e) { log.warn(e.getMessage()); return; @@ -101,34 +141,25 @@ public void flushQueue() { final RegisteredServer serverMain; try { - serverMain = getServer(Config.serverMain); + serverMain = getServer(Config.target); } catch (ServerNotReachableException e) { - if (Instant.now().getEpochSecond() % 10 == 0) { - serverQueue.getPlayersConnected().forEach(queuedPlayer -> queuedPlayer.sendMessage(Component.text(Config.messageOffline))); - } - + // TODO: offline notification return; } // check main server full - boolean full = serverMain.getPlayersConnected().size() >= Config.maxPlayers; - - // send info every 10 seconds - if (Instant.now().getEpochSecond() % 10 == 0) { - sendInfo(serverQueue, full); - } - - if (full) + if (serverMain.getPlayersConnected().size() >= Config.maxPlayers) + // TODO: full notification return; try { - queueSemaphore.acquire(); + mutex.acquire(); - QueuedPlayer currPlayer = null; + Player currPlayer = null; // try to find the first player that got not kicked recently - for (QueuedPlayer testPlayer : queuedPlayers) { - if (testPlayer.queueTime() + Config.joinDelay > System.currentTimeMillis()) + for (Player testPlayer : playerQueue) { + if (kickedPlayers.containsKey(testPlayer)) continue; currPlayer = testPlayer; @@ -136,57 +167,57 @@ public void flushQueue() { } if (currPlayer == null) { - queueSemaphore.release(); + mutex.release(); return; } // connect next player - UUID uuid = currPlayer.player().getUniqueId(); - - // lookup player from queue server and ping to be safe the player is connected - QueuedPlayer finalCurrPlayer = currPlayer; + UUID uuid = currPlayer.getUniqueId(); + Player finalCurrPlayer = currPlayer; serverQueue.getPlayersConnected().stream() .filter(p -> p.getUniqueId().equals(uuid)) .findAny().ifPresentOrElse(p -> { - p.sendMessage(Component.text(Config.messageConnecting)); + // TODO: direct connection to the main server if the queue is empty + p.sendMessage(mm(Config.messageConnecting)); try { if (p.createConnectionRequest(serverMain).connect().get().isSuccessful()) { - queuedPlayers.removeFirst(); + playerQueue.removeFirst(); log.info(mm("" + p.getUsername() + " connected to server " + serverMain.getServerInfo().getName() + ". Queue count is " + serverQueue.getPlayersConnected().size() + ". Main count is " + (serverMain.getPlayersConnected().size()) + " of " + Config.maxPlayers + ".")); } } catch (InterruptedException | ExecutionException e) { - log.error(mm("" + p.getUsername() + "s connection to server " + Config.serverMain + " failed: " + e.getMessage())); - // FIXME: requeue - queuedPlayers.removeFirst(); - queuedPlayers.add(new QueuedPlayer(finalCurrPlayer.player(), System.currentTimeMillis())); + log.error(mm("" + p.getUsername() + "s connection to server " + Config.target + " failed: " + e.getMessage())); + // count that as a kick ;) + kickedPlayers.put(finalCurrPlayer, Instant.now().getEpochSecond()); } }, () -> { - log.error(mm("" + finalCurrPlayer.player().getUsername() + "s connection to server " + Config.serverMain + " failed: player is not connected to " + serverQueue.getServerInfo().getName())); - queuedPlayers.removeFirst(); + log.error(mm("" + finalCurrPlayer.getUsername() + "s connection to server " + Config.target + " failed: player is not connected to " + serverQueue.getServerInfo().getName())); + playerQueue.removeFirst(); } ); } catch (InterruptedException e) { e.printStackTrace(); } finally { - queueSemaphore.release(); + mutex.release(); } } - private void sendInfo(RegisteredServer serverQueue, boolean full) { + /** + * Tells players their queue position + */ + public void sendUpdate() { int i = 1; - for (QueuedPlayer player : queuedPlayers) { - player.player().sendMessage(Component.text(Config.messagePosition + (i + 1) + "/" + queuedPlayers.size())); + for (Player player : playerQueue) { + player.sendMessage(mm(Config.messagePosition.replaceAll("%position%", Integer.toString(i)).replaceAll("%size%", Integer.toString(playerQueue.size())))); i++; } - - if (full) { - serverQueue.getPlayersConnected().forEach(queuedPlayer -> queuedPlayer.sendMessage(Component.text(Config.messageFull))); - } } + /** + * Test a server connection and return the server object. + */ private RegisteredServer getServer(String name) throws ServerNotReachableException { // Get server configured in velocity.toml by name Optional serverOpt = proxyServer.getServer(name); diff --git a/src/main/java/org/zeroBzeroT/anarchyqueue/QueuedPlayer.java b/src/main/java/org/zeroBzeroT/anarchyqueue/QueuedPlayer.java deleted file mode 100644 index 6db9fda..0000000 --- a/src/main/java/org/zeroBzeroT/anarchyqueue/QueuedPlayer.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.zeroBzeroT.anarchyqueue; - -import com.velocitypowered.api.proxy.Player; - -public record QueuedPlayer(Player player, long queueTime) { -} diff --git a/src/main/resources/config.toml b/src/main/resources/config.toml index 3270896..b17c5d0 100644 --- a/src/main/resources/config.toml +++ b/src/main/resources/config.toml @@ -1,12 +1,10 @@ -# ${project.artifactId} v${project.version} -server-main = "main" -server-queue = "queue" +target = "main" +queue = "queue" name = "0b0t" max-players = 420 -message-position = "Position in queue: " -message-connecting = "Connecting to the server..." -message-full = "Server is currently full!" -message-offline = "Server is currently down!" +message-position = "Position in queue: %position%" +message-connecting = "Connecting to the server..." +message-full = "Server is currently full!" +message-offline = "Server is currently down!" kick = true wait-on-kick = 16 -join-delay = 2000