Velocity queueing solution
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}