diff --git a/Plugins/Mineplex.ClansQueue.Common/pom.xml b/Plugins/Mineplex.ClansQueue.Common/pom.xml
new file mode 100644
index 000000000..d468c56ca
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+
+ com.mineplex
+ mineplex-parent
+ dev-SNAPSHOT
+ ../plugin.xml
+
+
+ ClansQueue-Common
+ mineplex-clansqueue-common
+
+
+
+ ${project.groupId}
+ mineplex-serverdata
+ ${project.version}
+
+
+
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/ClansQueueMessage.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/ClansQueueMessage.java
new file mode 100644
index 000000000..2bb7211c3
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/ClansQueueMessage.java
@@ -0,0 +1,8 @@
+package com.mineplex.clansqueue.common;
+
+public class ClansQueueMessage
+{
+ protected String Origin;
+ protected String BodyClass;
+ protected String BodySerialized;
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/ClansQueueMessageBody.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/ClansQueueMessageBody.java
new file mode 100644
index 000000000..9a81b2c75
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/ClansQueueMessageBody.java
@@ -0,0 +1,13 @@
+package com.mineplex.clansqueue.common;
+
+import mineplex.serverdata.Utility;
+
+public abstract class ClansQueueMessageBody
+{
+ @Override
+ public final String toString()
+ {
+ super.toString();
+ return Utility.serialize(this);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/ClansQueueMessenger.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/ClansQueueMessenger.java
new file mode 100644
index 000000000..1de841e9f
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/ClansQueueMessenger.java
@@ -0,0 +1,115 @@
+package com.mineplex.clansqueue.common;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+
+import mineplex.serverdata.Utility;
+import mineplex.serverdata.servers.ServerManager;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPubSub;
+
+public class ClansQueueMessenger
+{
+ private static final String CHANNEL_NAME_BASE = "ClansQueueMessageChannel:";
+
+ private static final Map _messengers = new ConcurrentHashMap<>();
+
+ private final String _identifier;
+ private final JedisPool _readPool;
+ private final JedisPool _writePool;
+ @SuppressWarnings("rawtypes")
+ private final Map _bodyTypes = Collections.synchronizedMap(new HashMap<>());
+ @SuppressWarnings("rawtypes")
+ private final Map> _listeners = Collections.synchronizedMap(new HashMap<>());
+
+ private ClansQueueMessenger(String identifier)
+ {
+ _identifier = identifier;
+
+ _writePool = Utility.generatePool(ServerManager.getMasterConnection());
+ _readPool = Utility.generatePool(ServerManager.getSlaveConnection());
+
+ initialize();
+ }
+
+ private void initialize()
+ {
+ new Thread("Clans Queue Messenger: " + _identifier)
+ {
+ public void run()
+ {
+ try (Jedis jedis = _readPool.getResource())
+ {
+ jedis.subscribe(new ClansQueueMessageListener(ClansQueueMessenger.this), CHANNEL_NAME_BASE + "ALL", CHANNEL_NAME_BASE + _identifier);
+ }
+ }
+ }.start();
+ }
+
+ public void registerListener(Class messageType, BiConsumer callback)
+ {
+ _bodyTypes.putIfAbsent(messageType.getName(), messageType);
+ _listeners.computeIfAbsent(messageType.getName(), (type) -> new ArrayList<>()).add(callback);
+ }
+
+ public void transmitMessage(ClansQueueMessageBody message)
+ {
+ transmitMessage(message, "ALL");
+ }
+
+ public void transmitMessage(ClansQueueMessageBody message, String target)
+ {
+ ClansQueueMessage msg = new ClansQueueMessage();
+ msg.Origin = _identifier;
+ msg.BodyClass = message.getClass().getName();
+ msg.BodySerialized = message.toString();
+
+ final String toSend = Utility.serialize(msg);
+
+ new Thread(() ->
+ {
+ try (Jedis jedis = _writePool.getResource())
+ {
+ jedis.publish(CHANNEL_NAME_BASE + target, toSend);
+ }
+ }).start();
+ }
+
+ @SuppressWarnings("unchecked")
+ public void receiveMessage(ClansQueueMessage message)
+ {
+ if (_listeners.containsKey(message.BodyClass) && _bodyTypes.containsKey(message.BodyClass))
+ {
+ T body = Utility.deserialize(message.BodySerialized, (Class)_bodyTypes.get(message.BodyClass));
+ _listeners.get(message.BodyClass).forEach(listener -> listener.accept(body, message.Origin));
+ }
+ }
+
+ private static class ClansQueueMessageListener extends JedisPubSub
+ {
+ private final ClansQueueMessenger _manager;
+
+ private ClansQueueMessageListener(ClansQueueMessenger manager)
+ {
+ _manager = manager;
+ }
+
+ @Override
+ public void onMessage(String channelName, String message)
+ {
+ ClansQueueMessage msg = Utility.deserialize(message, ClansQueueMessage.class);
+ _manager.receiveMessage(msg);
+ }
+ }
+
+ public static ClansQueueMessenger getMessenger(String identifier)
+ {
+ return _messengers.computeIfAbsent(identifier, (id) -> new ClansQueueMessenger(id));
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/QueueConstant.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/QueueConstant.java
new file mode 100644
index 000000000..505022227
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/QueueConstant.java
@@ -0,0 +1,7 @@
+package com.mineplex.clansqueue.common;
+
+public class QueueConstant
+{
+ public static final String SERVICE_MESSENGER_IDENTIFIER = "Queue System";
+ public static final int BYPASS_QUEUE_WEIGHT = -1;
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/SortableLinkedList.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/SortableLinkedList.java
new file mode 100644
index 000000000..1cffc506a
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/SortableLinkedList.java
@@ -0,0 +1,13 @@
+package com.mineplex.clansqueue.common;
+
+import java.util.LinkedList;
+
+public class SortableLinkedList> extends LinkedList
+{
+ private static final long serialVersionUID = -1751886037436467545L;
+
+ public void sort()
+ {
+ sort((t1, t2) -> t1.compareTo(t2));
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/ClansServerStatusMessage.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/ClansServerStatusMessage.java
new file mode 100644
index 000000000..929e084ee
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/ClansServerStatusMessage.java
@@ -0,0 +1,9 @@
+package com.mineplex.clansqueue.common.messages;
+
+import com.mineplex.clansqueue.common.ClansQueueMessageBody;
+
+public class ClansServerStatusMessage extends ClansQueueMessageBody
+{
+ public String ServerName;
+ public int OpenSlots;
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/PlayerJoinQueueCallbackMessage.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/PlayerJoinQueueCallbackMessage.java
new file mode 100644
index 000000000..73fa93238
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/PlayerJoinQueueCallbackMessage.java
@@ -0,0 +1,12 @@
+package com.mineplex.clansqueue.common.messages;
+
+import java.util.UUID;
+
+import com.mineplex.clansqueue.common.ClansQueueMessageBody;
+
+public class PlayerJoinQueueCallbackMessage extends ClansQueueMessageBody
+{
+ public UUID PlayerUUID;
+ public String TargetServer;
+ public int Position;
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/PlayerJoinQueueMessage.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/PlayerJoinQueueMessage.java
new file mode 100644
index 000000000..c31946294
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/PlayerJoinQueueMessage.java
@@ -0,0 +1,12 @@
+package com.mineplex.clansqueue.common.messages;
+
+import java.util.UUID;
+
+import com.mineplex.clansqueue.common.ClansQueueMessageBody;
+
+public class PlayerJoinQueueMessage extends ClansQueueMessageBody
+{
+ public UUID PlayerUUID;
+ public String TargetServer;
+ public int PlayerPriority;
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/PlayerLeaveQueueMessage.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/PlayerLeaveQueueMessage.java
new file mode 100644
index 000000000..dc177c457
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/PlayerLeaveQueueMessage.java
@@ -0,0 +1,11 @@
+package com.mineplex.clansqueue.common.messages;
+
+import java.util.UUID;
+
+import com.mineplex.clansqueue.common.ClansQueueMessageBody;
+
+public class PlayerLeaveQueueMessage extends ClansQueueMessageBody
+{
+ public UUID PlayerUUID;
+ public String TargetServer;
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/PlayerSendToServerMessage.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/PlayerSendToServerMessage.java
new file mode 100644
index 000000000..b1fe5fff0
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/PlayerSendToServerMessage.java
@@ -0,0 +1,11 @@
+package com.mineplex.clansqueue.common.messages;
+
+import java.util.UUID;
+
+import com.mineplex.clansqueue.common.ClansQueueMessageBody;
+
+public class PlayerSendToServerMessage extends ClansQueueMessageBody
+{
+ public UUID PlayerUUID;
+ public String TargetServer;
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/QueueDeleteMessage.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/QueueDeleteMessage.java
new file mode 100644
index 000000000..d8594724b
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/QueueDeleteMessage.java
@@ -0,0 +1,8 @@
+package com.mineplex.clansqueue.common.messages;
+
+import com.mineplex.clansqueue.common.ClansQueueMessageBody;
+
+public class QueueDeleteMessage extends ClansQueueMessageBody
+{
+ public String ServerName;
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/QueuePauseBroadcastMessage.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/QueuePauseBroadcastMessage.java
new file mode 100644
index 000000000..b871ee252
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/QueuePauseBroadcastMessage.java
@@ -0,0 +1,9 @@
+package com.mineplex.clansqueue.common.messages;
+
+import com.mineplex.clansqueue.common.ClansQueueMessageBody;
+
+public class QueuePauseBroadcastMessage extends ClansQueueMessageBody
+{
+ public String ServerName;
+ public boolean Paused;
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/QueuePauseUpdateMessage.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/QueuePauseUpdateMessage.java
new file mode 100644
index 000000000..c17064447
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/QueuePauseUpdateMessage.java
@@ -0,0 +1,9 @@
+package com.mineplex.clansqueue.common.messages;
+
+import com.mineplex.clansqueue.common.ClansQueueMessageBody;
+
+public class QueuePauseUpdateMessage extends ClansQueueMessageBody
+{
+ public String ServerName;
+ public boolean Paused;
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/QueueStatusMessage.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/QueueStatusMessage.java
new file mode 100644
index 000000000..135acb4cc
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/QueueStatusMessage.java
@@ -0,0 +1,20 @@
+package com.mineplex.clansqueue.common.messages;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import com.mineplex.clansqueue.common.ClansQueueMessageBody;
+
+public class QueueStatusMessage extends ClansQueueMessageBody
+{
+ public final List Snapshots = new ArrayList<>();
+
+ public static class QueueSnapshot
+ {
+ public String ServerName;
+ public Map Queue;
+ public boolean Paused;
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/ServerOfflineMessage.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/ServerOfflineMessage.java
new file mode 100644
index 000000000..51761ccb9
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/ServerOfflineMessage.java
@@ -0,0 +1,8 @@
+package com.mineplex.clansqueue.common.messages;
+
+import com.mineplex.clansqueue.common.ClansQueueMessageBody;
+
+public class ServerOfflineMessage extends ClansQueueMessageBody
+{
+ public String ServerName;
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/ServerOnlineMessage.java b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/ServerOnlineMessage.java
new file mode 100644
index 000000000..0d95cc1aa
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue.Common/src/com/mineplex/clansqueue/common/messages/ServerOnlineMessage.java
@@ -0,0 +1,8 @@
+package com.mineplex.clansqueue.common.messages;
+
+import com.mineplex.clansqueue.common.ClansQueueMessageBody;
+
+public class ServerOnlineMessage extends ClansQueueMessageBody
+{
+ public String ServerName;
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue/pom.xml b/Plugins/Mineplex.ClansQueue/pom.xml
new file mode 100644
index 000000000..e1e058ba4
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+
+ com.mineplex
+ mineplex-parent
+ dev-SNAPSHOT
+ ../plugin.xml
+
+
+ ClansQueue-Common
+ mineplex-clansqueue
+
+
+
+ ${project.groupId}
+ mineplex-clansqueue-common
+ ${project.version}
+
+
+
diff --git a/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/QueueService.java b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/QueueService.java
new file mode 100644
index 000000000..c2168c01e
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/QueueService.java
@@ -0,0 +1,94 @@
+package com.mineplex.clansqueue.service;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.mineplex.clansqueue.common.ClansQueueMessenger;
+import com.mineplex.clansqueue.common.QueueConstant;
+import com.mineplex.clansqueue.common.messages.ClansServerStatusMessage;
+import com.mineplex.clansqueue.common.messages.PlayerJoinQueueMessage;
+import com.mineplex.clansqueue.common.messages.PlayerLeaveQueueMessage;
+import com.mineplex.clansqueue.common.messages.QueuePauseUpdateMessage;
+import com.mineplex.clansqueue.common.messages.ServerOfflineMessage;
+import com.mineplex.clansqueue.common.messages.ServerOnlineMessage;
+import com.mineplex.clansqueue.service.commands.CommandSystem;
+import com.mineplex.clansqueue.service.commands.ConsoleCommand;
+import com.mineplex.clansqueue.service.queue.ClansQueueManager;
+
+import mineplex.serverdata.Region;
+
+public class QueueService
+{
+ public static void main(String[] args)
+ {
+ QueueService service = new QueueService(new File("eu.dat").exists());
+ service.start();
+ while (service.isRunning()) {};
+ }
+
+ private final Region _region;
+ private final AtomicBoolean _running;
+ private final Map _commandMap = Collections.synchronizedMap(new HashMap<>());
+ private final CommandSystem _commandSystem;
+ private final ClansQueueManager _queueManager;
+
+ private QueueService(boolean eu)
+ {
+ if (eu)
+ {
+ _region = Region.EU;
+ }
+ else
+ {
+ _region = Region.US;
+ }
+ _running = new AtomicBoolean();
+ _commandSystem = new CommandSystem(this, _commandMap);
+ _queueManager = new ClansQueueManager(this);
+ }
+
+ private void start()
+ {
+ System.out.println("[Queue Service] Enabling on region " + getRegion().name());
+ _running.set(true);
+ _commandSystem.start();
+ _queueManager.start();
+
+ ClansQueueMessenger messenger = ClansQueueMessenger.getMessenger(QueueConstant.SERVICE_MESSENGER_IDENTIFIER);
+ messenger.registerListener(ServerOnlineMessage.class, (online, origin) -> _queueManager.handleServerEnable(online.ServerName));
+ messenger.registerListener(ServerOfflineMessage.class, (offline, origin) -> _queueManager.handleServerDisable(offline.ServerName));
+ messenger.registerListener(QueuePauseUpdateMessage.class, (pause, origin) -> _queueManager.handleQueuePause(pause.ServerName, pause.Paused));
+ messenger.registerListener(PlayerJoinQueueMessage.class, (join, origin) -> _queueManager.joinQueue(join.TargetServer, origin, join.PlayerUUID, join.PlayerPriority));
+ messenger.registerListener(PlayerLeaveQueueMessage.class, (leave, origin) -> _queueManager.leaveQueue(leave.TargetServer, leave.PlayerUUID));
+ messenger.registerListener(ClansServerStatusMessage.class, (status, origin) -> _queueManager.handleServerUpdate(status.ServerName, status.OpenSlots));
+ }
+
+ public ClansQueueManager getQueueManager()
+ {
+ return _queueManager;
+ }
+
+ public boolean isRunning()
+ {
+ return _running.get();
+ }
+
+ public Region getRegion()
+ {
+ return _region;
+ }
+
+ public void registerCommand(ConsoleCommand command)
+ {
+ _commandMap.put(command.getCommand().toLowerCase(), command);
+ }
+
+ public void shutdown()
+ {
+ System.out.println("[Queue Service] Shutting down...");
+ _running.set(false);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/CommandSystem.java b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/CommandSystem.java
new file mode 100644
index 000000000..832a55097
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/CommandSystem.java
@@ -0,0 +1,64 @@
+package com.mineplex.clansqueue.service.commands;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Scanner;
+
+import com.mineplex.clansqueue.service.QueueService;
+
+public class CommandSystem extends Thread
+{
+ private final QueueService _service;
+ private final Map _commands;
+
+ public CommandSystem(QueueService service, Map commands)
+ {
+ super("Command System");
+ _service = service;
+ _commands = commands;
+
+ _service.registerCommand(new HelpCommand(_commands));
+ _service.registerCommand(new StopCommand(_service));
+ _service.registerCommand(new DeleteQueueCommand(_service));
+ _service.registerCommand(new ListQueuesCommand(_service));
+ _service.registerCommand(new PauseQueueCommand(_service));
+ }
+
+ private boolean matches(String key, String input)
+ {
+ if (key.equalsIgnoreCase(input))
+ {
+ return true;
+ }
+ if (input.toLowerCase().startsWith(key + " "))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void run()
+ {
+ try (Scanner scanner = new Scanner(System.in))
+ {
+ while (_service.isRunning())
+ {
+ String input = scanner.nextLine();
+ if (input.isEmpty())
+ {
+ continue;
+ }
+ Optional opt = _commands.entrySet().stream().filter(entry -> matches(entry.getKey(), input)).map(Map.Entry::getValue).findAny();
+ if (opt.isPresent())
+ {
+ opt.get().call(input);
+ }
+ else
+ {
+ System.out.println("Command '" + input.split(" ")[0] + "' was not found. Run 'help' for a list of commands.");
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/ConsoleCommand.java b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/ConsoleCommand.java
new file mode 100644
index 000000000..f962d4dd6
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/ConsoleCommand.java
@@ -0,0 +1,59 @@
+package com.mineplex.clansqueue.service.commands;
+
+public abstract class ConsoleCommand
+{
+ private final String _command;
+ private final String _usageText;
+ private StringBuilder _outputBuilder;
+
+ public ConsoleCommand(String command, String usageText)
+ {
+ _command = command;
+ _usageText = usageText;
+ }
+
+ public String getCommand()
+ {
+ return _command;
+ }
+
+ public String getUsageText()
+ {
+ return _usageText;
+ }
+
+ protected final void addOutput(String text)
+ {
+ if (_outputBuilder == null)
+ {
+ _outputBuilder = new StringBuilder();
+ }
+ else
+ {
+ _outputBuilder.append("\n");
+ }
+ _outputBuilder.append(text);
+ }
+
+ protected final void sendOutput()
+ {
+ System.out.println(_outputBuilder.toString());
+ _outputBuilder = null;
+ }
+
+ public final void call(String input)
+ {
+ String parsing = input.trim();
+ if (parsing.length() > getCommand().length() + 2)
+ {
+ String[] args = parsing.substring(getCommand().length() + 1).split(" ");
+ use(args);
+ }
+ else
+ {
+ use(new String[] {});
+ }
+ }
+
+ protected abstract void use(String[] arguments);
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/DeleteQueueCommand.java b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/DeleteQueueCommand.java
new file mode 100644
index 000000000..fd169d08e
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/DeleteQueueCommand.java
@@ -0,0 +1,38 @@
+package com.mineplex.clansqueue.service.commands;
+
+import com.mineplex.clansqueue.service.QueueService;
+import com.mineplex.clansqueue.service.queue.ClansServer;
+
+public class DeleteQueueCommand extends ConsoleCommand
+{
+ private final QueueService _service;
+
+ public DeleteQueueCommand(QueueService service)
+ {
+ super("delete", "Deletes an existing server and queue");
+
+ _service = service;
+ }
+
+ @Override
+ protected void use(String[] arguments)
+ {
+ if (arguments.length < 1)
+ {
+ addOutput("Usage: delete ");
+ sendOutput();
+ return;
+ }
+ ClansServer server = _service.getQueueManager().getLoadedServer(arguments[0]);
+ if (server == null)
+ {
+ addOutput("Server '" + arguments[0] + "' was not found. Run 'list' for a list of servers.");
+ sendOutput();
+ return;
+ }
+
+ _service.getQueueManager().deleteServer(server);
+ addOutput("Server and queue deleted.");
+ sendOutput();
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/HelpCommand.java b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/HelpCommand.java
new file mode 100644
index 000000000..808c1ab2d
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/HelpCommand.java
@@ -0,0 +1,41 @@
+package com.mineplex.clansqueue.service.commands;
+
+import java.util.Map;
+
+public class HelpCommand extends ConsoleCommand
+{
+ private final Map _commands;
+
+ public HelpCommand(Map commands)
+ {
+ super("help", "Lists commands and their usage");
+
+ _commands = commands;
+ }
+
+ @Override
+ protected void use(String[] arguments)
+ {
+ if (arguments.length < 1)
+ {
+ addOutput("Commands:");
+ _commands.values().forEach(command ->
+ {
+ addOutput(command.getCommand() + " : " + command.getUsageText());
+ });
+ }
+ else
+ {
+ if (_commands.containsKey(arguments[0].toLowerCase()))
+ {
+ ConsoleCommand cmd = _commands.get(arguments[0].toLowerCase());
+ addOutput(cmd.getCommand() + " : " + cmd.getUsageText());
+ }
+ else
+ {
+ addOutput("Command '" + arguments[0] + "' was not found. Run 'help' for a list of commands.");
+ }
+ }
+ sendOutput();
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/ListQueuesCommand.java b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/ListQueuesCommand.java
new file mode 100644
index 000000000..55de6784c
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/ListQueuesCommand.java
@@ -0,0 +1,29 @@
+package com.mineplex.clansqueue.service.commands;
+
+import java.util.stream.Collectors;
+
+import com.mineplex.clansqueue.service.QueueService;
+import com.mineplex.clansqueue.service.queue.ClansServer;
+
+public class ListQueuesCommand extends ConsoleCommand
+{
+ private final QueueService _service;
+
+ public ListQueuesCommand(QueueService service)
+ {
+ super("list", "Lists existing servers");
+
+ _service = service;
+ }
+
+ @Override
+ protected void use(String[] arguments)
+ {
+ StringBuilder servers = new StringBuilder("Servers: [");
+ servers.append(_service.getQueueManager().getLoadedServers().stream().map(ClansServer::getName).collect(Collectors.joining(", ")));
+ servers.append(']');
+
+ addOutput(servers.toString());
+ sendOutput();
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/PauseQueueCommand.java b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/PauseQueueCommand.java
new file mode 100644
index 000000000..c417883e6
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/PauseQueueCommand.java
@@ -0,0 +1,38 @@
+package com.mineplex.clansqueue.service.commands;
+
+import com.mineplex.clansqueue.service.QueueService;
+import com.mineplex.clansqueue.service.queue.ClansServer;
+
+public class PauseQueueCommand extends ConsoleCommand
+{
+ private final QueueService _service;
+
+ public PauseQueueCommand(QueueService service)
+ {
+ super("pause", "Pauses an existing queue");
+
+ _service = service;
+ }
+
+ @Override
+ protected void use(String[] arguments)
+ {
+ if (arguments.length < 1)
+ {
+ addOutput("Usage: pause ");
+ sendOutput();
+ return;
+ }
+ ClansServer server = _service.getQueueManager().getLoadedServer(arguments[0]);
+ if (server == null)
+ {
+ addOutput("Server '" + arguments[0] + "' was not found. Run 'list' for a list of servers.");
+ sendOutput();
+ return;
+ }
+
+ _service.getQueueManager().handleQueuePause(server.getName(), true);
+ addOutput("Queue paused.");
+ sendOutput();
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/StopCommand.java b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/StopCommand.java
new file mode 100644
index 000000000..a1c100d64
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/commands/StopCommand.java
@@ -0,0 +1,21 @@
+package com.mineplex.clansqueue.service.commands;
+
+import com.mineplex.clansqueue.service.QueueService;
+
+public class StopCommand extends ConsoleCommand
+{
+ private final QueueService _service;
+
+ public StopCommand(QueueService service)
+ {
+ super("stop", "Stops the Queue Service");
+
+ _service = service;
+ }
+
+ @Override
+ protected void use(String[] arguments)
+ {
+ _service.shutdown();
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/queue/ClansQueueManager.java b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/queue/ClansQueueManager.java
new file mode 100644
index 000000000..5c63540b2
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/queue/ClansQueueManager.java
@@ -0,0 +1,198 @@
+package com.mineplex.clansqueue.service.queue;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import com.mineplex.clansqueue.common.ClansQueueMessenger;
+import com.mineplex.clansqueue.common.QueueConstant;
+import com.mineplex.clansqueue.common.messages.PlayerJoinQueueCallbackMessage;
+import com.mineplex.clansqueue.common.messages.PlayerSendToServerMessage;
+import com.mineplex.clansqueue.common.messages.QueueDeleteMessage;
+import com.mineplex.clansqueue.common.messages.QueuePauseBroadcastMessage;
+import com.mineplex.clansqueue.common.messages.QueueStatusMessage;
+import com.mineplex.clansqueue.common.messages.QueueStatusMessage.QueueSnapshot;
+import com.mineplex.clansqueue.service.QueueService;
+
+public class ClansQueueManager
+{
+ private final Map _servers = new HashMap<>();
+ private final Map _queues = new HashMap<>();
+ private final Thread _updater;
+
+ public ClansQueueManager(QueueService service)
+ {
+ _updater = new Thread(() ->
+ {
+ while (service.isRunning())
+ {
+ try
+ {
+ updateQueues();
+ Thread.sleep(5000);
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }, "Queue Update Thread");
+ }
+
+ private QueueStatusMessage buildStatusMessage(Collection queues)
+ {
+ QueueStatusMessage message = new QueueStatusMessage();
+
+ queues.forEach(queue ->
+ {
+ QueueSnapshot snapshot = new QueueSnapshot();
+ snapshot.Paused = queue.isPaused();
+ snapshot.ServerName = queue.getServer().getName();
+ snapshot.Queue = new HashMap<>();
+ queue.getPlayers().values().forEach(player -> snapshot.Queue.put(player.PlayerUUID, player.Position));
+ });
+
+ return message;
+ }
+
+ private synchronized void updateQueues()
+ {
+ System.out.println("Updating queues");
+ Collection queues = _queues.values();
+
+ queues.forEach(q ->
+ {
+ q.updatePositions(q.getServer().getOpenSlots());
+ if (q.getServer().isOnline())
+ {
+ q.getNextSend().entrySet().forEach(entry ->
+ {
+ PlayerSendToServerMessage message = new PlayerSendToServerMessage();
+ message.PlayerUUID = entry.getKey();
+ message.TargetServer = q.getServer().getName();
+ ClansQueueMessenger.getMessenger(QueueConstant.SERVICE_MESSENGER_IDENTIFIER).transmitMessage(message, entry.getValue());
+ });
+ }
+ });
+
+ QueueStatusMessage message = buildStatusMessage(queues);
+ ClansQueueMessenger.getMessenger(QueueConstant.SERVICE_MESSENGER_IDENTIFIER).transmitMessage(message);
+ }
+
+ public synchronized ClansServer getLoadedServer(String serverName)
+ {
+ return _servers.get(serverName);
+ }
+
+ public synchronized Collection getLoadedServers()
+ {
+ return _servers.values();
+ }
+
+ public synchronized void deleteServer(ClansServer server)
+ {
+ _servers.remove(server.getName());
+ _queues.remove(server);
+ QueueDeleteMessage message = new QueueDeleteMessage();
+ ClansQueueMessenger.getMessenger(QueueConstant.SERVICE_MESSENGER_IDENTIFIER).transmitMessage(message);
+ }
+
+ public synchronized void handleServerEnable(String serverName)
+ {
+ _servers.computeIfAbsent(serverName, (name) ->
+ {
+ ClansServer server = new ClansServer(name);
+
+ _queues.put(server, new ServerQueue(server));
+
+ return server;
+ }).setOnline(true);
+
+ System.out.println("Clans server " + serverName + " enabled.");
+ }
+
+ public synchronized void handleServerDisable(String serverName)
+ {
+ _servers.computeIfAbsent(serverName, (name) ->
+ {
+ ClansServer server = new ClansServer(name);
+
+ _queues.put(server, new ServerQueue(server));
+
+ return server;
+ }).setOnline(false);
+ }
+
+ public synchronized void handleServerUpdate(String serverName, int openSlots)
+ {
+ _servers.computeIfAbsent(serverName, (name) ->
+ {
+ ClansServer server = new ClansServer(name);
+
+ _queues.put(server, new ServerQueue(server));
+
+ return server;
+ }).setOpenSlots(openSlots);
+ }
+
+ public synchronized void handleQueuePause(String serverName, boolean pause)
+ {
+ ClansServer server = _servers.get(serverName);
+ if (server != null)
+ {
+ _queues.get(server).setPaused(pause);
+ System.out.println("Clans server " + serverName + " queue pause: " + pause);
+ QueuePauseBroadcastMessage message = new QueuePauseBroadcastMessage();
+ message.ServerName = serverName;
+ message.Paused = pause;
+ ClansQueueMessenger.getMessenger(QueueConstant.SERVICE_MESSENGER_IDENTIFIER).transmitMessage(message);
+ }
+ }
+
+ public synchronized void joinQueue(String serverName, String currentServer, UUID uuid, int weight)
+ {
+ ClansServer server = _servers.get(serverName);
+ if (server != null)
+ {
+ ServerQueue queue = _queues.get(server);
+ if (weight == QueueConstant.BYPASS_QUEUE_WEIGHT)
+ {
+ queue.addBypasser(uuid, currentServer);
+ }
+ else
+ {
+ queue.addPlayer(uuid, currentServer, weight, player ->
+ {
+ PlayerJoinQueueCallbackMessage message = new PlayerJoinQueueCallbackMessage();
+ message.PlayerUUID = uuid;
+ message.TargetServer = serverName;
+ message.Position = player.Position;
+ ClansQueueMessenger.getMessenger(QueueConstant.SERVICE_MESSENGER_IDENTIFIER).transmitMessage(message, currentServer);
+ QueueStatusMessage update = buildStatusMessage(Arrays.asList(queue));
+ ClansQueueMessenger.getMessenger(QueueConstant.SERVICE_MESSENGER_IDENTIFIER).transmitMessage(update);
+ });
+ }
+ }
+ }
+
+ public synchronized void leaveQueue(String serverName, UUID uuid)
+ {
+ ClansServer server = _servers.get(serverName);
+ if (server != null)
+ {
+ ServerQueue queue = _queues.get(server);
+ queue.removePlayer(uuid, () ->
+ {
+ QueueStatusMessage message = buildStatusMessage(Arrays.asList(queue));
+ ClansQueueMessenger.getMessenger(QueueConstant.SERVICE_MESSENGER_IDENTIFIER).transmitMessage(message);
+ });
+ }
+ }
+
+ public void start()
+ {
+ _updater.start();
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/queue/ClansServer.java b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/queue/ClansServer.java
new file mode 100644
index 000000000..375a7ebc6
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/queue/ClansServer.java
@@ -0,0 +1,58 @@
+package com.mineplex.clansqueue.service.queue;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ClansServer
+{
+ private final String _serverName;
+ private final AtomicBoolean _online = new AtomicBoolean();
+ private final AtomicInteger _openSlots = new AtomicInteger();
+
+ public ClansServer(String serverName)
+ {
+ _serverName = serverName;
+ }
+
+ public String getName()
+ {
+ return _serverName;
+ }
+
+ public boolean isOnline()
+ {
+ return _online.get();
+ }
+
+ public void setOnline(boolean online)
+ {
+ _online.set(online);
+ }
+
+ public int getOpenSlots()
+ {
+ return _openSlots.get();
+ }
+
+ public void setOpenSlots(int openSlots)
+ {
+ _openSlots.set(openSlots);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return _serverName.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o == null || !getClass().isInstance(o))
+ {
+ return false;
+ }
+
+ return ((ClansServer)o)._serverName.equals(_serverName);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/queue/QueuePlayer.java b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/queue/QueuePlayer.java
new file mode 100644
index 000000000..2c5048d8b
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/queue/QueuePlayer.java
@@ -0,0 +1,33 @@
+package com.mineplex.clansqueue.service.queue;
+
+import java.util.UUID;
+
+public class QueuePlayer
+{
+ public final UUID PlayerUUID;
+ public final String CurrentServer;
+ public int Position;
+
+ public QueuePlayer(UUID uuid, String currentServer)
+ {
+ PlayerUUID = uuid;
+ CurrentServer = currentServer;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return PlayerUUID.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o == null || !getClass().isInstance(o))
+ {
+ return false;
+ }
+
+ return ((QueuePlayer)o).PlayerUUID.equals(PlayerUUID);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/queue/ServerQueue.java b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/queue/ServerQueue.java
new file mode 100644
index 000000000..e9e269efc
--- /dev/null
+++ b/Plugins/Mineplex.ClansQueue/src/com/mineplex/clansqueue/service/queue/ServerQueue.java
@@ -0,0 +1,222 @@
+package com.mineplex.clansqueue.service.queue;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Queue;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+import com.mineplex.clansqueue.common.SortableLinkedList;
+
+public class ServerQueue
+{
+ private final ClansServer _server;
+ private final Map _sending = new LinkedHashMap<>();
+ private final Map _bypassing = new LinkedHashMap<>();
+ private final SortableLinkedList _queues = new SortableLinkedList<>();
+ private final Object _bypassLock = new Object();
+ private final Object _queueLock = new Object();
+ private final Object _sendLock = new Object();
+ private final AtomicBoolean _paused = new AtomicBoolean();
+
+ public ServerQueue(ClansServer server)
+ {
+ _server = server;
+ }
+
+ public ClansServer getServer()
+ {
+ return _server;
+ }
+
+ public boolean isPaused()
+ {
+ return _paused.get() || !_server.isOnline();
+ }
+
+ public Map getNextSend()
+ {
+ synchronized (_sendLock)
+ {
+ Map sending = new LinkedHashMap<>();
+ sending.putAll(_sending);
+ _sending.clear();
+ return sending;
+ }
+ }
+
+ public Map getPlayers()
+ {
+ synchronized (_queueLock)
+ {
+ Map players = new LinkedHashMap<>();
+ _queues.forEach(queue -> queue._players.forEach(qp -> players.put(qp.PlayerUUID, qp)));
+
+ return players;
+ }
+ }
+
+ public void addBypasser(UUID uuid, String currentServer)
+ {
+ synchronized (_bypassLock)
+ {
+ _bypassing.put(uuid, currentServer);
+ }
+ }
+
+ public void addPlayer(UUID uuid, String currentServer, int weight, Consumer callback)
+ {
+ synchronized (_queueLock)
+ {
+ Optional queueOpt = _queues.stream().filter(q -> q._weight == weight).findFirst();
+ PlayerQueue queue = queueOpt.orElseGet(() ->
+ {
+ PlayerQueue creating = new PlayerQueue(weight);
+ if (_queues.add(creating))
+ {
+ _queues.sort();
+ }
+
+ return creating;
+ });
+
+ QueuePlayer player = new QueuePlayer(uuid, currentServer);
+ queue._players.add(player);
+
+ AtomicInteger position = new AtomicInteger(1);
+ if (_queues.removeIf(q -> q._players.isEmpty()))
+ {
+ _queues.sort();
+ }
+ _queues.forEach(q -> q._players.forEach(qp ->
+ {
+ qp.Position = position.getAndIncrement();
+ }));
+
+ if (callback != null)
+ {
+ callback.accept(player);
+ }
+ }
+ }
+
+ public void removePlayer(UUID uuid, Runnable after)
+ {
+ synchronized (_queueLock)
+ {
+ _queues.forEach(queue -> queue._players.removeIf(player -> player.PlayerUUID.equals(uuid)));
+
+ AtomicInteger position = new AtomicInteger(1);
+ if (_queues.removeIf(q -> q._players.isEmpty()))
+ {
+ _queues.sort();
+ }
+ _queues.forEach(q -> q._players.forEach(qp ->
+ {
+ qp.Position = position.getAndIncrement();
+ }));
+
+ if (after != null)
+ {
+ after.run();
+ }
+ }
+ }
+
+ public void setPaused(boolean paused)
+ {
+ _paused.set(paused);
+ }
+
+ public void updatePositions(int openPlayerSlots)
+ {
+ Map send = new LinkedHashMap<>();
+ if (_server.isOnline())
+ {
+ synchronized (_bypassLock)
+ {
+ send.putAll(_bypassing);
+ _bypassing.clear();
+ }
+ }
+ synchronized (_queueLock)
+ {
+ if (!isPaused() && openPlayerSlots > 0)
+ {
+ while (send.size() < openPlayerSlots)
+ {
+ if (_queues.removeIf(queue -> queue._players.isEmpty()))
+ {
+ _queues.sort();
+ }
+ PlayerQueue queue = _queues.peek();
+ if (queue == null)
+ {
+ break;
+ }
+ QueuePlayer player = queue._players.poll();
+ send.put(player.PlayerUUID, player.CurrentServer);
+ }
+ }
+ AtomicInteger position = new AtomicInteger(1);
+ if (_queues.removeIf(queue -> queue._players.isEmpty()))
+ {
+ _queues.sort();
+ }
+ _queues.forEach(queue -> queue._players.forEach(qp ->
+ {
+ qp.Position = position.getAndIncrement();
+ }));
+ }
+ if (send.isEmpty())
+ {
+ return;
+ }
+ synchronized (_sendLock)
+ {
+ _sending.putAll(send);
+ }
+ }
+
+ private static class PlayerQueue implements Comparable
+ {
+ private final int _weight;
+ private final Queue _players = new LinkedList<>();
+
+ private PlayerQueue(int weight)
+ {
+ _weight = weight;
+ }
+
+ @Override
+ public int compareTo(PlayerQueue queue)
+ {
+ if (queue == null)
+ {
+ throw new NullPointerException();
+ }
+ return Integer.compare(queue._weight, _weight);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Integer.hashCode(_weight);
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o == null || !getClass().isInstance(o))
+ {
+ return false;
+ }
+
+ return ((PlayerQueue)o)._weight == _weight;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.Core/src/mineplex/core/account/CoreClientManager.java b/Plugins/Mineplex.Core/src/mineplex/core/account/CoreClientManager.java
index 0c6a0e75e..8089ec178 100644
--- a/Plugins/Mineplex.Core/src/mineplex/core/account/CoreClientManager.java
+++ b/Plugins/Mineplex.Core/src/mineplex/core/account/CoreClientManager.java
@@ -617,7 +617,6 @@ public class CoreClientManager extends MiniPlugin
if (client.hasPermission(Perm.JOIN_FULL))
{
event.allow();
- event.setResult(PlayerLoginEvent.Result.ALLOWED);
return;
}
diff --git a/Plugins/Mineplex.Core/src/mineplex/core/updater/FileUpdater.java b/Plugins/Mineplex.Core/src/mineplex/core/updater/FileUpdater.java
index 90b9c8919..e72629d7b 100644
--- a/Plugins/Mineplex.Core/src/mineplex/core/updater/FileUpdater.java
+++ b/Plugins/Mineplex.Core/src/mineplex/core/updater/FileUpdater.java
@@ -22,12 +22,14 @@ import mineplex.core.account.permissions.PermissionGroup;
import mineplex.core.common.util.C;
import mineplex.core.common.util.F;
import mineplex.core.common.util.NautHashMap;
+import mineplex.core.common.util.UtilServer;
import mineplex.core.portal.GenericServer;
import mineplex.core.portal.Intent;
import mineplex.core.portal.Portal;
import mineplex.core.updater.command.BuildVersionCommand;
import mineplex.core.updater.command.RestartServerCommand;
import mineplex.core.updater.event.RestartServerEvent;
+import mineplex.core.updater.event.RestartTriggerEvent;
import mineplex.core.updater.event.UpdateEvent;
import mineplex.serverdata.Region;
import mineplex.serverdata.commands.RestartCommand;
@@ -50,6 +52,7 @@ public class FileUpdater extends MiniPlugin
private boolean _needUpdate;
private boolean _enabled = true;
+ private boolean _restartTriggered = false;
private Properties _buildProperties;
@@ -77,7 +80,6 @@ public class FileUpdater extends MiniPlugin
private void generatePermissions()
{
-
PermissionGroup.MOD.setPermission(Perm.BVERSION_COMMAND, true, true);
PermissionGroup.ADMIN.setPermission(Perm.RESTART_COMMAND, true, true);
PermissionGroup.QAM.setPermission(Perm.RESTART_COMMAND, false, true);
@@ -112,10 +114,17 @@ public class FileUpdater extends MiniPlugin
if (event.getType() != UpdateType.SLOWER)
return;
- if (!_needUpdate || !_enabled)
+ if (!_needUpdate || !_enabled || _restartTriggered)
return;
- RestartServerEvent restartEvent = new RestartServerEvent();
+ if (UtilServer.CallEvent(new RestartTriggerEvent(RestartTriggerEvent.RestartReason.UPDATE)).isCancelled())
+ {
+ return;
+ }
+
+ _restartTriggered = true;
+
+ RestartServerEvent restartEvent = new RestartServerEvent(RestartServerEvent.RestartReason.UPDATE);
getPluginManager().callEvent(restartEvent);
@@ -126,20 +135,14 @@ public class FileUpdater extends MiniPlugin
player.sendMessage(F.main("Updater", C.cGold + _serverName + C.cGray + " is restarting for an update."));
}
- getPlugin().getServer().getScheduler().scheduleSyncDelayedTask(getPlugin(), new Runnable()
+ getPlugin().getServer().getScheduler().scheduleSyncDelayedTask(getPlugin(), () ->
{
- public void run()
- {
- _portal.sendAllPlayersToGenericServer(_transferHub, Intent.KICK);
- }
+ _portal.sendAllPlayersToGenericServer(_transferHub, Intent.KICK);
}, 60L);
- getPlugin().getServer().getScheduler().scheduleSyncDelayedTask(getPlugin(), new Runnable()
+ getPlugin().getServer().getScheduler().scheduleSyncDelayedTask(getPlugin(), () ->
{
- public void run()
- {
- getPlugin().getServer().shutdown();
- }
+ getPlugin().getServer().shutdown();
}, 100L);
}
}
diff --git a/Plugins/Mineplex.Core/src/mineplex/core/updater/RestartHandler.java b/Plugins/Mineplex.Core/src/mineplex/core/updater/RestartHandler.java
index e37961ef9..9001426f6 100644
--- a/Plugins/Mineplex.Core/src/mineplex/core/updater/RestartHandler.java
+++ b/Plugins/Mineplex.Core/src/mineplex/core/updater/RestartHandler.java
@@ -1,14 +1,5 @@
package mineplex.core.updater;
-import mineplex.core.common.util.F;
-import mineplex.core.portal.GenericServer;
-import mineplex.core.portal.Intent;
-import mineplex.core.portal.Portal;
-import mineplex.serverdata.Region;
-import mineplex.serverdata.commands.CommandCallback;
-import mineplex.serverdata.commands.RestartCommand;
-import mineplex.serverdata.commands.ServerCommand;
-
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -17,6 +8,18 @@ import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerListPingEvent;
import org.bukkit.plugin.java.JavaPlugin;
+import mineplex.core.common.util.F;
+import mineplex.core.common.util.UtilServer;
+import mineplex.core.portal.GenericServer;
+import mineplex.core.portal.Intent;
+import mineplex.core.portal.Portal;
+import mineplex.core.updater.event.RestartServerEvent;
+import mineplex.core.updater.event.RestartTriggerEvent;
+import mineplex.serverdata.Region;
+import mineplex.serverdata.commands.CommandCallback;
+import mineplex.serverdata.commands.RestartCommand;
+import mineplex.serverdata.commands.ServerCommand;
+
public class RestartHandler implements CommandCallback, Listener
{
private JavaPlugin _plugin;
@@ -51,6 +54,16 @@ public class RestartHandler implements CommandCallback, Listener
if (!serverName.equalsIgnoreCase(_serverName) || _region != region)
return;
+ if (UtilServer.CallEvent(new RestartTriggerEvent(RestartTriggerEvent.RestartReason.COMMAND)).isCancelled())
+ {
+ return;
+ }
+
+ if (UtilServer.CallEvent(new RestartServerEvent(RestartServerEvent.RestartReason.COMMAND)).isCancelled())
+ {
+ return;
+ }
+
_restarting = true;
for (Player player : Bukkit.getOnlinePlayers())
@@ -58,21 +71,15 @@ public class RestartHandler implements CommandCallback, Listener
player.sendMessage(F.main("Restart", "Server is restarting, you're being sent to a lobby."));
}
- Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(Bukkit.getPluginManager().getPlugins()[0], new Runnable()
+ Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(UtilServer.getPlugin(), () ->
{
- public void run()
- {
- Portal.getInstance().sendAllPlayersToGenericServer(GenericServer.HUB, Intent.KICK);
- }
+ Portal.getInstance().sendAllPlayersToGenericServer(GenericServer.HUB, Intent.KICK);
}, 60L);
- Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(Bukkit.getPluginManager().getPlugins()[0], new Runnable()
+ Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(UtilServer.getPlugin(), () ->
{
- public void run()
- {
- Bukkit.getServer().shutdown();
- }
+ Bukkit.getServer().shutdown();
}, 100L);
}
}
-}
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.Core/src/mineplex/core/updater/event/RestartServerEvent.java b/Plugins/Mineplex.Core/src/mineplex/core/updater/event/RestartServerEvent.java
index a2c7e4fe5..072b6ded6 100644
--- a/Plugins/Mineplex.Core/src/mineplex/core/updater/event/RestartServerEvent.java
+++ b/Plugins/Mineplex.Core/src/mineplex/core/updater/event/RestartServerEvent.java
@@ -8,6 +8,12 @@ public class RestartServerEvent extends Event implements Cancellable
{
private static final HandlerList handlers = new HandlerList();
private boolean _cancelled = false;
+ private final RestartReason _reason;
+
+ public RestartServerEvent(RestartReason reason)
+ {
+ _reason = reason;
+ }
public HandlerList getHandlers()
{
@@ -18,6 +24,11 @@ public class RestartServerEvent extends Event implements Cancellable
{
return handlers;
}
+
+ public RestartReason getReason()
+ {
+ return _reason;
+ }
@Override
public boolean isCancelled()
@@ -30,4 +41,10 @@ public class RestartServerEvent extends Event implements Cancellable
{
_cancelled = cancel;
}
-}
+
+ public enum RestartReason
+ {
+ COMMAND,
+ UPDATE
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.Core/src/mineplex/core/updater/event/RestartTriggerEvent.java b/Plugins/Mineplex.Core/src/mineplex/core/updater/event/RestartTriggerEvent.java
new file mode 100644
index 000000000..b897934e8
--- /dev/null
+++ b/Plugins/Mineplex.Core/src/mineplex/core/updater/event/RestartTriggerEvent.java
@@ -0,0 +1,50 @@
+package mineplex.core.updater.event;
+
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+public class RestartTriggerEvent extends Event implements Cancellable
+{
+ private static final HandlerList handlers = new HandlerList();
+ private boolean _cancelled = false;
+ private final RestartReason _reason;
+
+ public RestartTriggerEvent(RestartReason reason)
+ {
+ _reason = reason;
+ }
+
+ public HandlerList getHandlers()
+ {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList()
+ {
+ return handlers;
+ }
+
+ public RestartReason getReason()
+ {
+ return _reason;
+ }
+
+ @Override
+ public boolean isCancelled()
+ {
+ return _cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancel)
+ {
+ _cancelled = cancel;
+ }
+
+ public enum RestartReason
+ {
+ COMMAND,
+ UPDATE
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.Game.Clans/pom.xml b/Plugins/Mineplex.Game.Clans/pom.xml
index 73a6d5ad9..02d16f542 100644
--- a/Plugins/Mineplex.Game.Clans/pom.xml
+++ b/Plugins/Mineplex.Game.Clans/pom.xml
@@ -23,6 +23,11 @@
${project.groupId}
mineplex-minecraft-game-classcombat
${project.version}
+
+
+ ${project.groupId}
+ mineplex-clansqueue-common
+ ${project.version}
diff --git a/Plugins/Mineplex.Game.Clans/src/mineplex/game/clans/clans/ClansManager.java b/Plugins/Mineplex.Game.Clans/src/mineplex/game/clans/clans/ClansManager.java
index 036d7a543..52928ef33 100644
--- a/Plugins/Mineplex.Game.Clans/src/mineplex/game/clans/clans/ClansManager.java
+++ b/Plugins/Mineplex.Game.Clans/src/mineplex/game/clans/clans/ClansManager.java
@@ -30,6 +30,7 @@ import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
+import org.bukkit.event.player.PlayerLoginEvent.Result;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.ServerListPingEvent;
import org.bukkit.event.vehicle.VehicleEnterEvent;
@@ -38,6 +39,11 @@ import org.bukkit.plugin.java.JavaPlugin;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
+import com.mineplex.clansqueue.common.ClansQueueMessenger;
+import com.mineplex.clansqueue.common.QueueConstant;
+import com.mineplex.clansqueue.common.messages.ClansServerStatusMessage;
+import com.mineplex.clansqueue.common.messages.ServerOfflineMessage;
+import com.mineplex.clansqueue.common.messages.ServerOnlineMessage;
import mineplex.core.Managers;
import mineplex.core.MiniClientPlugin;
@@ -511,6 +517,10 @@ public class ClansManager extends MiniClientPlugin implements IRelat
_restartManager = new RestartManager(plugin);
generatePermissions();
+
+ ServerOnlineMessage message = new ServerOnlineMessage();
+ message.ServerName = UtilServer.getServerName();
+ ClansQueueMessenger.getMessenger(UtilServer.getServerName()).transmitMessage(message, QueueConstant.SERVICE_MESSENGER_IDENTIFIER);
}
private void generatePermissions()
@@ -1312,16 +1322,19 @@ public class ClansManager extends MiniClientPlugin implements IRelat
_restartManager.onDisable();
_observerManager.onDisable();
Managers.get(MountManager.class).onDisable();
+ ServerOfflineMessage message = new ServerOfflineMessage();
+ message.ServerName = UtilServer.getServerName();
+ ClansQueueMessenger.getMessenger(UtilServer.getServerName()).transmitMessage(message, QueueConstant.SERVICE_MESSENGER_IDENTIFIER);
}
- @EventHandler(priority = EventPriority.HIGHEST)
- public void onJoin(PlayerLoginEvent event)
+ @EventHandler
+ public void transmitQueueStatus(UpdateEvent event)
{
- if (_restartManager.isRestarting())
+ if (event.getType() != UpdateType.FAST)
{
return;
}
-
+
int online = 0;
for (Player player : UtilServer.getPlayers())
@@ -1334,14 +1347,23 @@ public class ClansManager extends MiniClientPlugin implements IRelat
online++;
}
- if (online >= UtilServer.getServer().getMaxPlayers() && !_clientManager.Get(event.getPlayer()).hasPermission(Perm.JOIN_FULL) && !event.getPlayer().isWhitelisted() && !event.getPlayer().isOp())
+ ClansServerStatusMessage message = new ClansServerStatusMessage();
+ message.ServerName = UtilServer.getServerName();
+ message.OpenSlots = Math.max(0, Bukkit.getMaxPlayers() - online);
+ ClansQueueMessenger.getMessenger(UtilServer.getServerName()).transmitMessage(message, QueueConstant.SERVICE_MESSENGER_IDENTIFIER);
+ }
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onJoin(PlayerLoginEvent event)
+ {
+ if (_restartManager.isRestarting())
{
- event.disallow(PlayerLoginEvent.Result.KICK_OTHER, "This Clans server is full! Try again soon");
+ return;
}
- else
+
+ if (event.getResult() == Result.KICK_FULL)
{
event.allow();
- event.setResult(PlayerLoginEvent.Result.ALLOWED);
}
}
diff --git a/Plugins/Mineplex.Game.Clans/src/mineplex/game/clans/restart/RestartManager.java b/Plugins/Mineplex.Game.Clans/src/mineplex/game/clans/restart/RestartManager.java
index d7d2ac9bb..ce55be4b8 100644
--- a/Plugins/Mineplex.Game.Clans/src/mineplex/game/clans/restart/RestartManager.java
+++ b/Plugins/Mineplex.Game.Clans/src/mineplex/game/clans/restart/RestartManager.java
@@ -3,16 +3,19 @@ package mineplex.game.clans.restart;
import java.util.Calendar;
import java.util.LinkedList;
-import net.minecraft.server.v1_8_R3.MinecraftServer;
-
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result;
+import org.bukkit.event.server.ServerCommandEvent;
import org.bukkit.event.server.ServerListPingEvent;
import org.bukkit.plugin.java.JavaPlugin;
+import com.mineplex.clansqueue.common.ClansQueueMessenger;
+import com.mineplex.clansqueue.common.QueueConstant;
+import com.mineplex.clansqueue.common.messages.ServerOfflineMessage;
+
import mineplex.core.MiniPlugin;
import mineplex.core.account.permissions.Permission;
import mineplex.core.account.permissions.PermissionGroup;
@@ -29,9 +32,11 @@ import mineplex.core.slack.SlackAPI;
import mineplex.core.slack.SlackMessage;
import mineplex.core.slack.SlackTeam;
import mineplex.core.updater.UpdateType;
+import mineplex.core.updater.event.RestartServerEvent;
import mineplex.core.updater.event.UpdateEvent;
import mineplex.game.clans.clans.ClansManager;
import mineplex.game.clans.gameplay.safelog.npc.NPCManager;
+import net.minecraft.server.v1_8_R3.MinecraftServer;
public class RestartManager extends MiniPlugin
{
@@ -82,7 +87,6 @@ public class RestartManager extends MiniPlugin
private void generatePermissions()
{
-
PermissionGroup.CMOD.setPermission(Perm.RESTART_COMMAND, false, true);
PermissionGroup.QAM.setPermission(Perm.RESTART_COMMAND, false, true);
PermissionGroup.ADMIN.setPermission(Perm.RESTART_COMMAND, true, true);
@@ -147,6 +151,9 @@ public class RestartManager extends MiniPlugin
public void restart()
{
+ ServerOfflineMessage message = new ServerOfflineMessage();
+ message.ServerName = UtilServer.getServerName();
+ ClansQueueMessenger.getMessenger(UtilServer.getServerName()).transmitMessage(message, QueueConstant.SERVICE_MESSENGER_IDENTIFIER);
Bukkit.broadcastMessage(F.main("Clans", "This Clans server will be restarting in " + F.elem(UtilTime.MakeStr(120000)) + "!"));
UtilTextMiddle.display(C.cRed + "Server Restart", C.cGray + "This server will restart in " + F.elem(UtilTime.MakeStr(120000)) + "!");
_restartTime = System.currentTimeMillis() + 120000;
@@ -170,6 +177,36 @@ public class RestartManager extends MiniPlugin
}
}
+ @EventHandler
+ public void onRestart(RestartServerEvent event)
+ {
+ event.setCancelled(true);
+
+ if (_restarting || _restartTime != -1)
+ {
+ return;
+ }
+
+ restart();
+ }
+
+ @EventHandler
+ public void onShutdownCommand(ServerCommandEvent event)
+ {
+ String command = event.getCommand().toLowerCase().trim();
+ if (command.equals("stop") || command.startsWith("stop "))
+ {
+ event.setCancelled(true);
+
+ if (_restarting || _restartTime != -1)
+ {
+ return;
+ }
+
+ restart();
+ }
+ }
+
@EventHandler
public void checkRestart(UpdateEvent event)
{
diff --git a/Plugins/Mineplex.Hub.Clans/pom.xml b/Plugins/Mineplex.Hub.Clans/pom.xml
index b5bc14c36..8da5f3fc8 100644
--- a/Plugins/Mineplex.Hub.Clans/pom.xml
+++ b/Plugins/Mineplex.Hub.Clans/pom.xml
@@ -23,6 +23,11 @@
${project.groupId}
mineplex-minecraft-game-core
${project.version}
+
+
+ ${project.groupId}
+ mineplex-clansqueue-common
+ ${project.version}
diff --git a/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/ClansHub.java b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/ClansHub.java
index e44595555..60788e367 100644
--- a/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/ClansHub.java
+++ b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/ClansHub.java
@@ -157,7 +157,7 @@ public class ClansHub extends JavaPlugin
BoosterManager boosterManager = new BoosterManager(this, "", clientManager, donationManager, inventoryManager, thankManager);
HubManager hubManager = new HubManager(this, blockRestore, clientManager, incognito, donationManager, inventoryManager, condition, disguiseManager, new TaskManager(this, clientManager), portal, partyManager, preferenceManager, petManager, pollManager, statsManager, achievementManager, hologramManager, npcManager, packetHandler, punish, serverStatusManager, customDataManager, thankManager, boosterManager, castleManager);
- ClansTransferManager serverManager = new ClansTransferManager(this, clientManager, donationManager, partyManager, portal, hubManager);
+ ClansTransferManager serverManager = new ClansTransferManager(this, clientManager, donationManager, partyManager, portal);
Chat chat = new Chat(this, incognito, clientManager, preferenceManager, achievementManager, serverStatusManager.getCurrentServerName());
new MessageManager(this, incognito, clientManager, preferenceManager, ignoreManager, punish, friendManager, chat);
diff --git a/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/ClansServerPage.java b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/ClansServerPage.java
index 887b41520..62620ca77 100644
--- a/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/ClansServerPage.java
+++ b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/ClansServerPage.java
@@ -5,6 +5,7 @@ import java.util.Collection;
import org.bukkit.Material;
import org.bukkit.entity.Player;
+import mineplex.clanshub.queue.HubQueueManager;
import mineplex.core.Managers;
import mineplex.core.account.CoreClientManager;
import mineplex.core.common.util.C;
@@ -22,6 +23,8 @@ import mineplex.game.clans.core.repository.tokens.SimpleClanToken;
*/
public class ClansServerPage extends ShopPageBase
{
+ private final HubQueueManager _queue = Managers.require(HubQueueManager.class);
+
public ClansServerPage(ClansTransferManager plugin, ClansServerShop shop, CoreClientManager clientManager,
DonationManager donationManager, Player player)
{
@@ -127,10 +130,12 @@ public class ClansServerPage extends ShopPageBase servers = UtilAlg.sortSet(getPlugin().getServers(true), (o1, o2) ->
diff --git a/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/ClansTransferManager.java b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/ClansTransferManager.java
index c36c47ee0..8e4dfbed6 100644
--- a/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/ClansTransferManager.java
+++ b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/ClansTransferManager.java
@@ -13,6 +13,7 @@ import org.bukkit.plugin.java.JavaPlugin;
import com.google.common.collect.Lists;
+import mineplex.clanshub.queue.HubQueueManager;
import mineplex.core.MiniDbClientPlugin;
import mineplex.core.account.CoreClientManager;
import mineplex.core.account.permissions.Permission;
@@ -41,26 +42,24 @@ public class ClansTransferManager extends MiniDbClientPlugin
{
STAFF_PAGE,
ALLOW_HARDCORE,
- JOIN_FULL,
}
private static final long SERVER_RELOAD_INTERVAL = 5000;
private PartyManager _party;
private Portal _portal;
- private HubManager _hub;
private Region _region;
private final Map _servers = new HashMap<>();
private boolean _loading = false;
private long _lastLoaded;
private ClansServerShop _serverShop;
+ private final HubQueueManager _queue = require(HubQueueManager.class);
- public ClansTransferManager(JavaPlugin plugin, CoreClientManager client, DonationManager donation, PartyManager party, Portal portal, HubManager hub)
+ public ClansTransferManager(JavaPlugin plugin, CoreClientManager client, DonationManager donation, PartyManager party, Portal portal)
{
super("Server Transfer", plugin, client);
_party = party;
_portal = portal;
- _hub = hub;
_region = plugin.getConfig().getBoolean("serverstatus.us") ? Region.US : Region.EU;
_serverShop = new ClansServerShop(this, client, donation);
@@ -71,7 +70,7 @@ public class ClansTransferManager extends MiniDbClientPlugin
{
PermissionGroup.TRAINEE.setPermission(Perm.STAFF_PAGE, true, true);
PermissionGroup.TRAINEE.setPermission(Perm.ALLOW_HARDCORE, true, true);
- PermissionGroup.ULTRA.setPermission(Perm.JOIN_FULL, true, true);
+ PermissionGroup.CONTENT.setPermission(Perm.ALLOW_HARDCORE, true, true);
}
/**
@@ -92,7 +91,7 @@ public class ClansTransferManager extends MiniDbClientPlugin
List servers = Lists.newArrayList();
for (ServerInfo info : _servers.values())
{
- if (!info.MOTD.equalsIgnoreCase("Restarting soon") || !onlineOnly)
+ if (!(info.MOTD.equalsIgnoreCase("Restarting soon") || _queue.getData(info) == null) || !onlineOnly)
{
servers.add(info);
}
@@ -109,7 +108,7 @@ public class ClansTransferManager extends MiniDbClientPlugin
{
for (ServerInfo server : _servers.values())
{
- if (server.Name.equalsIgnoreCase(name) && !server.MOTD.equalsIgnoreCase("Restarting soon"))
+ if (server.Name.equalsIgnoreCase(name) && !server.MOTD.equalsIgnoreCase("Restarting soon") && _queue.getData(server) != null)
{
return server;
}
@@ -136,19 +135,6 @@ public class ClansTransferManager extends MiniDbClientPlugin
}
}
- /**
- * Selects a server to send a player to
- * @param player The player to send
- * @param serverInfo The server to send the player to
- */
- public void selectServer(Player player, ServerInfo serverInfo)
- {
- player.leaveVehicle();
- player.eject();
-
- _portal.sendPlayerToServer(player, serverInfo.Name, Intent.PLAYER_REQUEST);
- }
-
@EventHandler
public void reloadServers(UpdateEvent event)
{
@@ -157,13 +143,10 @@ public class ClansTransferManager extends MiniDbClientPlugin
return;
}
_loading = true;
- final Runnable after = new Runnable()
+ final Runnable after = () ->
{
- public void run()
- {
- _lastLoaded = System.currentTimeMillis();
- _loading = false;
- }
+ _lastLoaded = System.currentTimeMillis();
+ _loading = false;
};
runAsync(() ->
{
diff --git a/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/JoinServerButton.java b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/JoinServerButton.java
index 2c15ae64d..b36467ec6 100644
--- a/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/JoinServerButton.java
+++ b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/JoinServerButton.java
@@ -3,6 +3,8 @@ package mineplex.clanshub;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
+import mineplex.clanshub.queue.HubQueueManager;
+import mineplex.core.Managers;
import mineplex.core.shop.item.IButton;
import mineplex.core.shop.page.ShopPageBase;
@@ -12,13 +14,12 @@ import mineplex.core.shop.page.ShopPageBase;
public class JoinServerButton implements IButton
{
private ShopPageBase, ?> _page;
- private ClansTransferManager _transferManager;
private ServerInfo _serverInfo;
+ private final HubQueueManager _queue = Managers.require(HubQueueManager.class);
- public JoinServerButton(ShopPageBase, ?> page, ClansTransferManager transferManager, ServerInfo serverInfo)
+ public JoinServerButton(ShopPageBase, ?> page, ServerInfo serverInfo)
{
_page = page;
- _transferManager = transferManager;
_serverInfo = serverInfo;
}
@@ -37,16 +38,13 @@ public class JoinServerButton implements IButton
{
if (serverInfo != null)
{
- System.out.println("Selecting server :" + serverInfo.Name);
- int slots = 1;
-
- if (serverInfo.getAvailableSlots() < slots && !_page.getClientManager().Get(player).hasPermission(ClansTransferManager.Perm.JOIN_FULL))
+ if (_queue.Get(player).TargetServer == null || !_queue.Get(player).TargetServer.equals(serverInfo.Name))
{
- _page.playDenySound(player);
+ _queue.attemptEnterQueue(player, _queue.getData(serverInfo));
}
else
{
- _transferManager.selectServer(player, serverInfo);
+ _queue.leaveQueue(player, true);
}
}
else
diff --git a/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/queue/HubQueueManager.java b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/queue/HubQueueManager.java
new file mode 100644
index 000000000..96a5402a9
--- /dev/null
+++ b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/queue/HubQueueManager.java
@@ -0,0 +1,353 @@
+package mineplex.clanshub.queue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.player.PlayerQuitEvent;
+
+import com.mineplex.clansqueue.common.ClansQueueMessenger;
+import com.mineplex.clansqueue.common.QueueConstant;
+import com.mineplex.clansqueue.common.messages.PlayerJoinQueueCallbackMessage;
+import com.mineplex.clansqueue.common.messages.PlayerJoinQueueMessage;
+import com.mineplex.clansqueue.common.messages.PlayerLeaveQueueMessage;
+import com.mineplex.clansqueue.common.messages.PlayerSendToServerMessage;
+import com.mineplex.clansqueue.common.messages.QueueDeleteMessage;
+import com.mineplex.clansqueue.common.messages.QueuePauseBroadcastMessage;
+import com.mineplex.clansqueue.common.messages.QueuePauseUpdateMessage;
+import com.mineplex.clansqueue.common.messages.QueueStatusMessage;
+
+import mineplex.clanshub.ClansTransferManager;
+import mineplex.clanshub.ServerInfo;
+import mineplex.clanshub.queue.data.ClansQueueData;
+import mineplex.clanshub.queue.data.QueuePlayerData;
+import mineplex.core.Managers;
+import mineplex.core.MiniClientPlugin;
+import mineplex.core.ReflectivelyCreateMiniPlugin;
+import mineplex.core.account.CoreClientManager;
+import mineplex.core.account.permissions.Permission;
+import mineplex.core.account.permissions.PermissionGroup;
+import mineplex.core.command.CommandBase;
+import mineplex.core.common.util.C;
+import mineplex.core.common.util.F;
+import mineplex.core.common.util.UtilPlayer;
+import mineplex.core.common.util.UtilServer;
+import mineplex.core.common.util.UtilTime;
+import mineplex.core.common.util.UtilTime.TimeUnit;
+import mineplex.core.portal.Intent;
+import mineplex.core.portal.Portal;
+import mineplex.core.portal.events.ServerTransferEvent;
+import mineplex.core.punish.clans.ClansBanManager;
+
+@ReflectivelyCreateMiniPlugin
+public class HubQueueManager extends MiniClientPlugin
+{
+ public enum Perm implements Permission
+ {
+ JOIN_PAUSED_QUEUE,
+ TOGGLE_QUEUE_PAUSE,
+ LIST_QUEUES,
+ }
+
+ public enum QueuePriority implements Permission
+ {
+ BYPASS(-1, PermissionGroup.CONTENT, PermissionGroup.TRAINEE),
+ PRIORITY(7, PermissionGroup.BUILDER),
+ ETERNAL(6, PermissionGroup.ETERNAL),
+ TITAN(5, PermissionGroup.TITAN),
+ LEGEND(4, PermissionGroup.LEGEND),
+ HERO(3, PermissionGroup.HERO),
+ ULTRA(2, PermissionGroup.ULTRA),
+ DEFAULT(1, PermissionGroup.PLAYER)
+ ;
+
+ private final int _weight;
+ private final List _granted;
+
+ private QueuePriority(int weight, PermissionGroup... granted)
+ {
+ _weight = weight;
+ _granted = Collections.unmodifiableList(Arrays.asList(granted));
+ }
+
+ public int getWeight()
+ {
+ return _weight;
+ }
+
+ public List getGranted()
+ {
+ return _granted;
+ }
+ }
+
+ private final CoreClientManager _clientManager = require(CoreClientManager.class);
+ private final ClansBanManager _punish = require(ClansBanManager.class);
+ private final Portal _portal = require(Portal.class);
+ private final Comparator _prioritySorter = (q1, q2) ->
+ {
+ if (q1.getWeight() == -1 && q2.getWeight() != -1)
+ {
+ return -1;
+ }
+ if (q2.getWeight() == -1 && q1.getWeight() != -1)
+ {
+ return 1;
+ }
+
+ return Integer.compare(q1.getWeight(), q2.getWeight());
+ };
+ private final Map _queueData = new HashMap<>();
+ private final ClansQueueMessenger _messenger;
+
+ private HubQueueManager()
+ {
+ super("Queue Manager");
+
+ generatePermissions();
+ _messenger = ClansQueueMessenger.getMessenger(UtilServer.getServerName());
+
+ _messenger.registerListener(PlayerJoinQueueCallbackMessage.class, (callback, origin) ->
+ {
+ runSync(() ->
+ {
+ Player player = Bukkit.getPlayer(callback.PlayerUUID);
+ if (player != null)
+ {
+ QueuePlayerData data = Get(player);
+ data.Queued = true;
+ data.QueuePosition = callback.Position;
+ UtilPlayer.message(player, F.main(getName(), "You have joined the queue for server " + F.elem(data.TargetServer) + "! Your position: " + F.greenElem("#" + data.QueuePosition)));
+ }
+ });
+ });
+ _messenger.registerListener(PlayerSendToServerMessage.class, (callback, origin) ->
+ {
+ runSync(() ->
+ {
+ Player player = Bukkit.getPlayer(callback.PlayerUUID);
+ if (player != null)
+ {
+ player.leaveVehicle();
+ player.eject();
+ _portal.sendPlayerToServer(player, callback.TargetServer, Intent.FORCE_TRANSFER);
+ }
+ });
+ });
+ _messenger.registerListener(QueueStatusMessage.class, (status, origin) ->
+ {
+ runSync(() ->
+ {
+ status.Snapshots.forEach(snapshot ->
+ {
+ ClansQueueData data = _queueData.computeIfAbsent(snapshot.ServerName, (name) -> new ClansQueueData(name));
+
+ data.QueueMembers = snapshot.Queue.size();
+ data.QueuePaused = snapshot.Paused;
+ snapshot.Queue.entrySet().forEach(entry ->
+ {
+ Player player = Bukkit.getPlayer(entry.getKey());
+ if (player != null)
+ {
+ Get(player).QueuePosition = entry.getValue();
+ UtilPlayer.message(player, F.main(getName(), "Your position: " + F.greenElem("#" + entry.getValue())));
+ }
+ });
+ });
+ });
+ });
+ _messenger.registerListener(QueuePauseBroadcastMessage.class, (broadcast, origin) ->
+ {
+ runSync(() ->
+ {
+ ClansQueueData data = _queueData.computeIfAbsent(broadcast.ServerName, (name) -> new ClansQueueData(name));
+ data.QueuePaused = broadcast.Paused;
+ GetValues().forEach(qp ->
+ {
+ if (qp.TargetServer != null && qp.TargetServer.equals(broadcast.ServerName))
+ {
+ UtilPlayer.message(Bukkit.getPlayer(qp.UniqueId), F.main(getName(), "Queue pause status: " + F.elem(broadcast.Paused)));
+ }
+ });
+ });
+ });
+ _messenger.registerListener(QueueDeleteMessage.class, (delete, origin) ->
+ {
+ runSync(() ->
+ {
+ GetValues().forEach(qp ->
+ {
+ if (qp.TargetServer != null && qp.TargetServer.equals(delete.ServerName))
+ {
+ UtilPlayer.message(Bukkit.getPlayer(qp.UniqueId), F.main(getName(), "Queue deleted."));
+ }
+ qp.Queued = false;
+ qp.QueuePosition = 0;
+ qp.TargetServer = null;
+ });
+ _queueData.remove(delete.ServerName);
+ });
+ });
+ addCommand(new CommandBase(this, Perm.TOGGLE_QUEUE_PAUSE, "pausequeue")
+ {
+ @Override
+ public void Execute(Player caller, String[] args)
+ {
+ if (args.length < 1)
+ {
+ UtilPlayer.message(caller, F.main(getName(), "Usage: /pausequeue "));
+ return;
+ }
+ ServerInfo info = Managers.get(ClansTransferManager.class).getServer(args[0]);
+ if (info != null)
+ {
+ ClansQueueData data = getData(info);
+ if (data != null)
+ {
+ QueuePauseUpdateMessage message = new QueuePauseUpdateMessage();
+ message.ServerName = data.ServerName;
+ message.Paused = !data.QueuePaused;
+ _messenger.transmitMessage(message, QueueConstant.SERVICE_MESSENGER_IDENTIFIER);
+ UtilPlayer.message(caller, F.main(getName(), "Toggling queue pause"));
+ return;
+ }
+ }
+ UtilPlayer.message(caller, F.main(getName(), "Queue not found"));
+ }
+ });
+ addCommand(new CommandBase(this, Perm.LIST_QUEUES, "listqueues")
+ {
+ @Override
+ public void Execute(Player caller, String[] args)
+ {
+ StringBuilder queues = new StringBuilder("Queues: [");
+ queues.append(_queueData.values().stream().map(data -> data.ServerName).collect(Collectors.joining(", ")));
+ queues.append(']');
+ UtilPlayer.message(caller, F.main(getName(), queues.toString()));
+ }
+ });
+ }
+
+ private void generatePermissions()
+ {
+ for (QueuePriority priority : QueuePriority.values())
+ {
+ priority.getGranted().forEach(group -> group.setPermission(priority, true, true));
+ }
+ PermissionGroup.ADMIN.setPermission(Perm.JOIN_PAUSED_QUEUE, true, true);
+ PermissionGroup.ADMIN.setPermission(Perm.LIST_QUEUES, true, true);
+ PermissionGroup.ADMIN.setPermission(Perm.TOGGLE_QUEUE_PAUSE, true, true);
+ }
+
+ public QueuePriority getHighestPriority(Player player)
+ {
+ Optional opt = Stream.of(QueuePriority.values()).filter(_clientManager.Get(player)::hasPermission).sorted(_prioritySorter).findFirst();
+
+ if (opt.isPresent())
+ {
+ return opt.get();
+ }
+
+ return QueuePriority.DEFAULT;
+ }
+
+ public ClansQueueData getData(ServerInfo info)
+ {
+ return _queueData.get(info.Name);
+ }
+
+ public void attemptEnterQueue(Player player, ClansQueueData data)
+ {
+ if (Get(player).TargetServer != null)
+ {
+ if (Get(player).Queued)
+ {
+ UtilPlayer.message(player, F.main(getName(), "You are already in a queue!"));
+ }
+ else
+ {
+ UtilPlayer.message(player, F.main(getName(), "You are already entering a queue!"));
+ }
+ return;
+ }
+ if (data.QueuePaused && !_clientManager.Get(player).hasPermission(Perm.JOIN_PAUSED_QUEUE))
+ {
+ UtilPlayer.message(player, F.main(getName(), "That queue is paused and cannot currently be joined!"));
+ return;
+ }
+ Get(player).TargetServer = data.ServerName;
+ _punish.loadClient(player.getUniqueId(), client ->
+ {
+ if (client.isBanned())
+ {
+ Get(player).TargetServer = null;
+ String time = UtilTime.convertString(client.getLongestBan().getTimeLeft(), 0, TimeUnit.FIT);
+
+ if (client.getLongestBan().isPermanent())
+ {
+ time = "Permanent";
+ }
+
+ String reason = C.cRedB + "You are banned from Clans for " + time +
+ "\n" + C.cWhite + client.getLongestBan().getReason();
+ UtilPlayer.message(player, reason);
+ }
+ else
+ {
+ QueuePriority priority = getHighestPriority(player);
+ PlayerJoinQueueMessage message = new PlayerJoinQueueMessage();
+ message.PlayerUUID = player.getUniqueId();
+ message.TargetServer = data.ServerName;
+ message.PlayerPriority = priority.getWeight();
+ _messenger.transmitMessage(message, QueueConstant.SERVICE_MESSENGER_IDENTIFIER);
+ UtilPlayer.message(player, F.main(getName(), "Joining queue..."));
+ }
+ });
+ }
+
+ public void leaveQueue(Player player, boolean informFailure)
+ {
+ if (!Get(player).Queued && informFailure)
+ {
+ UtilPlayer.message(player, F.main(getName(), "You are not part of a queue!"));
+ return;
+ }
+ PlayerLeaveQueueMessage message = new PlayerLeaveQueueMessage();
+ message.PlayerUUID = player.getUniqueId();
+ message.TargetServer = Get(player).TargetServer;
+ _messenger.transmitMessage(message, QueueConstant.SERVICE_MESSENGER_IDENTIFIER);
+ Get(player).TargetServer = null;
+ Get(player).QueuePosition = 0;
+ Get(player).Queued = false;
+ UtilPlayer.message(player, F.main(getName(), "You have left the queue for " + F.elem(message.TargetServer) + "!"));
+ }
+
+ @EventHandler
+ public void onQuit(PlayerQuitEvent event)
+ {
+ leaveQueue(event.getPlayer(), false);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onTransfer(ServerTransferEvent event)
+ {
+ leaveQueue(event.getPlayer(), false);
+ }
+
+ @Override
+ protected QueuePlayerData addPlayer(UUID uuid)
+ {
+ return new QueuePlayerData(uuid);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/queue/data/ClansQueueData.java b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/queue/data/ClansQueueData.java
new file mode 100644
index 000000000..4512e409e
--- /dev/null
+++ b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/queue/data/ClansQueueData.java
@@ -0,0 +1,30 @@
+package mineplex.clanshub.queue.data;
+
+public class ClansQueueData
+{
+ public final String ServerName;
+ public int QueueMembers;
+ public boolean QueuePaused;
+
+ public ClansQueueData(String serverName)
+ {
+ ServerName = serverName;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return ServerName.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o == null || !getClass().isInstance(o))
+ {
+ return false;
+ }
+
+ return ((ClansQueueData)o).ServerName.equals(ServerName);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/queue/data/QueuePlayerData.java b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/queue/data/QueuePlayerData.java
new file mode 100644
index 000000000..c0b05f974
--- /dev/null
+++ b/Plugins/Mineplex.Hub.Clans/src/mineplex/clanshub/queue/data/QueuePlayerData.java
@@ -0,0 +1,36 @@
+package mineplex.clanshub.queue.data;
+
+import java.util.UUID;
+
+public class QueuePlayerData
+{
+ public final UUID UniqueId;
+
+ public boolean Queued;
+
+ public int QueuePosition;
+
+ public String TargetServer;
+
+ public QueuePlayerData(UUID uuid)
+ {
+ UniqueId = uuid;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o == null || !getClass().isInstance(o))
+ {
+ return false;
+ }
+
+ return UniqueId.equals(((QueuePlayerData)o).UniqueId);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return UniqueId.hashCode();
+ }
+}
\ No newline at end of file
diff --git a/Plugins/pom.xml b/Plugins/pom.xml
index 95880e508..e6a20d519 100644
--- a/Plugins/pom.xml
+++ b/Plugins/pom.xml
@@ -25,6 +25,8 @@
Mineplex.Database
Mineplex.DDoSProtectionSwitcher
Mineplex.EnjinTranslator
+ Mineplex.ClansQueue.Common
+ Mineplex.ClansQueue
Mineplex.Game.Clans
Mineplex.Game.Clans.Core
Mineplex.Game.Clans.Compensation