entry : multimap.entries()) {
+ human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
+ }
+ serializeMultimap(human, true, out);
+ } else {
+ serializeMultiset(multimap.keys(), out);
+ }
+ break;
+ case "Proxy":
+ out.writeUTF("Proxy");
+ out.writeUTF(plugin.getConfiguration().getServerId());
+ break;
+ case "PlayerProxy":
+ String username = in.readUTF();
+ out.writeUTF("PlayerProxy");
+ out.writeUTF(username);
+ out.writeUTF(plugin.getApi().getProxy(plugin.getUuidTranslator().getTranslatedUuid(username, true)));
+ break;
+ default:
+ return;
+ }
+
+ ((Server) event.getSender()).sendData(currentChannel, out.toByteArray());
+ });
+ }
+ }
+
+ @Override
+ @EventHandler
+ public void onPubSubMessage(PubSubMessageEvent event) {
+ if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getApi().getServerId())) {
+ String message = event.getMessage();
+ if (message.startsWith("/"))
+ message = message.substring(1);
+ plugin.logInfo("Invoking command via PubSub: /" + message);
+ ((Plugin) plugin).getProxy().getPluginManager().dispatchCommand(RedisBungeeCommandSender.getSingleton(), message);
+ }
+ }
+}
diff --git a/RedisBungee-Velocity/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java b/RedisBungee-Velocity/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java
new file mode 100644
index 0000000..cdc135f
--- /dev/null
+++ b/RedisBungee-Velocity/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/commands/RedisBungeeCommands.java
@@ -0,0 +1,343 @@
+package com.imaginarycode.minecraft.redisbungee.commands;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
+import com.imaginarycode.minecraft.redisbungee.RedisBungeeBungeePlugin;
+import com.imaginarycode.minecraft.redisbungee.internal.RedisBungeePlugin;
+import net.md_5.bungee.api.ChatColor;
+import net.md_5.bungee.api.CommandSender;
+import net.md_5.bungee.api.chat.BaseComponent;
+import net.md_5.bungee.api.chat.ComponentBuilder;
+import net.md_5.bungee.api.chat.TextComponent;
+import net.md_5.bungee.api.config.ServerInfo;
+import net.md_5.bungee.api.connection.ProxiedPlayer;
+import net.md_5.bungee.api.plugin.Command;
+
+import java.net.InetAddress;
+import java.text.SimpleDateFormat;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+
+/**
+ * This class contains subclasses that are used for the commands RedisBungee overrides or includes: /glist, /find and /lastseen.
+ *
+ * All classes use the {@link RedisBungeeAPI}.
+ *
+ * @author tuxed
+ * @since 0.2.3
+ */
+public class RedisBungeeCommands {
+ private static final BaseComponent[] NO_PLAYER_SPECIFIED =
+ new ComponentBuilder("You must specify a player name.").color(ChatColor.RED).create();
+ private static final BaseComponent[] PLAYER_NOT_FOUND =
+ new ComponentBuilder("No such player found.").color(ChatColor.RED).create();
+ private static final BaseComponent[] NO_COMMAND_SPECIFIED =
+ new ComponentBuilder("You must specify a command to be run.").color(ChatColor.RED).create();
+
+ private static String playerPlural(int num) {
+ return num == 1 ? num + " player is" : num + " players are";
+ }
+
+ public static class GlistCommand extends Command {
+ private final RedisBungeeBungeePlugin plugin;
+
+ public GlistCommand(RedisBungeeBungeePlugin plugin) {
+ super("glist", "bungeecord.command.list", "redisbungee", "rglist");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void execute(final CommandSender sender, final String[] args) {
+ plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
+ @Override
+ public void run() {
+ int count = plugin.getApi().getPlayerCount();
+ BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW)
+ .append(playerPlural(count) + " currently online.").create();
+ if (args.length > 0 && args[0].equals("showall")) {
+ Multimap serverToPlayers = plugin.getApi().getServerToPlayers();
+ Multimap human = HashMultimap.create();
+ for (Map.Entry entry : serverToPlayers.entries()) {
+ human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
+ }
+ for (String server : new TreeSet<>(serverToPlayers.keySet())) {
+ TextComponent serverName = new TextComponent();
+ serverName.setColor(ChatColor.GREEN);
+ serverName.setText("[" + server + "] ");
+ TextComponent serverCount = new TextComponent();
+ serverCount.setColor(ChatColor.YELLOW);
+ serverCount.setText("(" + serverToPlayers.get(server).size() + "): ");
+ TextComponent serverPlayers = new TextComponent();
+ serverPlayers.setColor(ChatColor.WHITE);
+ serverPlayers.setText(Joiner.on(", ").join(human.get(server)));
+ sender.sendMessage(serverName, serverCount, serverPlayers);
+ }
+ sender.sendMessage(playersOnline);
+ } else {
+ sender.sendMessage(playersOnline);
+ sender.sendMessage(new ComponentBuilder("To see all players online, use /glist showall.").color(ChatColor.YELLOW).create());
+ }
+ }
+ });
+ }
+ }
+
+ public static class FindCommand extends Command {
+ private final RedisBungeeBungeePlugin plugin;
+
+ public FindCommand(RedisBungeeBungeePlugin plugin) {
+ super("find", "bungeecord.command.find", "rfind");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void execute(final CommandSender sender, final String[] args) {
+ plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
+ @Override
+ public void run() {
+ if (args.length > 0) {
+ UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
+ if (uuid == null) {
+ sender.sendMessage(PLAYER_NOT_FOUND);
+ return;
+ }
+ ServerInfo si = plugin.getProxy().getServerInfo(plugin.getApi().getServerFor(uuid));
+ if (si != null) {
+ TextComponent message = new TextComponent();
+ message.setColor(ChatColor.BLUE);
+ message.setText(args[0] + " is on " + si.getName() + ".");
+ sender.sendMessage(message);
+ } else {
+ sender.sendMessage(PLAYER_NOT_FOUND);
+ }
+ } else {
+ sender.sendMessage(NO_PLAYER_SPECIFIED);
+ }
+ }
+ });
+ }
+ }
+
+ public static class LastSeenCommand extends Command {
+ private final RedisBungeeBungeePlugin plugin;
+
+ public LastSeenCommand(RedisBungeeBungeePlugin plugin) {
+ super("lastseen", "redisbungee.command.lastseen", "rlastseen");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void execute(final CommandSender sender, final String[] args) {
+ plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
+ @Override
+ public void run() {
+ if (args.length > 0) {
+ UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
+ if (uuid == null) {
+ sender.sendMessage(PLAYER_NOT_FOUND);
+ return;
+ }
+ long secs = plugin.getApi().getLastOnline(uuid);
+ TextComponent message = new TextComponent();
+ if (secs == 0) {
+ message.setColor(ChatColor.GREEN);
+ message.setText(args[0] + " is currently online.");
+ } else if (secs != -1) {
+ message.setColor(ChatColor.BLUE);
+ message.setText(args[0] + " was last online on " + new SimpleDateFormat().format(secs) + ".");
+ } else {
+ message.setColor(ChatColor.RED);
+ message.setText(args[0] + " has never been online.");
+ }
+ sender.sendMessage(message);
+ } else {
+ sender.sendMessage(NO_PLAYER_SPECIFIED);
+ }
+ }
+ });
+ }
+ }
+
+ public static class IpCommand extends Command {
+ private final RedisBungeeBungeePlugin plugin;
+
+ public IpCommand(RedisBungeeBungeePlugin plugin) {
+ super("ip", "redisbungee.command.ip", "playerip", "rip", "rplayerip");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void execute(final CommandSender sender, final String[] args) {
+ plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
+ @Override
+ public void run() {
+ if (args.length > 0) {
+ UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
+ if (uuid == null) {
+ sender.sendMessage(PLAYER_NOT_FOUND);
+ return;
+ }
+ InetAddress ia = plugin.getApi().getPlayerIp(uuid);
+ if (ia != null) {
+ TextComponent message = new TextComponent();
+ message.setColor(ChatColor.GREEN);
+ message.setText(args[0] + " is connected from " + ia.toString() + ".");
+ sender.sendMessage(message);
+ } else {
+ sender.sendMessage(PLAYER_NOT_FOUND);
+ }
+ } else {
+ sender.sendMessage(NO_PLAYER_SPECIFIED);
+ }
+ }
+ });
+ }
+ }
+
+ public static class PlayerProxyCommand extends Command {
+ private final RedisBungeeBungeePlugin plugin;
+
+ public PlayerProxyCommand(RedisBungeeBungeePlugin plugin) {
+ super("pproxy", "redisbungee.command.pproxy");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void execute(final CommandSender sender, final String[] args) {
+ plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
+ @Override
+ public void run() {
+ if (args.length > 0) {
+ UUID uuid = plugin.getUuidTranslator().getTranslatedUuid(args[0], true);
+ if (uuid == null) {
+ sender.sendMessage(PLAYER_NOT_FOUND);
+ return;
+ }
+ String proxy = plugin.getApi().getProxy(uuid);
+ if (proxy != null) {
+ TextComponent message = new TextComponent();
+ message.setColor(ChatColor.GREEN);
+ message.setText(args[0] + " is connected to " + proxy + ".");
+ sender.sendMessage(message);
+ } else {
+ sender.sendMessage(PLAYER_NOT_FOUND);
+ }
+ } else {
+ sender.sendMessage(NO_PLAYER_SPECIFIED);
+ }
+ }
+ });
+ }
+ }
+
+ public static class SendToAll extends Command {
+ private final RedisBungeeBungeePlugin plugin;
+
+ public SendToAll(RedisBungeeBungeePlugin plugin) {
+ super("sendtoall", "redisbungee.command.sendtoall", "rsendtoall");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ if (args.length > 0) {
+ String command = Joiner.on(" ").skipNulls().join(args);
+ plugin.getApi().sendProxyCommand(command);
+ TextComponent message = new TextComponent();
+ message.setColor(ChatColor.GREEN);
+ message.setText("Sent the command /" + command + " to all proxies.");
+ sender.sendMessage(message);
+ } else {
+ sender.sendMessage(NO_COMMAND_SPECIFIED);
+ }
+ }
+ }
+
+ public static class ServerId extends Command {
+ private final RedisBungeeBungeePlugin plugin;
+
+ public ServerId(RedisBungeeBungeePlugin plugin) {
+ super("serverid", "redisbungee.command.serverid", "rserverid");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] args) {
+ TextComponent textComponent = new TextComponent();
+ textComponent.setText("You are on " + plugin.getApi().getServerId() + ".");
+ textComponent.setColor(ChatColor.YELLOW);
+ sender.sendMessage(textComponent);
+ }
+ }
+
+ public static class ServerIds extends Command {
+ private final RedisBungeeBungeePlugin plugin;
+ public ServerIds(RedisBungeeBungeePlugin plugin) {
+ super("serverids", "redisbungee.command.serverids");
+ this.plugin =plugin;
+ }
+
+ @Override
+ public void execute(CommandSender sender, String[] strings) {
+ TextComponent textComponent = new TextComponent();
+ textComponent.setText("All server IDs: " + Joiner.on(", ").join(plugin.getApi().getAllServers()));
+ textComponent.setColor(ChatColor.YELLOW);
+ sender.sendMessage(textComponent);
+ }
+ }
+
+ public static class PlistCommand extends Command {
+ private final RedisBungeeBungeePlugin plugin;
+
+ public PlistCommand(RedisBungeeBungeePlugin plugin) {
+ super("plist", "redisbungee.command.plist", "rplist");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void execute(final CommandSender sender, final String[] args) {
+ plugin.getProxy().getScheduler().runAsync(plugin, new Runnable() {
+ @Override
+ public void run() {
+ String proxy = args.length >= 1 ? args[0] : plugin.getConfiguration().getServerId();
+ if (!plugin.getServerIds().contains(proxy)) {
+ sender.sendMessage(new ComponentBuilder(proxy + " is not a valid proxy. See /serverids for valid proxies.").color(ChatColor.RED).create());
+ return;
+ }
+ Set players = plugin.getApi().getPlayersOnProxy(proxy);
+ BaseComponent[] playersOnline = new ComponentBuilder("").color(ChatColor.YELLOW)
+ .append(playerPlural(players.size()) + " currently on proxy " + proxy + ".").create();
+ if (args.length >= 2 && args[1].equals("showall")) {
+ Multimap serverToPlayers = plugin.getApi().getServerToPlayers();
+ Multimap human = HashMultimap.create();
+ for (Map.Entry entry : serverToPlayers.entries()) {
+ if (players.contains(entry.getValue())) {
+ human.put(entry.getKey(), plugin.getUuidTranslator().getNameFromUuid(entry.getValue(), false));
+ }
+ }
+ for (String server : new TreeSet<>(human.keySet())) {
+ TextComponent serverName = new TextComponent();
+ serverName.setColor(ChatColor.RED);
+ serverName.setText("[" + server + "] ");
+ TextComponent serverCount = new TextComponent();
+ serverCount.setColor(ChatColor.YELLOW);
+ serverCount.setText("(" + human.get(server).size() + "): ");
+ TextComponent serverPlayers = new TextComponent();
+ serverPlayers.setColor(ChatColor.WHITE);
+ serverPlayers.setText(Joiner.on(", ").join(human.get(server)));
+ sender.sendMessage(serverName, serverCount, serverPlayers);
+ }
+ sender.sendMessage(playersOnline);
+ } else {
+ sender.sendMessage(playersOnline);
+ sender.sendMessage(new ComponentBuilder("To see all players online, use /plist " + proxy + " showall.").color(ChatColor.YELLOW).create());
+ }
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/RedisBungee-Velocity/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/events/PlayerChangedServerNetworkEvent.java b/RedisBungee-Velocity/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/events/PlayerChangedServerNetworkEvent.java
new file mode 100644
index 0000000..ac01a96
--- /dev/null
+++ b/RedisBungee-Velocity/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/events/PlayerChangedServerNetworkEvent.java
@@ -0,0 +1,38 @@
+package com.imaginarycode.minecraft.redisbungee.events;
+
+import net.md_5.bungee.api.plugin.Event;
+
+import java.util.UUID;
+
+/**
+ * This event is sent when a player connects to a new server. RedisBungee sends the event only when
+ * the proxy the player has been connected to is different than the local proxy.
+ *
+ * This event corresponds to {@link net.md_5.bungee.api.event.ServerConnectedEvent}, and is fired
+ * asynchronously.
+ *
+ * @since 0.3.4
+ */
+public class PlayerChangedServerNetworkEvent extends Event {
+ private final UUID uuid;
+ private final String previousServer;
+ private final String server;
+
+ public PlayerChangedServerNetworkEvent(UUID uuid, String previousServer, String server) {
+ this.uuid = uuid;
+ this.previousServer = previousServer;
+ this.server = server;
+ }
+
+ public UUID getUuid() {
+ return uuid;
+ }
+
+ public String getServer() {
+ return server;
+ }
+
+ public String getPreviousServer() {
+ return previousServer;
+ }
+}
diff --git a/RedisBungee-Velocity/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/events/PlayerJoinedNetworkEvent.java b/RedisBungee-Velocity/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/events/PlayerJoinedNetworkEvent.java
new file mode 100644
index 0000000..b9eac18
--- /dev/null
+++ b/RedisBungee-Velocity/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/events/PlayerJoinedNetworkEvent.java
@@ -0,0 +1,26 @@
+package com.imaginarycode.minecraft.redisbungee.events;
+
+import net.md_5.bungee.api.plugin.Event;
+
+import java.util.UUID;
+
+/**
+ * This event is sent when a player joins the network. RedisBungee sends the event only when
+ * the proxy the player has been connected to is different than the local proxy.
+ *
+ * This event corresponds to {@link net.md_5.bungee.api.event.PostLoginEvent}, and is fired
+ * asynchronously.
+ *
+ * @since 0.3.4
+ */
+public class PlayerJoinedNetworkEvent extends Event {
+ private final UUID uuid;
+
+ public PlayerJoinedNetworkEvent(UUID uuid) {
+ this.uuid = uuid;
+ }
+
+ public UUID getUuid() {
+ return uuid;
+ }
+}
diff --git a/RedisBungee-Velocity/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/events/PlayerLeftNetworkEvent.java b/RedisBungee-Velocity/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/events/PlayerLeftNetworkEvent.java
new file mode 100644
index 0000000..5e9e5ab
--- /dev/null
+++ b/RedisBungee-Velocity/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/events/PlayerLeftNetworkEvent.java
@@ -0,0 +1,26 @@
+package com.imaginarycode.minecraft.redisbungee.events;
+
+import net.md_5.bungee.api.plugin.Event;
+
+import java.util.UUID;
+
+/**
+ * This event is sent when a player disconnects. RedisBungee sends the event only when
+ * the proxy the player has been connected to is different than the local proxy.
+ *
+ * This event corresponds to {@link net.md_5.bungee.api.event.PlayerDisconnectEvent}, and is fired
+ * asynchronously.
+ *
+ * @since 0.3.4
+ */
+public class PlayerLeftNetworkEvent extends Event {
+ private final UUID uuid;
+
+ public PlayerLeftNetworkEvent(UUID uuid) {
+ this.uuid = uuid;
+ }
+
+ public UUID getUuid() {
+ return uuid;
+ }
+}
diff --git a/RedisBungee-Velocity/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/events/PubSubMessageEvent.java b/RedisBungee-Velocity/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/events/PubSubMessageEvent.java
new file mode 100644
index 0000000..32bcb40
--- /dev/null
+++ b/RedisBungee-Velocity/RedisBungee-Bungee/src/main/java/com/imaginarycode/minecraft/redisbungee/events/PubSubMessageEvent.java
@@ -0,0 +1,29 @@
+package com.imaginarycode.minecraft.redisbungee.events;
+
+import net.md_5.bungee.api.plugin.Event;
+
+/**
+ * This event is posted when a PubSub message is received.
+ *
+ * Warning: This event is fired in a separate thread!
+ *
+ * @since 0.2.6
+ */
+
+public class PubSubMessageEvent extends Event {
+ private final String channel;
+ private final String message;
+
+ public PubSubMessageEvent(String channel, String message) {
+ this.channel = channel;
+ this.message = message;
+ }
+
+ public String getChannel() {
+ return channel;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+}
diff --git a/RedisBungee-Velocity/RedisBungee-Bungee/src/main/resources/plugin.yml b/RedisBungee-Velocity/RedisBungee-Bungee/src/main/resources/plugin.yml
new file mode 100644
index 0000000..797ef14
--- /dev/null
+++ b/RedisBungee-Velocity/RedisBungee-Bungee/src/main/resources/plugin.yml
@@ -0,0 +1,9 @@
+name: RedisBungee
+main: com.imaginarycode.minecraft.redisbungee.RedisBungeeVelocityPlugin
+version: ${project.version}
+author: Chunkr and Govindas limework
+authors:
+ - chunkr
+ - Govindas Limework
+# This is used so that we can automatically override default BungeeCord behavior.
+softDepends: ["cmd_find", "cmd_list"]
\ No newline at end of file
diff --git a/RedisBungee-Velocity/pom.xml b/RedisBungee-Velocity/pom.xml
new file mode 100644
index 0000000..2106afb
--- /dev/null
+++ b/RedisBungee-Velocity/pom.xml
@@ -0,0 +1,125 @@
+
+
+
+ RedisBungee
+ com.imaginarycode.minecraft
+ 0.8.0-SNAPSHOT
+
+ 4.0.0
+
+ RedisBungee-Velocity
+
+
+ 8
+ 8
+
+
+
+ papermc-repo
+ https://repo.papermc.io/repository/maven-public/
+
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.3.2
+
+
+ ../javadoc
+ ${project.name}
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.4
+
+
+ package
+ shade
+
+
+
+ redis.clients.jedis
+ com.imaginarycode.minecraft.redisbungee.internal.jedis
+
+
+
+ redis.clients.util
+ com.imaginarycode.minecraft.redisbungee.internal.jedisutil
+
+
+
+ org.apache.commons.pool
+ com.imaginarycode.minecraft.redisbungee.internal.commonspool
+
+
+
+ com.squareup.okhttp
+ com.imaginarycode.minecraft.redisbungee.internal.okhttp
+
+
+
+ okio
+ com.imaginarycode.minecraft.redisbungee.internal.okio
+
+
+
+ com.google
+ com.imaginarycode.minecraft.redisbungee.internal.google
+
+
+
+ org.json
+ com.imaginarycode.minecraft.redisbungee.internal.json
+
+
+
+ org.checkerframework
+ com.imaginarycode.minecraft.redisbungee.internal.checkframework
+
+
+
+
+
+
+
+
+
+
+
+
+ com.imaginarycode.minecraft
+ RedisBungee-API
+ ${parent.version}
+
+
+ com.velocitypowered
+ velocity-api
+ 3.1.0
+ jar
+ provided
+
+
+
+
\ No newline at end of file
diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RBUtils.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RBUtils.java
new file mode 100644
index 0000000..4c9c9d1
--- /dev/null
+++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RBUtils.java
@@ -0,0 +1,38 @@
+package com.imaginarycode.minecraft.redisbungee;
+
+import com.google.gson.Gson;
+import com.imaginarycode.minecraft.redisbungee.internal.DataManager;
+import com.velocitypowered.api.proxy.InboundConnection;
+import com.velocitypowered.api.proxy.Player;
+import com.velocitypowered.api.proxy.ServerConnection;
+import redis.clients.jedis.Pipeline;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+public class RBUtils {
+
+ private static final Gson gson = new Gson();
+
+ protected static void createPlayer(Player player, Pipeline pipeline, boolean fireEvent) {
+ Optional server = player.getCurrentServer();
+ server.ifPresent(serverConnection -> pipeline.hset("player:" + player.getUniqueId().toString(), "server", serverConnection.getServerInfo().getName()));
+
+ Map playerData = new HashMap<>(4);
+ playerData.put("online", "0");
+ playerData.put("ip", player.getRemoteAddress().getHostName());
+ playerData.put("proxy", RedisBungeeAPI.getRedisBungeeApi().getServerId());
+
+ pipeline.sadd("proxy:" + RedisBungeeAPI.getRedisBungeeApi().getServerId() + ":usersOnline", player.getUniqueId().toString());
+ pipeline.hmset("player:" + player.getUniqueId().toString(), playerData);
+
+ if (fireEvent) {
+ pipeline.publish("redisbungee-data", gson.toJson(new DataManager.DataManagerMessage<>(
+ player.getUniqueId(), RedisBungeeAPI.getRedisBungeeApi().getServerId(), DataManager.DataManagerMessage.Action.JOIN,
+ new DataManager.LoginPayload(player.getRemoteAddress().getAddress()))));
+ }
+ }
+
+
+}
diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeCommandSource.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeCommandSource.java
new file mode 100644
index 0000000..f843028
--- /dev/null
+++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeCommandSource.java
@@ -0,0 +1,38 @@
+package com.imaginarycode.minecraft.redisbungee;
+
+
+import com.velocitypowered.api.command.CommandSource;
+import com.velocitypowered.api.permission.Tristate;
+import net.kyori.adventure.permission.PermissionChecker;
+import net.kyori.adventure.util.TriState;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class RedisBungeeCommandSource implements CommandSource {
+ private static final RedisBungeeCommandSource singleton;
+
+ static {
+ singleton = new RedisBungeeCommandSource();
+ }
+
+ public static RedisBungeeCommandSource getSingleton() {
+ return singleton;
+ }
+
+
+ @Override
+ public boolean hasPermission(String permission) {
+ return true;
+ }
+
+ @Override
+ public Tristate getPermissionValue(String s) {
+ return null;
+ }
+
+ @Override
+ public PermissionChecker getPermissionChecker() {
+ return PermissionChecker.always(TriState.TRUE);
+ }
+}
diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java
new file mode 100644
index 0000000..16e2443
--- /dev/null
+++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeListener.java
@@ -0,0 +1,161 @@
+package com.imaginarycode.minecraft.redisbungee;
+
+import com.imaginarycode.minecraft.redisbungee.internal.AbstractRedisBungeeListener;
+import com.imaginarycode.minecraft.redisbungee.internal.DataManager;
+import com.imaginarycode.minecraft.redisbungee.internal.RedisBungeePlugin;
+import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
+import com.imaginarycode.minecraft.redisbungee.internal.RedisUtil;
+import com.imaginarycode.minecraft.redisbungee.internal.util.RedisCallable;
+import com.velocitypowered.api.event.Continuation;
+import com.velocitypowered.api.event.PostOrder;
+import com.velocitypowered.api.event.ResultedEvent;
+import com.velocitypowered.api.event.Subscribe;
+import com.velocitypowered.api.event.connection.DisconnectEvent;
+import com.velocitypowered.api.event.connection.LoginEvent;
+import com.velocitypowered.api.event.connection.PluginMessageEvent;
+import com.velocitypowered.api.event.connection.PostLoginEvent;
+import com.velocitypowered.api.event.player.ServerConnectedEvent;
+import com.velocitypowered.api.event.proxy.ProxyPingEvent;
+import com.velocitypowered.api.proxy.Player;
+import com.velocitypowered.api.proxy.ServerConnection;
+import com.velocitypowered.api.proxy.server.ServerPing;
+import net.kyori.adventure.text.Component;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.Pipeline;
+
+import java.net.InetAddress;
+import java.util.*;
+
+public class RedisBungeeListener extends AbstractRedisBungeeListener {
+
+
+ public RedisBungeeListener(RedisBungeePlugin> plugin, List exemptAddresses) {
+ super(plugin, exemptAddresses);
+ }
+
+ @Override
+ @Subscribe
+ public void onLogin(LoginEvent event, Continuation continuation) {
+ plugin.executeAsync(new RedisCallable(plugin) {
+ @Override
+ protected Void call(Jedis jedis) {
+ try {
+ if (!event.getResult().isAllowed()) {
+ return null;
+ }
+
+ // We make sure they aren't trying to use an existing player's name.
+ // This is problematic for online-mode servers as they always disconnect old clients.
+ if (plugin.isOnlineMode()) {
+ Player player = (Player) plugin.getPlayer(event.getPlayer().getUsername());
+
+ if (player != null) {
+ event.setResult(ResultedEvent.ComponentResult.denied(Component.text(ONLINE_MODE_RECONNECT)));
+ return null;
+ }
+ }
+
+ for (String s : plugin.getServerIds()) {
+ if (jedis.sismember("proxy:" + s + ":usersOnline", event.getPlayer().getUniqueId().toString())) {
+ event.setResult(ResultedEvent.ComponentResult.denied(Component.text(ALREADY_LOGGED_IN)));
+ return null;
+ }
+ }
+ return null;
+ } finally {
+ continuation.resume();
+ }
+ }
+ });
+ }
+
+ @Override
+ @Subscribe
+ public void onPostLogin(PostLoginEvent event) {
+ plugin.executeAsync(new RedisCallable(plugin) {
+ @Override
+ protected Void call(Jedis jedis) {
+ // this code was moved out from login event due being async..
+ // and it can be cancelled but it will show as false in redis-bungee
+ // which will register the player into the redis database.
+ Pipeline pipeline = jedis.pipelined();
+ plugin.getUuidTranslator().persistInfo(event.getPlayer().getUsername(), event.getPlayer().getUniqueId(), pipeline);
+ RBUtils.createPlayer(event.getPlayer(), pipeline, false);
+ pipeline.sync();
+ // the end of moved code.
+
+ jedis.publish("redisbungee-data", gson.toJson(new DataManager.DataManagerMessage<>(
+ event.getPlayer().getUniqueId(), plugin.getApi().getServerId(), DataManager.DataManagerMessage.Action.JOIN,
+ new DataManager.LoginPayload(event.getPlayer().getRemoteAddress().getAddress()))));
+ return null;
+ }
+ });
+ }
+
+ @Override
+ @Subscribe
+ public void onPlayerDisconnect(DisconnectEvent event) {
+ plugin.executeAsync(new RedisCallable(plugin) {
+ @Override
+ protected Void call(Jedis jedis) {
+ Pipeline pipeline = jedis.pipelined();
+ RedisUtil.cleanUpPlayer(event.getPlayer().getUniqueId().toString(), pipeline);
+ pipeline.sync();
+ return null;
+ }
+ });
+
+ }
+
+ @Override
+ @Subscribe
+ public void onServerChange(ServerConnectedEvent event) {
+ Optional optionalServerConnection = event.getPlayer().getCurrentServer();
+ final String currentServer = optionalServerConnection.map(serverConnection -> serverConnection.getServerInfo().getName()).orElse(null);
+ plugin.executeAsync(new RedisCallable(plugin) {
+ @Override
+ protected Void call(Jedis jedis) {
+ jedis.hset("player:" + event.getPlayer().getUniqueId().toString(), "server", event.getServer().getServerInfo().getName());
+ jedis.publish("redisbungee-data", gson.toJson(new DataManager.DataManagerMessage<>(
+ event.getPlayer().getUniqueId(), plugin.getApi().getServerId(), DataManager.DataManagerMessage.Action.SERVER_CHANGE,
+ new DataManager.ServerChangePayload(event.getServer().getServerInfo().getName(), currentServer))));
+ return null;
+ }
+ });
+ }
+
+ @Override
+ @Subscribe(order = PostOrder.EARLY)
+ public void onPing(ProxyPingEvent event) {
+ if (exemptAddresses.contains(event.getConnection().getRemoteAddress().getAddress())) {
+ return;
+ }
+ ServerPing oldPing = event.getPing();
+ int max = oldPing.getPlayers().map(ServerPing.Players::getMax).orElse(0);
+ List list = oldPing.getPlayers().map(ServerPing.Players::getSample).orElse(Collections.emptyList());
+ event.setPing(new ServerPing(oldPing.getVersion(), new ServerPing.Players(plugin.getCount(), max, list), oldPing.getDescriptionComponent(), oldPing.getFavicon().orElse(null)));
+ }
+
+ @Override
+ public void onPluginMessage(PluginMessageEvent event) {
+ /*
+ * Ham1255 note: for some reason plugin messages were not working in velocity?
+ * not sure how to fix, but for now i have removed the code until a fix is made.
+ *
+ */
+ }
+
+
+ @Override
+ @Subscribe
+ public void onPubSubMessage(PubSubMessageEvent event) {
+ if (event.getChannel().equals("redisbungee-allservers") || event.getChannel().equals("redisbungee-" + plugin.getApi().getServerId())) {
+ String message = event.getMessage();
+ if (message.startsWith("/"))
+ message = message.substring(1);
+ plugin.logInfo("Invoking command via PubSub: /" + message);
+ ((RedisBungeeVelocityPlugin)plugin).getProxy().getCommandManager().executeAsync(RedisBungeeCommandSource.getSingleton(), message);//.dispatchCommand(RedisBungeeCommandSource.getSingleton(), message);
+
+ }
+ }
+}
diff --git a/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java
new file mode 100644
index 0000000..db90793
--- /dev/null
+++ b/RedisBungee-Velocity/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeVelocityPlugin.java
@@ -0,0 +1,667 @@
+package com.imaginarycode.minecraft.redisbungee;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import com.google.common.io.ByteStreams;
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+import com.imaginarycode.minecraft.redisbungee.commands.RedisBungeeCommands;
+import com.imaginarycode.minecraft.redisbungee.events.PlayerChangedServerNetworkEvent;
+import com.imaginarycode.minecraft.redisbungee.events.PlayerJoinedNetworkEvent;
+import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
+import com.imaginarycode.minecraft.redisbungee.internal.*;
+import com.imaginarycode.minecraft.redisbungee.internal.summoners.JedisSummoner;
+import com.imaginarycode.minecraft.redisbungee.internal.summoners.SinglePoolJedisSummoner;
+import com.imaginarycode.minecraft.redisbungee.internal.util.IOUtil;
+import com.imaginarycode.minecraft.redisbungee.internal.util.LuaManager;
+import com.imaginarycode.minecraft.redisbungee.internal.util.uuid.NameFetcher;
+import com.imaginarycode.minecraft.redisbungee.internal.util.uuid.UUIDFetcher;
+import com.imaginarycode.minecraft.redisbungee.internal.util.uuid.UUIDTranslator;
+import com.squareup.okhttp.Dispatcher;
+import com.squareup.okhttp.OkHttpClient;
+import com.velocitypowered.api.event.Subscribe;
+import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
+import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
+import com.velocitypowered.api.plugin.annotation.DataDirectory;
+import com.velocitypowered.api.proxy.Player;
+import com.velocitypowered.api.proxy.ProxyServer;
+import com.velocitypowered.api.scheduler.ScheduledTask;
+import org.slf4j.Logger;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPoolConfig;
+import redis.clients.jedis.Pipeline;
+import redis.clients.jedis.exceptions.JedisConnectionException;
+
+import java.io.*;
+import java.net.InetAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public class RedisBungeeVelocityPlugin implements RedisBungeePlugin {
+
+ private static final Gson gson = new Gson();
+ private final ProxyServer server;
+ private final Logger logger;
+ private final File dataFolder;
+ private RedisBungeeAPI api;
+ private PubSubListener psl = null;
+ private JedisSummoner jedisSummoner;
+ private UUIDTranslator uuidTranslator;
+ private RedisBungeeConfiguration configuration;
+ private VelocityDataManager dataManager;
+ private OkHttpClient httpClient;
+ private volatile List serverIds;
+ private final AtomicInteger nagAboutServers = new AtomicInteger();
+ private final AtomicInteger globalPlayerCount = new AtomicInteger();
+ private ScheduledTask integrityCheck;
+ private ScheduledTask heartbeatTask;
+ private LuaManager.Script serverToPlayersScript;
+ private LuaManager.Script getPlayerCountScript;
+
+ private static final Object SERVER_TO_PLAYERS_KEY = new Object();
+ private final Cache