From 7ff604f0054f199bb6b02af0515dd6661a476595 Mon Sep 17 00:00:00 2001 From: ajgeiss0702 Date: Mon, 31 Jul 2023 17:53:28 -0700 Subject: [PATCH] Added manual-max-players to the config, which allows you to set a max player count for servers/groups --- .../us/ajg0702/queue/api/premium/Logic.java | 10 ++-- .../ajg0702/queue/api/queues/QueueServer.java | 14 ++++- .../queue/api/server/AdaptedServer.java | 16 ++++- .../queue/common/QueueManagerImpl.java | 30 +++++++--- .../queue/common/queues/QueueServerImpl.java | 59 +++++++++++++++++-- .../queues/balancers/FirstBalancer.java | 37 ++++++++++++ common/src/main/resources/config.yml | 20 +++++-- .../bungeecord/server/BungeeServer.java | 11 ---- .../velocity/server/VelocityServer.java | 14 ----- 9 files changed, 159 insertions(+), 52 deletions(-) create mode 100644 common/src/main/java/us/ajg0702/queue/common/queues/balancers/FirstBalancer.java diff --git a/api/src/main/java/us/ajg0702/queue/api/premium/Logic.java b/api/src/main/java/us/ajg0702/queue/api/premium/Logic.java index d60468e..9dc7bf7 100644 --- a/api/src/main/java/us/ajg0702/queue/api/premium/Logic.java +++ b/api/src/main/java/us/ajg0702/queue/api/premium/Logic.java @@ -61,12 +61,10 @@ public interface Logic { } if(fulljoinPriority > 0) { - if(server.isFull() && (server.canJoinFull(player) || - ( - player.hasPermission("ajqueue.make-room") && - AjQueueAPI.getInstance().getConfig().getBoolean("enable-make-room-permission") - ) - )) { + boolean hasMakeRoom = player.hasPermission("ajqueue.make-room") && AjQueueAPI.getInstance().getConfig().getBoolean("enable-make-room-permission"); + if( + (server.isFull() && (server.canJoinFull(player) || hasMakeRoom)) || + (queueServer.isManuallyFull() && (AdaptedServer.canJoinFull(player, queueServer.getName()) || hasMakeRoom))) { highest = Math.max(highest, fulljoinPriority); } } diff --git a/api/src/main/java/us/ajg0702/queue/api/queues/QueueServer.java b/api/src/main/java/us/ajg0702/queue/api/queues/QueueServer.java index 652ca67..d8d135f 100644 --- a/api/src/main/java/us/ajg0702/queue/api/queues/QueueServer.java +++ b/api/src/main/java/us/ajg0702/queue/api/queues/QueueServer.java @@ -4,9 +4,7 @@ import com.google.common.collect.ImmutableList; import us.ajg0702.queue.api.players.AdaptedPlayer; import us.ajg0702.queue.api.players.QueuePlayer; import us.ajg0702.queue.api.server.AdaptedServer; -import us.ajg0702.queue.api.server.AdaptedServerPing; -import java.util.HashMap; import java.util.List; import java.util.UUID; @@ -69,6 +67,18 @@ public interface QueueServer { */ boolean isJoinable(AdaptedPlayer p); + /** + * Gets the manually-set max player count for this server/group + * @return The manually-set max player count + */ + int getManualMaxPlayers(); + + /** + * Checks if the total number of players in this server/group is above the manually-set max player count + * @return If the server is at or above the manually-set player limit + */ + boolean isManuallyFull(); + /** * Pauses or unpauses a server * @param paused true = paused, false = unpaused diff --git a/api/src/main/java/us/ajg0702/queue/api/server/AdaptedServer.java b/api/src/main/java/us/ajg0702/queue/api/server/AdaptedServer.java index 94c5f4f..80ee926 100644 --- a/api/src/main/java/us/ajg0702/queue/api/server/AdaptedServer.java +++ b/api/src/main/java/us/ajg0702/queue/api/server/AdaptedServer.java @@ -1,5 +1,6 @@ package us.ajg0702.queue.api.server; +import us.ajg0702.queue.api.AjQueueAPI; import us.ajg0702.queue.api.players.AdaptedPlayer; import us.ajg0702.queue.api.util.Handle; import us.ajg0702.queue.api.util.QueueLogger; @@ -52,7 +53,20 @@ public interface AdaptedServer extends Handle { */ int getOfflineTime(); - boolean canJoinFull(AdaptedPlayer player); + default boolean canJoinFull(AdaptedPlayer player) { + return canJoinFull(player, getName()); + } + + static boolean canJoinFull(AdaptedPlayer player, String serverName) { + if(player == null) return true; + return + player.hasPermission("ajqueue.joinfull") || + player.hasPermission("ajqueue.joinfullserver." + serverName) || + player.hasPermission("ajqueue.joinfullandbypassserver." + serverName) || + player.hasPermission("ajqueue.joinfullandbypass") || + (AjQueueAPI.getInstance().isPremium() && AjQueueAPI.getInstance().getLogic().getPermissionGetter().hasUniqueFullBypass(player, serverName)) + ; + } boolean justWentOnline(); diff --git a/common/src/main/java/us/ajg0702/queue/common/QueueManagerImpl.java b/common/src/main/java/us/ajg0702/queue/common/QueueManagerImpl.java index 08f1edf..c44666d 100644 --- a/common/src/main/java/us/ajg0702/queue/common/QueueManagerImpl.java +++ b/common/src/main/java/us/ajg0702/queue/common/QueueManagerImpl.java @@ -598,9 +598,10 @@ public class QueueManagerImpl implements QueueManager { final ConcurrentHashMap sendingNowAntiSpam = new ConcurrentHashMap<>(); final Map sendingAttempts = new WeakHashMap<>(); + final Map makeRoomAntispam = new WeakHashMap<>(); @Override - public void sendPlayers(QueueServer queueServer) { + public synchronized void sendPlayers(QueueServer queueServer) { List sendingServers; if(queueServer == null) { sendingServers = new ArrayList<>(servers); @@ -633,7 +634,10 @@ public class QueueManagerImpl implements QueueManager { continue; } - if(selected.isFull() && !selected.canJoinFull(p.getPlayer())) continue; + if( + (selected.isFull() && !selected.canJoinFull(player)) || + (server.isManuallyFull() && !AdaptedServer.canJoinFull(player, server.getName())) + ) continue; player.sendMessage(msgs.getComponent("status.sending-now", "SERVER:"+server.getAlias())); Debug.info("Calling player.connect for " + player.getName() + "(send when back online)"); @@ -683,23 +687,33 @@ public class QueueManagerImpl implements QueueManager { if(!server.canAccess(nextPlayer)) continue; if( - selected.isFull() && - !selected.canJoinFull(nextPlayer) && + ( + (selected.isFull() && !selected.canJoinFull(nextPlayer)) || + (server.isManuallyFull() && !AdaptedServer.canJoinFull(nextPlayer, server.getName())) + ) && !( nextPlayer.hasPermission("ajqueue.make-room") && - main.getConfig().getBoolean("enable-make-room-permission") + main.getConfig().getBoolean("enable-make-room-permission") && + (!server.isGroup() || server.isManuallyFull()) // only use make-room on groups if the server is manually full ) ) continue; // ajqueue.make-room logic if( - selected.isFull() && - !selected.canJoinFull(nextPlayer) && + ( + (selected.isFull() && !selected.canJoinFull(nextPlayer)) || + (server.isManuallyFull() && !AdaptedServer.canJoinFull(nextPlayer, server.getName())) + ) && main.getConfig().getBoolean("enable-make-room-permission") && nextPlayer.hasPermission("ajqueue.make-room") && - !server.isGroup() + (!server.isGroup() || server.isManuallyFull()) && // only use make-room on groups if the server is manually full + ( // don't make room more than the minimum ping time + System.currentTimeMillis() - makeRoomAntispam.getOrDefault(nextQueuePlayer, 0L) + >= (main.getConfig().getDouble("minimum-ping-time") * 1e3) + ) ) { + makeRoomAntispam.put(nextQueuePlayer, System.currentTimeMillis()); List players = selected.getPlayers(); // first, we need to find what the lowest priority on the server is diff --git a/common/src/main/java/us/ajg0702/queue/common/queues/QueueServerImpl.java b/common/src/main/java/us/ajg0702/queue/common/queues/QueueServerImpl.java index 9eea8f4..cbb3828 100644 --- a/common/src/main/java/us/ajg0702/queue/common/queues/QueueServerImpl.java +++ b/common/src/main/java/us/ajg0702/queue/common/queues/QueueServerImpl.java @@ -11,14 +11,13 @@ import us.ajg0702.queue.api.server.AdaptedServerPing; import us.ajg0702.queue.common.QueueMain; import us.ajg0702.queue.common.players.QueuePlayerImpl; import us.ajg0702.queue.common.queues.balancers.DefaultBalancer; +import us.ajg0702.queue.common.queues.balancers.FirstBalancer; import us.ajg0702.queue.common.queues.balancers.MinigameBalancer; import us.ajg0702.queue.common.utils.Debug; import us.ajg0702.utils.common.Messages; import java.util.*; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.TimeUnit; public class QueueServerImpl implements QueueServer { @@ -38,6 +37,9 @@ public class QueueServerImpl implements QueueServer { private long lastSentTime = 0; + private int manualMaxPlayers = Integer.MAX_VALUE; + + public QueueServerImpl(String name, QueueMain main, AdaptedServer server, List previousPlayers) { this(name, main, Collections.singletonList(server), previousPlayers); } @@ -55,11 +57,13 @@ public class QueueServerImpl implements QueueServer { String balancerType = type.substring(colon+1); if(groupName.equals(name)) { - //noinspection SwitchStatementWithTooFewBranches switch(balancerType.toLowerCase(Locale.ROOT)) { case "minigame": balancer = new MinigameBalancer(this, main); break; + case "first": + balancer = new FirstBalancer(this, main); + break; default: balancerType = "default"; balancer = new DefaultBalancer(this, main); @@ -73,6 +77,26 @@ public class QueueServerImpl implements QueueServer { Debug.info("Using default balancer for "+name); } + List manualLimits = main.getConfig().getStringList("manual-max-players"); + for (String manualLimit : manualLimits) { + String[] parts = manualLimit.split(":"); + if(parts.length != 2) { + main.getLogger().warn("Invalid manual limit: " + manualLimit); + continue; + } + String limitFor = parts[0]; + + if(!limitFor.equals(name)) continue; + + String limitStr = parts[1]; + try { + manualMaxPlayers = Integer.parseInt(limitStr); + } catch(NumberFormatException e) { + main.getLogger().warn("Invalid limit number for " + limitFor); + } + break; + } + for(QueuePlayer queuePlayer : previousPlayers) { if(queuePlayer.getPlayer() == null) { addPlayer( @@ -123,7 +147,7 @@ public class QueueServerImpl implements QueueServer { return msgs.getString("status.offline.whitelisted"); } - if(server.isFull() && !server.canJoinFull(p)) { + if((server.isFull() && !server.canJoinFull(p)) || (isManuallyFull() && !AdaptedServer.canJoinFull(p, getName()))) { return msgs.getString("status.offline.full"); } @@ -159,7 +183,7 @@ public class QueueServerImpl implements QueueServer { return "whitelisted"; } - if(server.isFull() && !server.canJoinFull(p)) { + if(((server.isFull() && !server.canJoinFull(p)) || (isManuallyFull() && !AdaptedServer.canJoinFull(p, getName())))) { return "full"; } @@ -186,11 +210,31 @@ public class QueueServerImpl implements QueueServer { @Override public boolean isJoinable(AdaptedPlayer p) { + if(isManuallyFull() && !AdaptedServer.canJoinFull(p, getName())) return false; AdaptedServer server = getIdealServer(p); if(server == null) return false; return server.isJoinable(p) && !isPaused(); } + @Override + public int getManualMaxPlayers() { + return manualMaxPlayers; + } + + @Override + public boolean isManuallyFull() { + int total = 0; + for (AdaptedServer server : servers) { + Optional lastPing = server.getLastPing(); + if(!lastPing.isPresent()) continue; + total += lastPing.get().getPlayerCount(); + } + +// Debug.info(total + " >= " + getManualMaxPlayers() + " = " + (total >= getManualMaxPlayers())); + + return total >= getManualMaxPlayers(); + } + @Override public synchronized void setPaused(boolean paused) { this.paused = paused; @@ -272,6 +316,11 @@ public class QueueServerImpl implements QueueServer { return ImmutableList.copyOf(names); } + @Override + public boolean isOnline() { + return QueueServer.super.isOnline(); + } + @Override public boolean isGroup() { return servers.size() > 1; diff --git a/common/src/main/java/us/ajg0702/queue/common/queues/balancers/FirstBalancer.java b/common/src/main/java/us/ajg0702/queue/common/queues/balancers/FirstBalancer.java new file mode 100644 index 0000000..0604768 --- /dev/null +++ b/common/src/main/java/us/ajg0702/queue/common/queues/balancers/FirstBalancer.java @@ -0,0 +1,37 @@ +package us.ajg0702.queue.common.queues.balancers; + +import org.jetbrains.annotations.Nullable; +import us.ajg0702.queue.api.players.AdaptedPlayer; +import us.ajg0702.queue.api.queues.Balancer; +import us.ajg0702.queue.api.queues.QueueServer; +import us.ajg0702.queue.api.server.AdaptedServer; +import us.ajg0702.queue.common.QueueMain; + +public class FirstBalancer implements Balancer { + + private final QueueServer server; + private final QueueMain main; + public FirstBalancer(QueueServer server, QueueMain main) { + this.server = server; + this.main = main; + } + + @Override + public AdaptedServer getIdealServer(@Nullable AdaptedPlayer player) { + AdaptedServer alreadyConnected; + if(player == null) { + alreadyConnected = null; + } else { + alreadyConnected = player.getCurrentServer(); + } + for (AdaptedServer sv : server.getServers()) { + if(!sv.isOnline()) continue; + if(sv.equals(alreadyConnected)) continue; + if(!sv.isJoinable(player)) continue; + return sv; + } + + // If all servers are unavailable, just select the first one + return server.getServers().get(0); + } +} diff --git a/common/src/main/resources/config.yml b/common/src/main/resources/config.yml index 1ffcf1d..e53d287 100644 --- a/common/src/main/resources/config.yml +++ b/common/src/main/resources/config.yml @@ -91,6 +91,14 @@ queue-servers: # Default: true kick-kicked-players: true +# This option allows you to manually set player caps for servers +# Note that this does NOT override the player cap returned by the server. +# Format: - ':' +# Example: - 'lobbys:50' +# The example above will limit the lobbys group to have a total of 50 players. +# This works on both groups and individual servers +manual-max-players: [] + # Server groups are a group of servers that you can queue for. It will send you to the server that is the least full. # If all servers in the group are full, it will act the same as it would when a single server is full. @@ -101,10 +109,12 @@ server-groups: # What balancer should we use for groups? # If a group is not specified here, then the default one is used -# Example entry: - "bedwars:minigame" +# Example: - "bedwars:minigame" +# The example above will set the bedwars group to use the minigame balancer # Balancers: # default - Will send the player to the server in the group with the least number of players # minigame - Will send the player to the server with the most players, until that server is full, which it will then send to the next server +# first - Will send the player to the first available server in the group (as long as it is joinable) balancer-types: - "bedwars:minigame" @@ -211,12 +221,12 @@ wait-to-load-servers: false # How long should we wait after the server finishes loading to load the server list? # Only works if the above is enabled. -# This is in miliseconds so 1000 = 1 second +# This is in milliseconds so 1000 = 1 second # Default: 500 wait-to-load-servers-delay: 500 # How often (in seconds) we should check for new servers to add queues for. -# If you dynamicly add servers, set this to something other than 0. +# If you dynamically add servers, set this to something other than 0. # To disable, set to 0 # Default: 0 reload-servers-interval: 0 @@ -296,7 +306,7 @@ make-room-kick-to: lobby # For the make-room permission, players with the lowest priority are kicked first. # Of those players, this option decides which to kick. # true - kick players who have been on the server the longest -# false - kick players who have been on nthe server the shortest +# false - kick players who have been on the server the shortest # Default: true make-room-kick-longest-playtime: true @@ -367,7 +377,7 @@ debug: false # Don't touch this number please -config-version: 41 +config-version: 42 # This is ONLY here so that they can be moved to messages.yml. Please edit these in messages.yml! diff --git a/platforms/bungeecord/src/main/java/us/ajg0702/queue/platforms/bungeecord/server/BungeeServer.java b/platforms/bungeecord/src/main/java/us/ajg0702/queue/platforms/bungeecord/server/BungeeServer.java index 8361efd..55bb3f2 100644 --- a/platforms/bungeecord/src/main/java/us/ajg0702/queue/platforms/bungeecord/server/BungeeServer.java +++ b/platforms/bungeecord/src/main/java/us/ajg0702/queue/platforms/bungeecord/server/BungeeServer.java @@ -106,17 +106,6 @@ public class BungeeServer implements AdaptedServer { return offlineTime; } - @Override - public boolean canJoinFull(AdaptedPlayer player) { - if(player == null) return true; - return - player.hasPermission("ajqueue.joinfull") || - player.hasPermission("ajqueue.joinfullserver."+getName()) || - player.hasPermission("ajqueue.joinfullandbypassserver."+getName()) || - player.hasPermission("ajqueue.joinfullandbypass") || - (AjQueueAPI.getInstance().isPremium() && AjQueueAPI.getInstance().getLogic().getPermissionGetter().hasUniqueFullBypass(player, getName())) - ; - } @Override public boolean justWentOnline() { diff --git a/platforms/velocity/src/main/java/us/ajg0702/queue/platforms/velocity/server/VelocityServer.java b/platforms/velocity/src/main/java/us/ajg0702/queue/platforms/velocity/server/VelocityServer.java index e42268b..4630a7c 100644 --- a/platforms/velocity/src/main/java/us/ajg0702/queue/platforms/velocity/server/VelocityServer.java +++ b/platforms/velocity/src/main/java/us/ajg0702/queue/platforms/velocity/server/VelocityServer.java @@ -9,7 +9,6 @@ import us.ajg0702.queue.api.server.AdaptedServer; import us.ajg0702.queue.api.server.AdaptedServerInfo; import us.ajg0702.queue.api.server.AdaptedServerPing; import us.ajg0702.queue.api.util.QueueLogger; -import us.ajg0702.queue.common.utils.Debug; import us.ajg0702.queue.platforms.velocity.players.VelocityPlayer; import java.util.ArrayList; @@ -113,19 +112,6 @@ public class VelocityServer implements AdaptedServer { return offlineTime; } - @Override - public boolean canJoinFull(AdaptedPlayer player) { - if(player == null) return true; - Debug.info("on "+getName()); - return - player.hasPermission("ajqueue.joinfull") || - player.hasPermission("ajqueue.joinfullserver."+getName()) || - player.hasPermission("ajqueue.joinfullandbypassserver."+getName()) || - player.hasPermission("ajqueue.joinfullandbypass") || - (AjQueueAPI.getInstance().isPremium() && AjQueueAPI.getInstance().getLogic().getPermissionGetter().hasUniqueFullBypass(player, getName())) - ; - } - @Override public boolean justWentOnline() { return System.currentTimeMillis()-lastOffline <= (AjQueueAPI.getInstance().getConfig().getDouble("wait-time") * 2 * 1000) && isOnline();