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.queues;
25
26import ch.jalu.configme.SettingsManager;
27import com.velocitypowered.api.event.PostOrder;
28import com.velocitypowered.api.event.Subscribe;
29import com.velocitypowered.api.event.connection.DisconnectEvent;
30import com.velocitypowered.api.event.player.KickedFromServerEvent;
31import com.velocitypowered.api.event.player.ServerPostConnectEvent;
32import com.velocitypowered.api.event.player.ServerPreConnectEvent;
33import com.velocitypowered.api.proxy.Player;
34import com.velocitypowered.api.proxy.ServerConnection;
35import com.velocitypowered.api.proxy.server.RegisteredServer;
36import net.kyori.adventure.text.Component;
37import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
38import uk.co.notnull.proxyqueues.Messages;
39import uk.co.notnull.proxyqueues.ProxyQueuesImpl;
40import uk.co.notnull.proxyqueues.api.QueueType;
41import uk.co.notnull.proxyqueues.api.queues.QueuePlayer;
42import uk.co.notnull.proxyqueues.configuration.sections.ConfigOptions;
43
44import java.time.Instant;
45import java.util.*;
46
47public class ProxyQueueEventHandler {
48 private final ProxyQueuesImpl proxyQueues;
49 private final SettingsManager settingsManager;
50 private final ProxyQueueImpl queue;
51
52 public ProxyQueueEventHandler(ProxyQueuesImpl proxyQueues, ProxyQueueImpl queue) {
53 this.settingsManager = proxyQueues.getSettingsHandler().getSettingsManager();
54 this.proxyQueues = proxyQueues;
55 this.queue = queue;
56 }
57
58 @Subscribe(priority = Short.MIN_VALUE / 2)
59 public void onPreConnect(ServerPreConnectEvent event) {
60 if(!event.getResult().isAllowed()) {
61 return;
62 }
63
64 RegisteredServer server = event.getOriginalServer();
65 Player player = event.getPlayer();
66 RegisteredServer redirected = event.getResult().getServer().orElse(null);
67
68 if(redirected != null && !redirected.equals(server)) {
69 server = redirected;
70 }
71
72 // Check if the server has a queue
73 if(!server.equals(queue.getServer()) || !this.queue.isActive()) {
74 return;
75 }
76
77 // Get the queue
78 Optional<QueuePlayer> queuePlayer = queue.getQueuePlayer(player, true);
79
80 if(queuePlayer.isEmpty()) {
81 if(event.getPlayer().getCurrentServer().isEmpty()) {
82 Optional<RegisteredServer> waitingServer = proxyQueues.getWaitingServer();
83
84 if(waitingServer.isPresent()) {
85 event.setResult(ServerPreConnectEvent.ServerResult.allowed(waitingServer.get()));
86 } else {
87 player.disconnect(Messages.getComponent("cannot-connect-directly"));
88
89 return;
90 }
91 } else {
92 // Cancel the event so they don't go right away
93 event.setResult(ServerPreConnectEvent.ServerResult.denied());
94 }
95
96 // Add the player to the queue
97 queue.addPlayer(player);
98 } else if(!queuePlayer.get().isConnecting()) {
99 event.setResult(ServerPreConnectEvent.ServerResult.denied());
100 }
101 }
102
103 @Subscribe(priority = Short.MIN_VALUE / 2)
104 public void onConnected(ServerPostConnectEvent event) {
105 RegisteredServer server = event.getPlayer().getCurrentServer()
106 .map(ServerConnection::getServer).orElse(null);
107
108 if(server != null) {
109 if(server.equals(queue.getServer())) {
110 queue.removePlayer(event.getPlayer(), true);
111 } else {
112 queue.getQueuePlayer(event.getPlayer(), true).ifPresent(p -> {
113 queue.getNotifier().notifyPlayer(p);
114 });
115 }
116 }
117
118 RegisteredServer previousServer = event.getPreviousServer();
119
120 if(previousServer != null && previousServer.equals(this.queue.getServer())) {
121 queue.clearConnectedState(event.getPlayer());
122 }
123 }
124
125 /**
126 * Handles a player disconnecting from the proxy
127 * If they are queued their lastSeen time will be set
128 * Otherwise they will be added to the priority queue with a lastSeen time set
129 * @param event - The event
130 */
131 @Subscribe(priority = Short.MAX_VALUE / 2)
132 public void onLeave(DisconnectEvent event) {
133 Player player = event.getPlayer();
134 Optional<QueuePlayer> queuePlayer = queue.getQueuePlayer(player, false);
135 queue.clearConnectedState(player);
136
137 proxyQueues.getLogger().debug("Handling proxy disconnect for " + player.getUsername());
138
139 if (queuePlayer.isPresent()) {
140 proxyQueues.getLogger().debug("Player in queue, setting last seen");
141 ((QueuePlayerImpl) queuePlayer.get()).setLastSeen(Instant.now());
142 return;
143 }
144
145 player.getCurrentServer().ifPresent(server -> {
146 if(server.getServer().equals(queue.getServer())) {
147 boolean staff = player.hasPermission(settingsManager.getProperty(ConfigOptions.STAFF_PERMISSION));
148 proxyQueues.getLogger().debug(
149 "Player not in queue, adding to " + (staff ? "staff" : "priority") + " queue");
150 queue.addPlayer(player, staff ? QueueType.STAFF : QueueType.PRIORITY);
151 }
152 });
153 }
154
155 @Subscribe(priority = Short.MIN_VALUE + 1)
156 public void onKick(KickedFromServerEvent event) {
157 if (!event.getServer().equals(queue.getServer()) || event.kickedDuringServerConnect()) {
158 return;
159 }
160
161 proxyQueues.getLogger()
162 .debug("Player " + event.getPlayer().getUsername() + " kicked from " +
163 event.getServer().getServerInfo().getName() + ". Reason: " + event.getServerKickReason());
164
165 Component reason = event.getServerKickReason().orElse(Component.empty());
166 String reasonPlain = PlainTextComponentSerializer.plainText().serialize(reason);
167 List<String> fatalErrors = proxyQueues.getSettingsHandler().getSettingsManager().getProperty(
168 ConfigOptions.FATAL_ERRORS);
169 Optional<RegisteredServer> waitingServer = proxyQueues.getWaitingServer();
170
171 boolean fatal = fatalErrors.stream().anyMatch(reasonPlain::contains);
172
173 if (!fatal) {
174 proxyQueues.getLogger()
175 .debug("Reason not fatal, re-queueing " + event.getPlayer().getUsername());
176
177 boolean staff = event.getPlayer()
178 .hasPermission(settingsManager.getProperty(ConfigOptions.STAFF_PERMISSION));
179
180 queue.addPlayer(event.getPlayer(), staff ? QueueType.STAFF : QueueType.PRIORITY);
181
182 if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer && waitingServer.isPresent()) {
183 event.setResult(KickedFromServerEvent.RedirectPlayer.create(waitingServer.get()));
184 }
185 } else {
186 proxyQueues.getLogger().debug("Reason fatal");
187 }
188 }
189}