Velocity queueing solution
1/*
2 * ProxyQueues, a Velocity queueing solution
3 * Copyright (c) 2021 James Lyne
4 *
5 * Some portions of this file were taken from https://github.com/darbyjack/DeluxeQueues
6 * These portions are Copyright (c) 2019 Glare
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a copy
9 * of this software and associated documentation files (the "Software"), to deal
10 * in the Software without restriction, including without limitation the rights
11 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the Software is
13 * furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in all
16 * copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25
26package uk.co.notnull.proxyqueues.tasks;
27
28import com.velocitypowered.api.proxy.ServerConnection;
29import com.velocitypowered.api.proxy.server.RegisteredServer;
30import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
31import uk.co.notnull.proxyqueues.api.MessageType;
32import uk.co.notnull.proxyqueues.ProxyQueuesImpl;
33import uk.co.notnull.proxyqueues.api.QueueType;
34import uk.co.notnull.proxyqueues.configuration.sections.ConfigOptions;
35import uk.co.notnull.proxyqueues.queues.ProxyQueueImpl;
36import uk.co.notnull.proxyqueues.queues.QueuePlayerImpl;
37import net.kyori.adventure.text.Component;
38
39import java.time.Instant;
40import java.util.Collections;
41import java.util.List;
42import java.util.concurrent.ConcurrentLinkedQueue;
43
44
45public class QueueMoveTask implements Runnable {
46 private final ProxyQueueImpl queue;
47 private final RegisteredServer server;
48 private final ProxyQueuesImpl proxyQueues;
49 private final List<String> fatalErrors;
50 private final RegisteredServer waitingServer;
51
52 private QueuePlayerImpl targetPlayer = null;
53
54 public QueueMoveTask(ProxyQueueImpl queue, RegisteredServer server, ProxyQueuesImpl proxyQueues) {
55 this.queue = queue;
56 this.server = server;
57 this.proxyQueues = proxyQueues;
58 this.fatalErrors = proxyQueues.getSettingsHandler().getSettingsManager().getProperty(
59 ConfigOptions.FATAL_ERRORS);
60
61 waitingServer = proxyQueues.getWaitingServer().orElse(null);
62 }
63
64 @Override
65 public void run() {
66 ConcurrentLinkedQueue<QueuePlayerImpl> normalQueue = queue.getQueue();
67 ConcurrentLinkedQueue<QueuePlayerImpl> priorityQueue = queue.getPriorityQueue();
68 ConcurrentLinkedQueue<QueuePlayerImpl> staffQueue = queue.getStaffQueue();
69
70 if(targetPlayer != null && (!targetPlayer.isConnecting() || !targetPlayer.getPlayer().isActive())) {
71 targetPlayer.setConnecting(false);
72 targetPlayer = null;
73 }
74
75 if(targetPlayer != null && targetPlayer.getLastConnectionAttempt().isBefore(Instant.now().minusSeconds(10))) {
76 proxyQueues.getLogger().debug("Target player timed out");
77 targetPlayer.setConnecting(false);
78 targetPlayer = null;
79 }
80
81 handleQueue(staffQueue);
82 handleQueue(priorityQueue);
83 handleQueue(normalQueue);
84
85 // Nothing to do if no player to queue, connection attempt already underway
86 if(targetPlayer == null || targetPlayer.isConnecting() || queue.isPaused()) {
87 return;
88 }
89
90 // Nothing to do if queue is paused and queued player isn't staff
91 if(queue.isPaused() && targetPlayer.getQueueType() != QueueType.STAFF) {
92 return;
93 }
94
95 // Check if the max amount of players on the server are the max slots
96 if (queue.isServerFull(targetPlayer.getQueueType())) {
97 // Tell the backend server a player is waiting to allow proactive freeing of slots
98 queue.getServer().sendPluginMessage(ProxyQueuesImpl.playerWaitingIdentifier, new byte[0]);
99 return;
100 }
101
102 connectPlayer();
103 }
104
105 private void connectPlayer() {
106 proxyQueues.getLogger().info("Attempting to connect player " + targetPlayer.toString());
107
108 targetPlayer.getPlayer().createConnectionRequest(server).connect().thenAcceptAsync(result -> {
109 targetPlayer.setConnecting(false);
110
111 if(result.isSuccessful()) {
112 return;
113 }
114
115 proxyQueues.getLogger()
116 .info("Player " +
117 targetPlayer.getPlayer().getUsername() + " failed to join "
118 + queue.getServer().getServerInfo().getName()
119 + ". Reason: \""
120 + PlainTextComponentSerializer.plainText()
121 .serialize(result.getReasonComponent().orElse(Component.text("(None)"))) + "\"");
122
123 Component reason = result.getReasonComponent().orElse(Component.empty());
124 String reasonPlain = PlainTextComponentSerializer.plainText().serialize(reason);
125 ServerConnection currentServer = targetPlayer.getPlayer().getCurrentServer().orElse(null);
126
127 boolean fatal = fatalErrors.stream().anyMatch(reasonPlain::contains);
128
129 if(fatal) {
130 queue.removePlayer(targetPlayer, false);
131
132 if(currentServer == null || currentServer.getServer().equals(waitingServer)) {
133 targetPlayer.getPlayer().disconnect(result.getReasonComponent().orElse(Component.empty()));
134 } else {
135 proxyQueues.sendMessage(targetPlayer.getPlayer(), MessageType.ERROR,
136 "errors.queue-cannot-join",
137 Collections.singletonMap("reason", reasonPlain));
138 }
139 }
140 });
141
142 targetPlayer.setConnecting(true);
143 }
144
145 private void handleQueue(ConcurrentLinkedQueue<QueuePlayerImpl> q) {
146 int position = 1;
147 int disconnectTimeout = proxyQueues.getSettingsHandler()
148 .getSettingsManager().getProperty(ConfigOptions.DISCONNECT_TIMEOUT);
149
150 for (QueuePlayerImpl player : q) {
151 boolean online = player.getPlayer().isActive();
152
153 if (online || player.getLastSeen() == null) {
154 player.setLastSeen(Instant.now());
155 }
156
157 if (!online && player.getLastSeen().isBefore(Instant.now().minusSeconds(disconnectTimeout))) {
158 proxyQueues.getLogger()
159 .info("Removing timed out player " + player.getPlayer().getUsername()
160 + " from " + player.getQueueType() + "/" + position);
161
162 queue.removePlayer(player, false);
163 continue;
164 }
165
166 if (targetPlayer == null && online) {
167 targetPlayer = player;
168 }
169
170 player.setPosition(position);
171 queue.getNotifier().notifyPlayer(player);
172 position++;
173 }
174 }
175}