Velocity queueing solution
at master 16 kB view raw
1/* 2 * ProxyQueues, a Velocity queueing solution 3 * 4 * Copyright (c) 2021 James Lyne 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in all 14 * copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 */ 23 24package uk.co.notnull.proxyqueues; 25 26import cloud.commandframework.CommandManager; 27import cloud.commandframework.annotations.Argument; 28import cloud.commandframework.annotations.CommandDescription; 29import cloud.commandframework.annotations.CommandMethod; 30import cloud.commandframework.annotations.CommandPermission; 31import cloud.commandframework.annotations.specifier.Greedy; 32import cloud.commandframework.minecraft.extras.MinecraftHelp; 33import com.velocitypowered.api.command.CommandSource; 34import com.velocitypowered.api.plugin.PluginContainer; 35import com.velocitypowered.api.proxy.Player; 36import com.velocitypowered.api.proxy.ServerConnection; 37import com.velocitypowered.api.proxy.server.RegisteredServer; 38import uk.co.notnull.proxyqueues.api.MessageType; 39import uk.co.notnull.proxyqueues.api.QueueType; 40import uk.co.notnull.proxyqueues.api.queues.ProxyQueue; 41import uk.co.notnull.proxyqueues.api.queues.QueueHandler; 42import uk.co.notnull.proxyqueues.api.queues.QueuePlayer; 43import uk.co.notnull.proxyqueues.configuration.sections.ConfigOptions; 44import uk.co.notnull.proxyqueues.queues.QueueHandlerImpl; 45import uk.co.notnull.proxyqueues.utils.Constants; 46 47import java.time.Instant; 48import java.time.temporal.ChronoUnit; 49import java.util.ArrayList; 50import java.util.Collections; 51import java.util.List; 52import java.util.Map; 53import java.util.Optional; 54import java.util.UUID; 55 56import static java.util.Map.entry; 57 58public class Commands { 59 private final ProxyQueuesImpl plugin; 60 private final QueueHandler queueHandler; 61 private final MinecraftHelp<CommandSource> minecraftHelp; 62 63 public Commands(ProxyQueuesImpl plugin, CommandManager<CommandSource> commandManager) { 64 this.plugin = plugin; 65 this.queueHandler = plugin.getQueueHandler(); 66 67 this.minecraftHelp = new MinecraftHelp<>("/queue", p -> p, commandManager); 68 } 69 70 @CommandMethod("queue help [query]") 71 private void commandHelp( 72 final CommandSource sender, 73 final @Argument("query") @Greedy String query 74 ) { 75 this.minecraftHelp.queryCommands(query == null ? "" : query, sender); 76 } 77 78 @CommandMethod("queue clear <server>") 79 @CommandDescription("Clear the queue for the specified server, removing all players currently in the queue") 80 @CommandPermission(Constants.BASE_PERM + "clear") 81 public void clear(CommandSource sender, @Argument("server") RegisteredServer server) { 82 ProxyQueue queue = queueHandler.getQueue(server); 83 ProxyQueuesImpl proxyQueues = ProxyQueuesImpl.getInstance(); 84 85 if(queue == null) { 86 proxyQueues.sendMessage(sender, MessageType.ERROR, "errors.server-no-queue", 87 Collections.singletonMap("server", server.getServerInfo().getName())); 88 return; 89 } 90 91 queue.clear(); 92 93 proxyQueues.sendMessage(sender, MessageType.INFO, "commands.clear-success", 94 Collections.singletonMap("server", server.getServerInfo().getName())); 95 } 96 97 @CommandMethod("queue info server <server>") 98 @CommandDescription("Shows information about the specified server's queue") 99 @CommandPermission(Constants.BASE_PERM + "info") 100 public void serverInfo(CommandSource sender, @Argument("server") RegisteredServer server) { 101 ProxyQueue queue = queueHandler.getQueue(server); 102 ProxyQueuesImpl proxyQueues = ProxyQueuesImpl.getInstance(); 103 String status = Messages.get("commands.info-status-online"); 104 105 if(queue == null) { 106 proxyQueues.sendMessage(sender, MessageType.ERROR, "errors.server-no-queue", 107 Collections.singletonMap("server", server.getServerInfo().getName())); 108 return; 109 } 110 111 int playersRequired = queue.getPlayersRequired(), 112 normalMax = queue.getMaxSlots(QueueType.NORMAL), 113 priorityMax = queue.getMaxSlots(QueueType.PRIORITY), 114 staffMax = queue.getMaxSlots(QueueType.STAFF), 115 normalSize = queue.getQueueSize(QueueType.NORMAL), 116 prioritySize = queue.getQueueSize(QueueType.PRIORITY), 117 staffSize = queue.getQueueSize(QueueType.STAFF), 118 connectedSize = queue.getConnectedCount(), 119 priorityConnectedSize = queue.getConnectedCount(QueueType.PRIORITY), 120 staffConnectedSize = queue.getConnectedCount(QueueType.STAFF); 121 122 normalSize += prioritySize; 123 normalSize += staffSize; 124 125 QueuePlayer[] normalPlayers = queue.getTopPlayers(QueueType.NORMAL, 3); 126 QueuePlayer[] priorityPlayers = queue.getTopPlayers(QueueType.PRIORITY, 3); 127 QueuePlayer[] staffPlayers = queue.getTopPlayers(QueueType.STAFF, 3); 128 129 if(queue.isPaused()) { 130 Map<PluginContainer, String> pauses = queue.getPauses(); 131 List<String> reasons = new ArrayList<>(pauses.size()); 132 133 pauses.forEach((plugin, reason) -> reasons.add(Messages.get("commands.info-pause-reason", Map.of( 134 "plugin", plugin.getDescription().getName().orElse(plugin.getDescription().getId()), 135 "reason", reason)))); 136 137 status = Messages.get("commands.info-status-paused", 138 Collections.singletonMap("reasons", String.join("\n", reasons))); 139 } 140 141 proxyQueues.sendMessage(sender, MessageType.INFO, "commands.info-server-response", Map.ofEntries( 142 entry("status", status), 143 entry("server", server.getServerInfo().getName()), 144 entry("size", String.valueOf(normalSize)), 145 entry("priority_size", String.valueOf(prioritySize)), 146 entry("staff_size", String.valueOf(staffSize)), 147 entry("connected_size", String.valueOf(connectedSize)), 148 entry("priority_connected_size", String.valueOf(priorityConnectedSize)), 149 entry("staff_connected_size", String.valueOf(staffConnectedSize)), 150 entry("required", String.valueOf(playersRequired)), 151 entry("max", String.valueOf(normalMax)), 152 entry("priority_max", String.valueOf(priorityMax)), 153 entry("global_max", String.valueOf(staffMax)), 154 entry("staff_first", staffPlayers[0] != null ? staffPlayers[0].getPlayer().getUsername() : "n/a"), 155 entry("staff_second", staffPlayers[1] != null ? staffPlayers[1].getPlayer().getUsername() : "n/a"), 156 entry("staff_third", staffPlayers[2] != null ? staffPlayers[2].getPlayer().getUsername() : "n/a"), 157 entry("priority_first", 158 priorityPlayers[0] != null ? priorityPlayers[0].getPlayer().getUsername() : "n/a"), 159 entry("priority_second", 160 priorityPlayers[1] != null ? priorityPlayers[1].getPlayer().getUsername() : "n/a"), 161 entry("priority_third", 162 priorityPlayers[2] != null ? priorityPlayers[2].getPlayer().getUsername() : "n/a"), 163 entry("first", normalPlayers[0] != null ? normalPlayers[0].getPlayer().getUsername() : "n/a"), 164 entry("second", normalPlayers[1] != null ? normalPlayers[1].getPlayer().getUsername() : "n/a"), 165 entry("third", normalPlayers[2] != null ? normalPlayers[2].getPlayer().getUsername() : "n/a"))); 166 } 167 168 @CommandMethod("queue info player <player>") 169 @CommandDescription("Shows information about the specified player's queue status") 170 @CommandPermission(Constants.BASE_PERM + "info") 171 public void playerInfo(CommandSource sender, @Argument(parserName = "visibleplayer", value = "player") Player player) { 172 ProxyQueuesImpl proxyQueues = ProxyQueuesImpl.getInstance(); 173 UUID uuid = player.getUniqueId(); 174 175 ProxyQueue queue = queueHandler.getCurrentQueue(uuid).orElse(null); 176 177 if(queue == null) { 178 proxyQueues.sendMessage(sender, MessageType.ERROR, "errors.target-no-queue", 179 Collections.singletonMap("player", player.getUsername())); 180 return; 181 } 182 183 QueuePlayer queuePlayer = queue.getQueuePlayer(uuid).orElseThrow(); 184 String status; 185 long queuedTime = queuePlayer.getQueuedTime(); 186 187 if(queuePlayer.getPlayer().isActive()) { 188 status = Messages.get("commands.info-status-online"); 189 } else { 190 int disconnectTimeout = ProxyQueuesImpl.getInstance().getSettingsHandler() 191 .getSettingsManager().getProperty(ConfigOptions.DISCONNECT_TIMEOUT); 192 193 long lastSeenTime = queuePlayer.getLastSeen().until(Instant.now(), ChronoUnit.SECONDS); 194 long remainingTime = Math.max(0, Instant.now().minusSeconds(disconnectTimeout) 195 .until(queuePlayer.getLastSeen(), ChronoUnit.SECONDS)); 196 197 status = Messages.get("commands.info-status-offline", 198 Map.of("last_seen", lastSeenTime + "s", "remaining", remainingTime + "s")); 199 } 200 201 proxyQueues.sendMessage(sender, MessageType.INFO, "commands.info-player-response", 202 Map.of("player", player.getUsername(), 203 "server", queue.getServer().getServerInfo().getName(), 204 "type", queuePlayer.getQueueType().toString(), 205 "position", Integer.toString(queuePlayer.getPosition()), 206 "status", status, 207 "queued_time", queuedTime + "s")); 208 } 209 210 @CommandMethod("queue join <server>") 211 @CommandDescription("Join the queue for a server") 212 @CommandPermission(Constants.BASE_PERM + "join") 213 public void join(CommandSource sender, @Argument("server") RegisteredServer server) { 214 ProxyQueue queue = queueHandler.getQueue(server); 215 ProxyQueuesImpl proxyQueues = ProxyQueuesImpl.getInstance(); 216 217 if(queue == null || !queue.isActive()) { 218 proxyQueues.sendMessage(sender, MessageType.ERROR, "errors.server-no-queue", 219 Collections.singletonMap("server", server.getServerInfo().getName())); 220 return; 221 } 222 223 Optional<ServerConnection> currentServer = ((Player) sender).getCurrentServer(); 224 225 if(currentServer.isPresent() && currentServer.get().getServer().equals(server)) { 226 proxyQueues.sendMessage(sender, MessageType.ERROR, "errors.player-same-server", 227 Collections.singletonMap("server", server.getServerInfo().getName())); 228 return; 229 } 230 231 queueHandler.clearPlayer((Player) sender); 232 queue.addPlayer((Player) sender); 233 } 234 235 @CommandMethod("queue kick <player>") 236 @CommandDescription("Kick the specified player from any queue they are in") 237 @CommandPermission(Constants.BASE_PERM + "kick") 238 public void kick(CommandSource sender, @Argument(parserName = "visibleplayer", value = "player") Player player) { 239 ProxyQueuesImpl proxyQueues = ProxyQueuesImpl.getInstance(); 240 UUID uuid = player.getUniqueId(); 241 242 Optional<ProxyQueue> currentQueue = queueHandler.getCurrentQueue(uuid); 243 244 if(currentQueue.isEmpty()) { 245 proxyQueues.sendMessage(sender, MessageType.ERROR, "errors.target-no-queue", 246 Collections.singletonMap("player", player.getUsername())); 247 return; 248 } 249 250 queueHandler.kickPlayer(uuid); 251 proxyQueues.sendMessage(sender, MessageType.INFO, "commands.kick-success", 252 Map.of("player", player.getUsername(), 253 "server", currentQueue.get().getServer().getServerInfo().getName())); 254 } 255 256 @CommandMethod("queue leave") 257 @CommandDescription("Leave the current queue you are in") 258 @CommandPermission(Constants.BASE_PERM + "leave") 259 public void leave(CommandSource sender) { 260 ProxyQueuesImpl proxyQueues = ProxyQueuesImpl.getInstance(); 261 Player player = (Player) sender; 262 Optional<ProxyQueue> currentQueue = queueHandler.getCurrentQueue(player); 263 264 if(currentQueue.isEmpty()) { 265 proxyQueues.sendMessage(sender, MessageType.ERROR, "errors.player-no-queue"); 266 return; 267 } 268 269 queueHandler.clearPlayer(player, false); 270 } 271 272 @CommandMethod("queue pause <server> <reason>") 273 @CommandDescription("Pauses a queue, stopping players from being connected to the server") 274 @CommandPermission(Constants.BASE_PERM + "pause") 275 public void pause(CommandSource sender, @Argument("server") RegisteredServer server, 276 @Argument("reason") @Greedy String reason) { 277 ProxyQueue queue = queueHandler.getQueue(server); 278 ProxyQueuesImpl proxyQueues = ProxyQueuesImpl.getInstance(); 279 280 if(queue == null) { 281 proxyQueues.sendMessage(sender, MessageType.ERROR, "errors.server-no-queue", 282 Collections.singletonMap("server", server.getServerInfo().getName())); 283 return; 284 } 285 286 queue.addPause(plugin, reason); 287 proxyQueues.sendMessage(sender, MessageType.INFO, "commands.pause-success", 288 Collections.singletonMap("server", server.getServerInfo().getName())); 289 } 290 291 @CommandMethod("queue unpause <server>") 292 @CommandDescription("Unpauses a queue. Has no effect on pauses added by other plugins") 293 @CommandPermission(Constants.BASE_PERM + "pause") 294 public void unpause(CommandSource sender, @Argument("server") RegisteredServer server) { 295 ProxyQueue queue = queueHandler.getQueue(server); 296 ProxyQueuesImpl proxyQueues = ProxyQueuesImpl.getInstance(); 297 298 if(queue == null) { 299 proxyQueues.sendMessage(sender, MessageType.ERROR, "errors.server-no-queue", 300 Collections.singletonMap("server", server.getServerInfo().getName())); 301 return; 302 } 303 304 if(!queue.hasPause(plugin)) { 305 proxyQueues.sendMessage(sender, MessageType.ERROR, "errors.queue-not-paused", 306 Collections.singletonMap("server", server.getServerInfo().getName())); 307 308 return; 309 } 310 311 queue.removePause(plugin); 312 proxyQueues.sendMessage(sender, MessageType.INFO, "commands.unpause-success", 313 Collections.singletonMap("server", server.getServerInfo().getName())); 314 } 315 316 @CommandMethod("queue reload") 317 @CommandDescription("Reload the configuration of the plugin") 318 @CommandPermission(Constants.ADMIN_PERM) 319 public void reload(CommandSource issuer) { 320 plugin.loadMessagesConfig(); 321 plugin.getSettingsHandler().getSettingsManager().reload(); 322 plugin.setPlayerLimit(plugin.getSettingsHandler().getSettingsManager().getProperty(ConfigOptions.PLAYER_LIMIT)); 323 ((QueueHandlerImpl) queueHandler).updateQueues(); 324 plugin.sendMessage(issuer, MessageType.INFO, "commands.reload-success"); 325 } 326}