Velocity queueing solution
at master 7.5 kB view raw
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}