diff --git a/README.md b/README.md index ba7b68af..aa96d86d 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ These are some quick links for common resources of SubServers 2. > [https://dev.me1312.net/jenkins/job/SubServers Platform/javadoc/SubServers.Bungee.Common](https://dev.me1312.net/jenkins/job/SubServers%20Platform/javadoc/SubServers.Bungee.Common)
> [https://dev.me1312.net/jenkins/job/SubServers Platform/javadoc/SubServers.Host](https://dev.me1312.net/jenkins/job/SubServers%20Platform/javadoc/SubServers.Host)
> [https://dev.me1312.net/jenkins/job/SubServers Platform/javadoc/SubServers.Sync](https://dev.me1312.net/jenkins/job/SubServers%20Platform/javadoc/SubServers.Sync)
+> [https://dev.me1312.net/jenkins/job/SubServers Platform/javadoc/SubServers.Sync.Velocity](https://dev.me1312.net/jenkins/job/SubServers%20Platform/javadoc/SubServers.Sync.Velocity)
> [https://dev.me1312.net/jenkins/job/SubServers Platform/javadoc/SubServers.Client.Bukkit](https://dev.me1312.net/jenkins/job/SubServers%20Platform/javadoc/SubServers.Client.Bukkit)
> [https://dev.me1312.net/jenkins/job/SubServers Platform/javadoc/SubServers.Client.Common](https://dev.me1312.net/jenkins/job/SubServers%20Platform/javadoc/SubServers.Client.Common)
> [https://dev.me1312.net/jenkins/job/SubServers Platform/javadoc/SubServers.Client.Sponge](https://dev.me1312.net/jenkins/job/SubServers%20Platform/javadoc/SubServers.Client.Sponge) @@ -30,6 +31,7 @@ These are some quick links for common resources of SubServers 2. > [https://bstats.org/plugin/bungeecord/SubServers_Bungee](https://bstats.org/plugin/bungeecord/SubServers%202)
> [https://bstats.org/plugin/other/SubServers_Host](https://bstats.org/plugin/other/SubServers%20Host)
> [https://bstats.org/plugin/bungeecord/SubServers_Sync](https://bstats.org/plugin/bungeecord/SubServers%20Sync)
+> [https://bstats.org/plugin/velocity/SubServers_Sync](https://bstats.org/plugin/velocity/SubServers%20Sync)
> [https://bstats.org/plugin/bungeecord/SubServers_Console](https://bstats.org/plugin/bungeecord/SubServers%20Console)
> [https://bstats.org/plugin/bukkit/SubServers_Client](https://bstats.org/plugin/bukkit/SubServers%20Client)
> [https://bstats.org/plugin/sponge/SubServers_Client](https://bstats.org/plugin/sponge/SubServers%20Client) diff --git a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/External/ExternalSubServer.java b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/External/ExternalSubServer.java index 09cfbf7c..47421692 100644 --- a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/External/ExternalSubServer.java +++ b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/External/ExternalSubServer.java @@ -7,13 +7,17 @@ import net.ME1312.Galaxi.Library.Container.Value; import net.ME1312.Galaxi.Library.Map.ObjectMap; import net.ME1312.Galaxi.Library.Map.ObjectMapValue; import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubData.Server.SubDataClient; import net.ME1312.SubServers.Bungee.Event.*; import net.ME1312.SubServers.Bungee.Host.*; import net.ME1312.SubServers.Bungee.Library.Compatibility.Logger; import net.ME1312.SubServers.Bungee.Library.Exception.InvalidServerException; -import net.ME1312.SubServers.Bungee.Network.Packet.PacketExEditServer; +import net.ME1312.SubServers.Bungee.Network.Packet.PacketExControlServer; +import net.ME1312.SubServers.Bungee.Network.Packet.PacketExControlServer.Action; +import net.ME1312.SubServers.Bungee.Network.Packet.PacketOutExEditServer; +import net.ME1312.SubServers.Bungee.Network.Packet.PacketOutExEditServer.Edit; +import net.ME1312.SubServers.Bungee.SubAPI; -import net.md_5.bungee.BungeeServerInfo; import net.md_5.bungee.api.ChatColor; import java.io.IOException; @@ -111,7 +115,7 @@ public class ExternalSubServer extends SubServerImpl { if (!event.isCancelled()) { Logger.get("SubServers").info("Now starting " + getName()); started(null); - host.queue(new PacketExEditServer(this, PacketExEditServer.UpdateType.START, logger.getExternalAddress().toString())); + host.queue(new PacketExControlServer(this, Action.START, logger.getExternalAddress().toString())); return true; } else { lock = false; @@ -126,7 +130,7 @@ public class ExternalSubServer extends SubServerImpl { lock = false; logger.start(); if (address != null) { - if (address != logger.getExternalAddress()) host.queue(new PacketExEditServer(this, PacketExEditServer.UpdateType.SET_LOGGING_ADDRESS, logger.getExternalAddress().toString())); + if (address != logger.getExternalAddress()) host.queue(new PacketExControlServer(this, Action.SET_LOGGING_ADDRESS, logger.getExternalAddress().toString())); host.plugin.getPluginManager().callEvent(new SubStartEvent(null, this)); } } @@ -144,7 +148,7 @@ public class ExternalSubServer extends SubServerImpl { host.plugin.getPluginManager().callEvent(event); if (!event.isCancelled()) { history.add(new LoggedCommand(player, stopcmd)); - host.queue(new PacketExEditServer(this, PacketExEditServer.UpdateType.STOP)); + host.queue(new PacketExControlServer(this, Action.STOP)); return true; } else return false; } else return false; @@ -197,7 +201,7 @@ public class ExternalSubServer extends SubServerImpl { SubStopEvent event = new SubStopEvent(player, this, true); host.plugin.getPluginManager().callEvent(event); if (!event.isCancelled()) { - host.queue(new PacketExEditServer(this, PacketExEditServer.UpdateType.TERMINATE)); + host.queue(new PacketExControlServer(this, Action.TERMINATE)); return true; } else return false; } else return false; @@ -212,9 +216,9 @@ public class ExternalSubServer extends SubServerImpl { if (!event.isCancelled() && (player == null || !DISALLOWED_COMMANDS.matcher(command).find())) { history.add(new LoggedCommand(player, event.getCommand())); if (event.getCommand().equalsIgnoreCase(stopcmd)) { - host.queue(new PacketExEditServer(this, PacketExEditServer.UpdateType.STOP)); + host.queue(new PacketExControlServer(this, Action.STOP)); } else { - host.queue(new PacketExEditServer(this, PacketExEditServer.UpdateType.COMMAND, event.getCommand())); + host.queue(new PacketExControlServer(this, Action.COMMAND, event.getCommand())); } return true; } else return false; @@ -258,14 +262,7 @@ public class ExternalSubServer extends SubServerImpl { break; case "display": if (value.isString()) { - Field f = ServerImpl.class.getDeclaredField("nick"); - f.setAccessible(true); - if (value.isNull() || value.asString().length() == 0 || getName().equals(value.asString())) { - f.set(this, null); - } else { - f.set(this, value.asString()); - } - f.setAccessible(false); + setDisplayName(value.asRawString()); logger.name = getDisplayName(); if (perma && this.host.plugin.servers.get().getMap("Servers").getKeys().contains(getName())) { if (getName().equals(getDisplayName())) { @@ -280,7 +277,7 @@ public class ExternalSubServer extends SubServerImpl { break; case "enabled": if (value.isBoolean()) { - if (enabled != value.asBoolean()) host.queue(new PacketExEditServer(this, PacketExEditServer.UpdateType.SET_ENABLED, (Boolean) value.asBoolean())); + if (enabled != value.asBoolean()) host.queue(new PacketExControlServer(this, Action.SET_ENABLED, (Boolean) value.asBoolean())); enabled = value.asBoolean(); if (perma && this.host.plugin.servers.get().getMap("Servers").getKeys().contains(getName())) { this.host.plugin.servers.get().getMap("Servers").getMap(getName()).set("Enabled", isEnabled()); @@ -349,7 +346,7 @@ public class ExternalSubServer extends SubServerImpl { break; case "log": if (value.isBoolean()) { - if (log.value() != value.asBoolean()) host.queue(new PacketExEditServer(this, PacketExEditServer.UpdateType.SET_LOGGING, (Boolean) value.asBoolean())); + if (log.value() != value.asBoolean()) host.queue(new PacketExControlServer(this, Action.SET_LOGGING, (Boolean) value.asBoolean())); log.value(value.asBoolean()); if (perma && this.host.plugin.servers.get().getMap("Servers").getKeys().contains(getName())) { this.host.plugin.servers.get().getMap("Servers").getMap(getName()).set("Log", isLogging()); @@ -396,7 +393,7 @@ public class ExternalSubServer extends SubServerImpl { case "stop-cmd": case "stop-command": if (value.isString()) { - if (!stopcmd.equals(value)) host.queue(new PacketExEditServer(this, PacketExEditServer.UpdateType.SET_STOP_COMMAND, value.asRawString())); + if (!stopcmd.equals(value)) host.queue(new PacketExControlServer(this, Action.SET_STOP_COMMAND, value.asRawString())); stopcmd = value.asRawString(); if (perma && this.host.plugin.servers.get().getMap("Servers").getKeys().contains(getName())) { this.host.plugin.servers.get().getMap("Servers").getMap(getName()).set("Stop-Command", getStopCommand()); @@ -465,6 +462,9 @@ public class ExternalSubServer extends SubServerImpl { case "whitelist": if (value.isList()) { Util.reflect(ServerImpl.class.getDeclaredField("whitelist"), this, value.asUUIDList()); + if (isRegistered()) for (Proxy proxy : SubAPI.getInstance().getProxies().values()) if (proxy.getSubData()[0] != null) { + ((SubDataClient) proxy.getSubData()[0]).sendPacket(new PacketOutExEditServer(this, Edit.WHITELIST_SET, value.asUUIDList())); + } c++; } break; @@ -534,7 +534,7 @@ public class ExternalSubServer extends SubServerImpl { @Override public void setEnabled(boolean value) { if (Util.isNull(value)) throw new NullPointerException(); - if (enabled != value) host.queue(new PacketExEditServer(this, PacketExEditServer.UpdateType.SET_ENABLED, (Boolean) value)); + if (enabled != value) host.queue(new PacketExControlServer(this, Action.SET_ENABLED, (Boolean) value)); enabled = value; } @@ -546,7 +546,7 @@ public class ExternalSubServer extends SubServerImpl { @Override public void setLogging(boolean value) { if (Util.isNull(value)) throw new NullPointerException(); - if (log.value() != value) host.queue(new PacketExEditServer(this, PacketExEditServer.UpdateType.SET_LOGGING, (Boolean) value)); + if (log.value() != value) host.queue(new PacketExControlServer(this, Action.SET_LOGGING, (Boolean) value)); log.value(value); } @@ -578,7 +578,7 @@ public class ExternalSubServer extends SubServerImpl { @Override public void setStopCommand(String value) { if (Util.isNull(value)) throw new NullPointerException(); - if (!stopcmd.equals(value)) host.queue(new PacketExEditServer(this, PacketExEditServer.UpdateType.SET_STOP_COMMAND, value)); + if (!stopcmd.equals(value)) host.queue(new PacketExControlServer(this, Action.SET_STOP_COMMAND, value)); stopcmd = value; } diff --git a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Internal/InternalSubCreator.java b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Internal/InternalSubCreator.java index a874452c..aa317431 100644 --- a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Internal/InternalSubCreator.java +++ b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Internal/InternalSubCreator.java @@ -602,7 +602,6 @@ public class InternalSubCreator extends SubCreator { if (installed) { YAMLSection config = new YAMLSection(); FileWriter writer = new FileWriter(new UniversalFile(dir, "subdata.json"), false); - config.set("Name", name); config.setAll(getSubData()); writer.write(config.toJSON().toString()); writer.close(); diff --git a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Internal/InternalSubServer.java b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Internal/InternalSubServer.java index 82693bcb..9a57f2ad 100644 --- a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Internal/InternalSubServer.java +++ b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Internal/InternalSubServer.java @@ -8,10 +8,14 @@ import net.ME1312.Galaxi.Library.Map.ObjectMapValue; import net.ME1312.Galaxi.Library.UniversalFile; import net.ME1312.Galaxi.Library.Util; import net.ME1312.Galaxi.Library.Version.Version; +import net.ME1312.SubData.Server.SubDataClient; import net.ME1312.SubServers.Bungee.Event.*; import net.ME1312.SubServers.Bungee.Host.*; import net.ME1312.SubServers.Bungee.Library.Compatibility.Logger; import net.ME1312.SubServers.Bungee.Library.Exception.InvalidServerException; +import net.ME1312.SubServers.Bungee.Network.Packet.PacketOutExEditServer; +import net.ME1312.SubServers.Bungee.Network.Packet.PacketOutExEditServer.Edit; +import net.ME1312.SubServers.Bungee.SubAPI; import net.ME1312.SubServers.Bungee.SubProxy; import net.md_5.bungee.BungeeServerInfo; @@ -330,14 +334,7 @@ public class InternalSubServer extends SubServerImpl { break; case "display": if (value.isString()) { - Field f = ServerImpl.class.getDeclaredField("nick"); - f.setAccessible(true); - if (value.isNull() || value.asString().length() == 0 || getName().equals(value.asString())) { - f.set(this, null); - } else { - f.set(this, value.asString()); - } - f.setAccessible(false); + setDisplayName(value.asRawString()); logger.name = getDisplayName(); if (perma && this.host.plugin.servers.get().getMap("Servers").getKeys().contains(getName())) { if (getName().equals(getDisplayName())) { @@ -533,6 +530,9 @@ public class InternalSubServer extends SubServerImpl { case "whitelist": if (value.isList()) { Util.reflect(ServerImpl.class.getDeclaredField("whitelist"), this, value.asUUIDList()); + if (isRegistered()) for (Proxy proxy : SubAPI.getInstance().getProxies().values()) if (proxy.getSubData()[0] != null) { + ((SubDataClient) proxy.getSubData()[0]).sendPacket(new PacketOutExEditServer(this, Edit.WHITELIST_SET, value.asUUIDList())); + } c++; } break; diff --git a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Proxy.java b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Proxy.java index 0abf82ca..b729b234 100644 --- a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Proxy.java +++ b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Proxy.java @@ -23,20 +23,14 @@ public class Proxy implements ClientHandler, ExtraDataHandler { private HashMap subdata = new HashMap(); private ObjectMap extra = new ObjectMap(); private final String signature; - private boolean persistent; + private boolean persistent = false; private String nick = null; private final String name; public Proxy(String name) throws IllegalArgumentException { - this(name, name != null); - } - - @SuppressWarnings("deprecation") - public Proxy(String name, boolean persistent) throws IllegalArgumentException { if (name == null) name = Util.getNew(SubAPI.getInstance().getInternals().proxies.keySet(), () -> UUID.randomUUID().toString()); if (name.contains(" ")) throw new IllegalArgumentException("Proxy names cannot have spaces: " + name); this.name = name; - this.persistent = persistent; this.signature = SubAPI.getInstance().signAnonymousObject(); subdata.put(0, null); @@ -146,6 +140,13 @@ public class Proxy implements ClientHandler, ExtraDataHandler { return players; } + /** + * Makes it so the proxy object will still exist within the server manager even if it is disconnected + */ + public final void persist() { + persistent = true; + } + /** * Get the Signature of this Object * diff --git a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Server.java b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Server.java index 5b411bd2..abe6cb95 100644 --- a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Server.java +++ b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/Server.java @@ -136,6 +136,11 @@ public interface Server extends ServerInfo, ClientHandler, ExtraDataHandler { */ void unwhitelist(UUID player); + /** + * Makes it so the server object will still exist within the server manager even if it is disconnected + */ + void persist(); + /** * Get the Signature of this Object * diff --git a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/ServerImpl.java b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/ServerImpl.java index ec00a071..a4da49a3 100644 --- a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/ServerImpl.java +++ b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/ServerImpl.java @@ -1,21 +1,19 @@ package net.ME1312.SubServers.Bungee.Host; -import net.ME1312.Galaxi.Library.Container.ContainedPair; import net.ME1312.Galaxi.Library.Map.ObjectMap; import net.ME1312.Galaxi.Library.Map.ObjectMapValue; import net.ME1312.Galaxi.Library.Util; import net.ME1312.SubData.Server.DataClient; import net.ME1312.SubData.Server.SubDataClient; -import net.ME1312.SubServers.Bungee.Event.SubEditServerEvent; -import net.ME1312.SubServers.Bungee.Event.SubNetworkConnectEvent; -import net.ME1312.SubServers.Bungee.Event.SubNetworkDisconnectEvent; +import net.ME1312.SubServers.Bungee.Event.SubRemoveProxyEvent; import net.ME1312.SubServers.Bungee.Library.Exception.InvalidServerException; -import net.ME1312.SubServers.Bungee.Network.Packet.PacketOutExRunEvent; -import net.ME1312.SubServers.Bungee.Network.Packet.PacketOutExUpdateWhitelist; +import net.ME1312.SubServers.Bungee.Network.Packet.PacketOutExEditServer; +import net.ME1312.SubServers.Bungee.Network.Packet.PacketOutExEditServer.Edit; import net.ME1312.SubServers.Bungee.SubAPI; import net.md_5.bungee.BungeeServerInfo; import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.connection.ProxiedPlayer; import java.net.InetSocketAddress; @@ -33,6 +31,7 @@ public class ServerImpl extends BungeeServerInfo implements Server { private List whitelist = new ArrayList(); private boolean hidden; private final String signature = SubAPI.getInstance().signAnonymousObject(); + private volatile boolean persistent = true; /** * Construct a new Server data type @@ -80,6 +79,16 @@ public class ServerImpl extends BungeeServerInfo implements Server { subdata.put(0, null); } + /** + * Get if this server has been registered + * + * @return Registered status + */ + @SuppressWarnings("deprecation") + protected boolean isRegistered() { + return SubAPI.getInstance().getInternals().exServers.containsKey(getName().toLowerCase()); + } + @Override public DataClient[] getSubData() { Integer[] keys = subdata.keySet().toArray(new Integer[0]); @@ -103,12 +112,20 @@ public class ServerImpl extends BungeeServerInfo implements Server { subdata.remove(channel); } - if (update) for (Proxy proxy : SubAPI.getInstance().getProxies().values()) if (proxy.getSubData()[0] != null) { - ObjectMap args = new ObjectMap(); - args.set("server", getName()); - args.set("channel", channel); - if (client != null) args.set("id", client.getID()); - ((SubDataClient) proxy.getSubData()[0]).sendPacket(new PacketOutExRunEvent((client != null)?SubNetworkConnectEvent.class:SubNetworkDisconnectEvent.class, args)); + if (update) { + for (Proxy proxy : SubAPI.getInstance().getProxies().values()) if (proxy.getSubData()[0] != null) { + if (client != null) { + ((SubDataClient) proxy.getSubData()[0]).sendPacket(new PacketOutExEditServer(this, Edit.CONNECTED, channel, client.getID())); + } else { + ((SubDataClient) proxy.getSubData()[0]).sendPacket(new PacketOutExEditServer(this, Edit.DISCONNECTED, channel)); + } + } + if (!persistent) { + DataClient[] subdata = getSubData(); + if (subdata[0] == null && subdata.length <= 1) { + SubAPI.getInstance().removeServer(getName()); + } + } } } @@ -130,6 +147,9 @@ public class ServerImpl extends BungeeServerInfo implements Server { } else { this.nick = value; } + for (Proxy proxy : SubAPI.getInstance().getProxies().values()) if (proxy.getSubData()[0] != null) { + ((SubDataClient) proxy.getSubData()[0]).sendPacket(new PacketOutExEditServer(this, Edit.DISPLAY_NAME, getDisplayName())); + } } @Override @@ -167,14 +187,19 @@ public class ServerImpl extends BungeeServerInfo implements Server { @Override public void setHidden(boolean value) { - if (Util.isNull(value)) throw new NullPointerException(); this.hidden = value; + if (isRegistered()) for (Proxy proxy : SubAPI.getInstance().getProxies().values()) if (proxy.getSubData()[0] != null) { + ((SubDataClient) proxy.getSubData()[0]).sendPacket(new PacketOutExEditServer(this, Edit.HIDDEN, isHidden())); + } } public void setMotd(String value) { if (Util.isNull(value)) throw new NullPointerException(); try { Util.reflect(BungeeServerInfo.class.getDeclaredField("motd"), this, value); + for (Proxy proxy : SubAPI.getInstance().getProxies().values()) if (proxy.getSubData()[0] != null) { + ((SubDataClient) proxy.getSubData()[0]).sendPacket(new PacketOutExEditServer(this, Edit.MOTD, getMotd())); + } } catch (Exception e) { e.printStackTrace(); } @@ -184,6 +209,10 @@ public class ServerImpl extends BungeeServerInfo implements Server { if (Util.isNull(value)) throw new NullPointerException(); try { Util.reflect(BungeeServerInfo.class.getDeclaredField("restricted"), this, value); + + if (isRegistered()) for (Proxy proxy : SubAPI.getInstance().getProxies().values()) if (proxy.getSubData()[0] != null) { + ((SubDataClient) proxy.getSubData()[0]).sendPacket(new PacketOutExEditServer(this, Edit.RESTRICTED, isRestricted())); + } } catch (Exception e) { e.printStackTrace(); } @@ -197,7 +226,7 @@ public class ServerImpl extends BungeeServerInfo implements Server { */ @Override public boolean canAccess(CommandSender player) { - return (player instanceof ProxiedPlayer && whitelist.contains(((ProxiedPlayer) player).getUniqueId())) || super.canAccess(player); + return super.canAccess(player) || (player instanceof ProxiedPlayer && whitelist.contains(((ProxiedPlayer) player).getUniqueId())); } @Override @@ -214,14 +243,23 @@ public class ServerImpl extends BungeeServerInfo implements Server { public void whitelist(UUID player) { if (Util.isNull(player)) throw new NullPointerException(); if (!whitelist.contains(player)) whitelist.add(player); - for (Proxy proxy : SubAPI.getInstance().getProxies().values()) if (proxy.getSubData()[0] != null) ((SubDataClient) proxy.getSubData()[0]).sendPacket(new PacketOutExUpdateWhitelist(getName(), true, player)); + if (isRegistered()) for (Proxy proxy : SubAPI.getInstance().getProxies().values()) if (proxy.getSubData()[0] != null) { + ((SubDataClient) proxy.getSubData()[0]).sendPacket(new PacketOutExEditServer(this, Edit.WHITELIST_ADD, player)); + } } @Override public void unwhitelist(UUID player) { if (Util.isNull(player)) throw new NullPointerException(); whitelist.remove(player); - for (Proxy proxy : SubAPI.getInstance().getProxies().values()) if (proxy.getSubData()[0] != null) ((SubDataClient) proxy.getSubData()[0]).sendPacket(new PacketOutExUpdateWhitelist(getName(), false, player)); + if (isRegistered()) for (Proxy proxy : SubAPI.getInstance().getProxies().values()) if (proxy.getSubData()[0] != null) { + ((SubDataClient) proxy.getSubData()[0]).sendPacket(new PacketOutExEditServer(this, Edit.WHITELIST_REMOVE, player)); + } + } + + @Override + public final void persist() { + persistent = true; } @Override diff --git a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/SubServerImpl.java b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/SubServerImpl.java index c9decd5b..1cb74986 100644 --- a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/SubServerImpl.java +++ b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Host/SubServerImpl.java @@ -98,6 +98,11 @@ public abstract class SubServerImpl extends ServerImpl implements SubServer { return -1; } + @Override + protected final boolean isRegistered() { + return registered; + } + @Override public boolean isAvailable() { return registered && !updating && getHost().isAvailable(); diff --git a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Library/ConfigUpdater.java b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Library/ConfigUpdater.java index bd8b5e01..3644d3e9 100644 --- a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Library/ConfigUpdater.java +++ b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Library/ConfigUpdater.java @@ -138,6 +138,11 @@ public class ConfigUpdater { existing = updated.clone(); i++; + } else if (was.compareTo(new Version("21w27b")) <= 0) { + + //existing = updated.clone(); + i++; + }// if (was.compareTo(new Version("99w99a")) <= 0) { // // do something // existing = updated.clone(); @@ -151,6 +156,7 @@ public class ConfigUpdater { YAMLSection settings = new YAMLSection(); settings.set("Version", ((now.compareTo(was) <= 0)?was:now).toString()); if (updated.getMap("Settings", new YAMLSection()).contains("RPEC-Check-Interval")) settings.set("RPEC-Check-Interval", updated.getMap("Settings").getRawString("RPEC-Check-Interval")); + settings.set("Strict-Server-Linking", updated.getMap("Settings", new YAMLSection()).getBoolean("Strict-Server-Linking", true)); settings.set("Disabled-Overrides", updated.getMap("Settings", new YAMLSection()).getRawStringList("Disabled-Overrides", Collections.emptyList())); YAMLSection smart_fallback = new YAMLSection(); @@ -318,7 +324,7 @@ public class ConfigUpdater { existing = updated.clone(); i++; - } if (was.compareTo(new Version("21w27a")) <= 0) { + } if (was.compareTo(new Version("21w27b")) <= 0) { //existing = updated.clone(); i++; @@ -339,6 +345,7 @@ public class ConfigUpdater { LinkedHashMap def = new LinkedHashMap(); def.put("Bungee.Feature.Smart-Fallback", "&6Returning from $str$: &r$msg$"); def.put("Bungee.Feature.Smart-Fallback.Result", "&6You are now on $str$."); + def.put("Bungee.Restricted", "&cYou don't have permission to access this server."); def.put("Bungee.Ping.Offline", "&6&l[&e&lWarning&6&l] &7Backend server(s) are not running"); def.put("Bungee.Server.Current", "&6You are currently connected to $str$"); def.put("Bungee.Server.Available", "&6You may connect to the following servers at this time:"); diff --git a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketExEditServer.java b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketExControlServer.java similarity index 78% rename from SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketExEditServer.java rename to SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketExControlServer.java index accd0444..6fe2ad7c 100644 --- a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketExEditServer.java +++ b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketExControlServer.java @@ -12,15 +12,15 @@ import net.ME1312.SubServers.Bungee.SubProxy; import java.util.Arrays; /** - * Edit External Server Packet + * Control External Server Packet */ -public class PacketExEditServer implements PacketObjectIn, PacketObjectOut { +public class PacketExControlServer implements PacketObjectIn, PacketObjectOut { private SubProxy plugin; private SubServer server; - private UpdateType type; + private Action type; private Object[] args; - public enum UpdateType { + public enum Action { // Actions START(1, String.class), COMMAND(2, String.class), @@ -35,7 +35,7 @@ public class PacketExEditServer implements PacketObjectIn, PacketObject private short value; private Class[] args; - UpdateType(int value, Class... args) { + Action(int value, Class... args) { this.value = (short) value; this.args = args; } @@ -50,30 +50,31 @@ public class PacketExEditServer implements PacketObjectIn, PacketObject } /** - * New PacketExEditServer (In) + * New PacketExControlServer (In) * @param plugin SubPlugin */ - public PacketExEditServer(SubProxy plugin) { + public PacketExControlServer(SubProxy plugin) { this.plugin = plugin; } /** - * New PacketExEditServer (Out) + * New PacketExControlServer (Out) * * @param server SubServer * @param type Update Type * @param arguments Arguments */ - public PacketExEditServer(SubServer server, UpdateType type, Object... arguments) { - if (arguments.length != type.getArguments().length) throw new IllegalArgumentException(((arguments.length > type.getArguments().length)?"Too many":"Not enough") + " arguments for type: " + type.toString()); - int i = 0; - while (i < arguments.length) { - if (!type.getArguments()[i].isInstance(arguments[i])) throw new IllegalArgumentException("Argument " + (i+1) + " is not " + type.getArguments()[i].getCanonicalName()); - i++; - } + public PacketExControlServer(SubServer server, Action type, Object... arguments) { + if (arguments.length < type.getArguments().length) throw new IllegalArgumentException("Not enough arguments for type: " + type); + this.server = server; this.type = type; - this.args = arguments; + this.args = new Object[type.getArguments().length]; + + for (int i = 0; i < type.getArguments().length; ++i) { + if (!type.getArguments()[i].isInstance(arguments[i])) throw new IllegalArgumentException("Argument " + (i+1) + " is not " + type.getArguments()[i].getCanonicalName()); + args[i] = arguments[i]; + } } @Override diff --git a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketLinkServer.java b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketLinkServer.java index 8d149de1..accc6b93 100644 --- a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketLinkServer.java +++ b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketLinkServer.java @@ -13,6 +13,7 @@ import net.ME1312.SubServers.Bungee.Host.ServerImpl; import net.ME1312.SubServers.Bungee.Host.SubServer; import net.ME1312.SubServers.Bungee.Host.SubServerImpl; import net.ME1312.SubServers.Bungee.Library.Compatibility.Logger; +import net.ME1312.SubServers.Bungee.SubAPI; import net.ME1312.SubServers.Bungee.SubProxy; import net.md_5.bungee.api.ProxyServer; @@ -25,6 +26,7 @@ import java.util.concurrent.TimeUnit; * Link Server Packet */ public class PacketLinkServer implements InitialPacket, PacketObjectIn, PacketObjectOut { + public static boolean strict = true; private SubProxy plugin; private int response; private String message; @@ -72,19 +74,28 @@ public class PacketLinkServer implements InitialPacket, PacketObjectIn, @Override public void receive(SubDataClient client, ObjectMap data) { String name = (data.contains(0x0000))?data.getRawString(0x0000):null; - Integer port = (data.contains(0x0001))?data.getInt(0x0001):null; Integer channel = data.getInt(0x0002); + InetSocketAddress address; try { + if (!data.contains(0x0001)) { + address = null; + } else if (data.isNumber(0x0001)) { + address = new InetSocketAddress(client.getAddress().getAddress(), data.getInt(0x0001)); + } else { + String[] sa = data.getRawString(0x0001).split(":"); + address = new InetSocketAddress(sa[0], Integer.parseInt(sa[1])); + } + Map servers = plugin.api.getServers(); Server server; if (name != null && servers.keySet().contains(name.toLowerCase())) { - link(client, servers.get(name.toLowerCase()), channel); - } else if (port != null) { - if ((server = search(new InetSocketAddress(client.getAddress().getAddress(), port))) != null) { - link(client, server, channel); + link(client, name, address, servers.get(name.toLowerCase()), channel); + } else if (address != null) { + if ((server = search(address)) != null || !strict) { + link(client, name, address, server, channel); } else { - throw new ServerLinkException("There is no server with address: " + client.getAddress().getAddress().getHostAddress() + ':' + port); + throw new ServerLinkException("There is no server with address: " + address.getAddress().getHostAddress() + ':' + address.getPort()); } } else { throw new ServerLinkException("Not enough arguments"); @@ -101,9 +112,18 @@ public class PacketLinkServer implements InitialPacket, PacketObjectIn, } } - static int req = 1; + static long req = 1; static long last = Calendar.getInstance().getTime().getTime(); - private void link(SubDataClient client, Server server, int channel) throws Throwable { + private void link(SubDataClient client, String name, InetSocketAddress address, Server resolved, int channel) throws Throwable { + final Server server; + if (resolved == null) { + String id = (name == null)? Util.getNew(SubAPI.getInstance().getServers().keySet(), () -> UUID.randomUUID().toString()) : name; + server = SubAPI.getInstance().addServer(id, address.getAddress(), address.getPort(), "Some Dynamic Server", name == null, false); + Util.reflect(ServerImpl.class.getDeclaredField("persistent"), server, false); + } else { + server = resolved; + } + HashMap subdata = Util.getDespiteException(() -> Util.reflect(ServerImpl.class.getDeclaredField("subdata"), server), null); if (!subdata.keySet().contains(channel) || (channel == 0 && subdata.get(0) == null)) { server.setSubData(client, channel); diff --git a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketOutExEditServer.java b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketOutExEditServer.java new file mode 100644 index 00000000..95b57784 --- /dev/null +++ b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketOutExEditServer.java @@ -0,0 +1,95 @@ +package net.ME1312.SubServers.Bungee.Network.Packet; + +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.SubData.Server.Protocol.PacketObjectOut; +import net.ME1312.SubData.Server.SubDataClient; +import net.ME1312.SubServers.Bungee.Host.Server; +import net.ME1312.SubServers.Bungee.SubProxy; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +/** + * External Server Edit Notification Packet + */ +public class PacketOutExEditServer implements PacketObjectOut { + private SubProxy plugin; + private Server server; + private Edit type; + private Object[] args; + + public enum Edit { + // Generic + DISPLAY_NAME(0, String.class), + MOTD(1, String.class), + RESTRICTED(2, Boolean.class), + HIDDEN(3, Boolean.class), + + // SubData + CONNECTED(4, Integer.class, UUID.class), + DISCONNECTED(5, Integer.class), + + // Whitelist + WHITELIST_SET(6, List.class), + WHITELIST_ADD(7, UUID.class), + WHITELIST_REMOVE(8, UUID.class); + + private short value; + private Class[] args; + Edit(int value, Class... args) { + this.value = (short) value; + this.args = args; + } + + public Class[] getArguments() { + return args; + } + + public short getValue() { + return value; + } + } + + /** + * New PacketExEditServer (In) + * @param plugin SubPlugin + */ + public PacketOutExEditServer(SubProxy plugin) { + this.plugin = plugin; + } + + /** + * New PacketExEditServer (Out) + * + * @param server SubServer + * @param type Update Type + * @param arguments Arguments + */ + public PacketOutExEditServer(Server server, Edit type, Object... arguments) { + if (arguments.length < type.getArguments().length) throw new IllegalArgumentException("Not enough arguments for type: " + type); + + this.server = server; + this.type = type; + this.args = new Object[type.getArguments().length]; + + for (int i = 0; i < type.getArguments().length; ++i) { + if (!type.getArguments()[i].isInstance(arguments[i])) throw new IllegalArgumentException("Argument " + (i+1) + " is not " + type.getArguments()[i].getCanonicalName()); + args[i] = arguments[i]; + } + } + + @Override + public ObjectMap send(SubDataClient client) { + ObjectMap data = new ObjectMap(); + data.set(0x0000, server.getName()); + data.set(0x0001, type.getValue()); + data.set(0x0002, Arrays.asList(args)); + return data; + } + + @Override + public int version() { + return 0x0002; + } +} diff --git a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketOutExUpdateWhitelist.java b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketOutExUpdateWhitelist.java deleted file mode 100644 index 03c90154..00000000 --- a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/Packet/PacketOutExUpdateWhitelist.java +++ /dev/null @@ -1,45 +0,0 @@ -package net.ME1312.SubServers.Bungee.Network.Packet; - -import net.ME1312.Galaxi.Library.Map.ObjectMap; -import net.ME1312.Galaxi.Library.Util; -import net.ME1312.SubData.Server.Protocol.PacketObjectOut; -import net.ME1312.SubData.Server.SubDataClient; - -import java.util.UUID; - -/** - * Update External Whitelist Packet - */ -public class PacketOutExUpdateWhitelist implements PacketObjectOut { - private String name; - private boolean mode; - private UUID value; - - /** - * New PacketOutExUpdateWhitelist - * - * @param name Server Name - * @param mode Update Mode (true for add, false for remove) - * @param value Whitelist Value - */ - public PacketOutExUpdateWhitelist(String name, boolean mode, UUID value) { - if (Util.isNull(name, mode, value)) throw new NullPointerException(); - this.name = name; - this.mode = mode; - this.value = value; - } - - @Override - public ObjectMap send(SubDataClient client) { - ObjectMap data = new ObjectMap(); - data.set(0x0000, name); - data.set(0x0001, mode); - data.set(0x0002, value); - return data; - } - - @Override - public int version() { - return 0x0001; - } -} diff --git a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/SubProtocol.java b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/SubProtocol.java index 7756a7d2..3fae0664 100644 --- a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/SubProtocol.java +++ b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/Network/SubProtocol.java @@ -101,7 +101,7 @@ public class SubProtocol extends SubDataProtocol { //registerPacket(0x0053, PacketInExRequestQueue.class); registerPacket(0x0054, PacketExCreateServer.class); registerPacket(0x0055, PacketExAddServer.class); - registerPacket(0x0056, PacketExEditServer.class); + registerPacket(0x0056, PacketExControlServer.class); //registerPacket(0x0057, PacketInExLogMessage.class); registerPacket(0x0058, PacketExRemoveServer.class); registerPacket(0x0059, PacketExDeleteServer.class); @@ -113,7 +113,7 @@ public class SubProtocol extends SubDataProtocol { registerPacket(0x0053, new PacketInExRequestQueue(plugin)); registerPacket(0x0054, new PacketExCreateServer(null)); registerPacket(0x0055, new PacketExAddServer()); - registerPacket(0x0056, new PacketExEditServer(plugin)); + registerPacket(0x0056, new PacketExControlServer(plugin)); registerPacket(0x0057, new PacketInExLogMessage()); registerPacket(0x0058, new PacketExRemoveServer()); registerPacket(0x0059, new PacketExDeleteServer()); @@ -124,7 +124,7 @@ public class SubProtocol extends SubDataProtocol { registerPacket(0x0070, PacketOutExRunEvent.class); registerPacket(0x0071, PacketOutExReset.class); registerPacket(0x0072, PacketOutExReload.class); - registerPacket(0x0073, PacketOutExUpdateWhitelist.class); + registerPacket(0x0073, PacketOutExEditServer.class); registerPacket(0x0074, PacketExSyncPlayer.class); registerPacket(0x0075, PacketExTransferPlayer.class); registerPacket(0x0076, PacketExDisconnectPlayer.class); diff --git a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/SubAPI.java b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/SubAPI.java index d57b6734..d1b4cd27 100644 --- a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/SubAPI.java +++ b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/SubAPI.java @@ -266,7 +266,7 @@ public final class SubAPI implements BungeeAPI { if (Util.isNull(name, getHost(name))) throw new NullPointerException(); SubRemoveHostEvent event = new SubRemoveHostEvent(player, getHost(name)); plugin.getPluginManager().callEvent(event); - if (event.isCancelled()) { + if (!event.isCancelled()) { if (getHost(name).destroy()) { plugin.hosts.remove(name.toLowerCase()); return true; @@ -466,7 +466,7 @@ public final class SubAPI implements BungeeAPI { if (Util.isNull(name, getServer(name))) throw new NullPointerException(); SubRemoveServerEvent event = new SubRemoveServerEvent(player, null, getServer(name)); plugin.getPluginManager().callEvent(event); - if (event.isCancelled()) { + if (!event.isCancelled()) { plugin.exServers.remove(name.toLowerCase()); return true; } else return false; diff --git a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/SubProxy.java b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/SubProxy.java index 58e98552..26b7fd99 100644 --- a/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/SubProxy.java +++ b/SubServers.Bungee/src/net/ME1312/SubServers/Bungee/SubProxy.java @@ -26,6 +26,7 @@ import net.ME1312.SubServers.Bungee.Library.Fallback.SmartFallback; import net.ME1312.SubServers.Bungee.Library.Metrics; import net.ME1312.SubServers.Bungee.Network.Packet.PacketExDisconnectPlayer; import net.ME1312.SubServers.Bungee.Network.Packet.PacketExSyncPlayer; +import net.ME1312.SubServers.Bungee.Network.Packet.PacketLinkServer; import net.ME1312.SubServers.Bungee.Network.Packet.PacketOutExReload; import net.ME1312.SubServers.Bungee.Network.SubProtocol; @@ -365,6 +366,7 @@ public final class SubProxy extends BungeeCommon implements Listener { Util.isException(subdata::waitFor); } + PacketLinkServer.strict = config.get().getMap("Settings").getBoolean("Strict-Server-Linking", true); SmartFallback.dns_forward = config.get().getMap("Settings").getMap("Smart-Fallback", new ObjectMap<>()).getBoolean("DNS-Forward", false); int hosts = 0; @@ -937,14 +939,14 @@ public final class SubProxy extends BungeeCommon implements Listener { if ((dynamic = SmartFallback.getForcedHost(e.getConnection()) == null) && getReconnectHandler() instanceof SmartFallback && (override = SmartFallback.getDNS(e.getConnection())) != null) { if (!(override instanceof SubServer) || ((SubServer) override).isRunning()) { if (!e.getConnection().getListener().isPingPassthrough()) { - e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(override.getMotd()), null)); + e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(override.getMotd()), e.getResponse().getFaviconObject())); } else { Container lock = new Container<>(true); boolean mode = plugin != null; if (mode) e.registerIntent(plugin); ((BungeeServerInfo) override).ping((ping, error) -> { if (error != null) { - e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(getTranslation("ping_cannot_connect")), null)); + e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(getTranslation("ping_cannot_connect")), e.getResponse().getFaviconObject())); } else e.setResponse(ping); lock.value = false; if (mode) e.completeIntent(plugin); @@ -962,7 +964,7 @@ public final class SubProxy extends BungeeCommon implements Listener { ServerInfo override; if ((override = SmartFallback.getForcedHost(e.getConnection())) != null || (override = SmartFallback.getDNS(e.getConnection())) != null) { if (override instanceof SubServer && !((SubServer) override).isRunning()) { - e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(api.getLang("SubServers", "Bungee.Ping.Offline")), null)); + e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(api.getLang("SubServers", "Bungee.Ping.Offline")), e.getResponse().getFaviconObject())); } } else { int offline = 0; @@ -973,7 +975,7 @@ public final class SubProxy extends BungeeCommon implements Listener { } if (offline >= e.getConnection().getListener().getServerPriority().size()) { - e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(api.getLang("SubServers", "Bungee.Ping.Offline")), null)); + e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(api.getLang("SubServers", "Bungee.Ping.Offline")), e.getResponse().getFaviconObject())); } } } @@ -1010,14 +1012,14 @@ public final class SubProxy extends BungeeCommon implements Listener { if (e.getPlayer().getServer() == null || fallback.containsKey(e.getPlayer().getUniqueId())) { if (!fallback.containsKey(e.getPlayer().getUniqueId()) || fallback.get(e.getPlayer().getUniqueId()).names.contains(e.getTarget().getName())) { ServerKickEvent kick = new ServerKickEvent(e.getPlayer(), e.getTarget(), new BaseComponent[]{ - new TextComponent(getTranslation("no_server_permission")) + new TextComponent(api.getLang("SubServers", "Bungee.Restricted")) }, null, ServerKickEvent.State.CONNECTING); fallback(kick); if (!kick.isCancelled()) e.getPlayer().disconnect(kick.getKickReasonComponent()); if (e.getPlayer().getServer() != null) e.setCancelled(true); } } else { - e.getPlayer().sendMessage(getTranslation("no_server_permission")); + e.getPlayer().sendMessage(api.getLang("SubServers", "Bungee.Restricted")); e.setCancelled(true); } } else if (e.getPlayer().getServer() != null && !fallback.containsKey(e.getPlayer().getUniqueId()) && e.getTarget() instanceof SubServer && !((SubServer) e.getTarget()).isRunning()) { @@ -1088,9 +1090,6 @@ public final class SubProxy extends BungeeCommon implements Listener { if (init) fallback.put(e.getPlayer().getUniqueId(), state); e.setCancelServer(state.servers.getFirst()); - if (Util.isException(() -> ServerKickEvent.class.getMethod("setCancelServers", ServerInfo[].class).invoke(e, (Object) state.servers.toArray(new ServerInfo[0])))) { - ((UserConnection) e.getPlayer()).setServerJoinQueue(new LinkedList<>(state.names)); - } } } } diff --git a/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Graphic/DefaultUIHandler.java b/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Graphic/DefaultUIHandler.java index 04630543..1c32f07c 100644 --- a/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Graphic/DefaultUIHandler.java +++ b/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Graphic/DefaultUIHandler.java @@ -53,7 +53,7 @@ public class DefaultUIHandler implements UIHandler, Listener { } public DefaultUIRenderer getRenderer(Player player) { - if (!gui.keySet().contains(player.getUniqueId())) gui.put(player.getUniqueId(), new DefaultUIRenderer(plugin, player.getUniqueId())); + if (!gui.containsKey(player.getUniqueId())) gui.put(player.getUniqueId(), new DefaultUIRenderer(plugin, player.getUniqueId())); return gui.get(player.getUniqueId()); } @@ -64,7 +64,7 @@ public class DefaultUIHandler implements UIHandler, Listener { @EventHandler(priority = EventPriority.HIGHEST) public void click(InventoryClickEvent event) { Player player = (Player) event.getWhoClicked(); - if (!event.isCancelled() && enabled && gui.keySet().contains(player.getUniqueId())) { + if (!event.isCancelled() && enabled && gui.containsKey(player.getUniqueId())) { DefaultUIRenderer gui = this.gui.get(player.getUniqueId()); String title = event.getView().getTitle(); @@ -463,7 +463,7 @@ public class DefaultUIHandler implements UIHandler, Listener { @SuppressWarnings("deprecation") @EventHandler(priority = EventPriority.LOWEST) public void input(org.bukkit.event.player.PlayerChatEvent event) { - if (!event.isCancelled() && enabled && input.keySet().contains(event.getPlayer().getUniqueId())) { + if (!event.isCancelled() && enabled && input.containsKey(event.getPlayer().getUniqueId())) { YAMLSection data = new YAMLSection(); data.set("message", event.getMessage()); input.get(event.getPlayer().getUniqueId()).run(data); @@ -479,7 +479,7 @@ public class DefaultUIHandler implements UIHandler, Listener { */ @EventHandler(priority = EventPriority.LOWEST) public void input(PlayerCommandPreprocessEvent event) { - if (!event.isCancelled() && enabled && input.keySet().contains(event.getPlayer().getUniqueId())) { + if (!event.isCancelled() && enabled && input.containsKey(event.getPlayer().getUniqueId())) { YAMLSection data = new YAMLSection(); data.set("message", (event.getMessage().startsWith("/"))?event.getMessage().substring(1):event.getMessage()); input.get(event.getPlayer().getUniqueId()).run(data); diff --git a/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Graphic/UIRenderer.java b/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Graphic/UIRenderer.java index 829fa956..84837536 100644 --- a/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Graphic/UIRenderer.java +++ b/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Graphic/UIRenderer.java @@ -227,42 +227,43 @@ public abstract class UIRenderer { * @param subtitle Subtitle to display (or null to hide) */ public void setDownloading(String subtitle) { - final String text = subtitle; + if (subtitle != null) { + if (!canSendTitle()) { + final String text = subtitle; + if (download != -1) Bukkit.getScheduler().cancelTask(download); + download = Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> { + if (tdownload != null) Bukkit.getPlayer(player).sendMessage(plugin.api.getLang("SubServers", "Interface.Generic.Downloading").replace("$str$", text)); + download = -1; + }, 50L); + return; + } - if (text != null && !canSendTitle()) { - if (download != -1) Bukkit.getScheduler().cancelTask(download); - download = Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> { - if (tdownload != null) Bukkit.getPlayer(player).sendMessage(plugin.api.getLang("SubServers", "Interface.Generic.Downloading").replace("$str$", text)); - download = -1; - }, 50L); - return; - } + if (!subtitle.startsWith(Character.toString(ChatColor.COLOR_CHAR))) { + subtitle = plugin.api.getLang("SubServers", "Interface.Generic.Downloading.Title-Color-Alt") + subtitle; + } + if (tdownload == null) { + tdownload = new ContainedPair(subtitle, 0); - if (subtitle != null && !subtitle.startsWith(Character.toString(ChatColor.COLOR_CHAR))) { - subtitle = plugin.api.getLang("SubServers", "Interface.Generic.Downloading.Title-Color-Alt") + subtitle; - } - if (subtitle != null && tdownload == null) { - tdownload = new ContainedPair(subtitle, 0); + new BukkitRunnable() { + @Override + public void run() { + if (tdownload != null) { + if (++tdownload.value >= adownload.length) { + tdownload.value = 0; + } - new BukkitRunnable() { - @Override - public void run() { - if (tdownload != null) { - if (++tdownload.value >= adownload.length) { - tdownload.value = 0; - } - - if (!sendTitle(adownload[tdownload.value], tdownload.key, 0, 10, 5)) { + if (!sendTitle(adownload[tdownload.value], tdownload.key, 0, 10, 5)) { + cancel(); + } + } else { + sendTitle(null); cancel(); } - } else { - sendTitle(null); - cancel(); } - } - }.runTaskTimer(plugin, 0, 1); - } else if (subtitle != null) { - tdownload.key = subtitle; + }.runTaskTimer(plugin, 0, 1); + } else { + tdownload.key = subtitle; + } } else { if (tdownload != null) { tdownload = null; diff --git a/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Library/ConfigUpdater.java b/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Library/ConfigUpdater.java index e35afdc6..f92dccb7 100644 --- a/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Library/ConfigUpdater.java +++ b/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Library/ConfigUpdater.java @@ -60,7 +60,8 @@ public class ConfigUpdater { settings.set("Show-Addresses", updated.getMap("Settings", new YAMLSection()).getBoolean("Show-Addresses", false)); settings.set("Use-Title-Messages", updated.getMap("Settings", new YAMLSection()).getBoolean("Use-Title-Messages", true)); settings.set("PlaceholderAPI-Ready", updated.getMap("Settings", new YAMLSection()).getBoolean("PlaceholderAPI-Ready", false)); - if (updated.getMap("Settings", new YAMLSection()).contains("PlaceholderAPI-Cache-Interval")) settings.set("PlaceholderAPI-Cache-Interval", updated.getMap("Settings").getRawString("PlaceholderAPI-Cache-Interval")); + if (updated.getMap("Settings", new YAMLSection()).contains("PlaceholderAPI-Cache-Interval")) settings.set("PlaceholderAPI-Cache-Interval", updated.getMap("Settings").getInt("PlaceholderAPI-Cache-Interval")); + if (updated.getMap("Settings", new YAMLSection()).contains("Connect-Address")) settings.set("Connect-Address", updated.getMap("Settings").getRawString("Connect-Address")); YAMLSection subdata = new YAMLSection(); if (updated.getMap("Settings", new YAMLSection()).getMap("SubData", new YAMLSection()).contains("Name")) subdata.set("Name", updated.getMap("Settings").getMap("SubData").getRawString("Name")); diff --git a/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Network/Packet/PacketLinkServer.java b/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Network/Packet/PacketLinkServer.java index ec7fc714..cf83be4f 100644 --- a/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Network/Packet/PacketLinkServer.java +++ b/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/Network/Packet/PacketLinkServer.java @@ -43,7 +43,15 @@ public class PacketLinkServer implements InitialPacket, PacketObjectIn, public ObjectMap send(SubDataSender client) { ObjectMap json = new ObjectMap(); if (plugin.api.getName() != null) json.set(0x0000, plugin.api.getName()); - json.set(0x0001, Bukkit.getServer().getPort()); + + String address = plugin.server_address; + if (address != null) { + if (address.indexOf(':') == -1) address += ":" + Bukkit.getServer().getPort(); + json.set(0x0001, address); + } else { + json.set(0x0001, Bukkit.getServer().getPort()); + } + json.set(0x0002, channel); return json; } diff --git a/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/SubPlugin.java b/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/SubPlugin.java index fb7b1067..7fcb2fc4 100644 --- a/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/SubPlugin.java +++ b/SubServers.Client/Bukkit/src/net/ME1312/SubServers/Client/Bukkit/SubPlugin.java @@ -49,6 +49,7 @@ public final class SubPlugin extends JavaPlugin { public UIHandler gui = null; public final Version version; public final SubAPI api = new SubAPI(this); + public String server_address; private long resetDate = 0; private boolean reconnect = false; @@ -94,6 +95,7 @@ public final class SubPlugin extends JavaPlugin { subprotocol.registerCipher("DHE-192", DHE.get(192)); subprotocol.registerCipher("DHE-256", DHE.get(256)); api.name = config.get().getMap("Settings").getMap("SubData").getString("Name", System.getenv("name")); + server_address = config.get().getMap("Settings").getRawString("Connect-Address", System.getenv("address")); if (config.get().getMap("Settings").getMap("SubData").getRawString("Password", "").length() > 0) { subprotocol.registerCipher("AES", new AES(128, config.get().getMap("Settings").getMap("SubData").getRawString("Password"))); diff --git a/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/Library/ConfigUpdater.java b/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/Library/ConfigUpdater.java index 8558d1d7..e41321a0 100644 --- a/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/Library/ConfigUpdater.java +++ b/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/Library/ConfigUpdater.java @@ -59,6 +59,7 @@ public class ConfigUpdater { settings.set("Allow-Deletion", updated.getMap("Settings", new YAMLSection()).getBoolean("Allow-Deletion", false)); settings.set("Show-Addresses", updated.getMap("Settings", new YAMLSection()).getBoolean("Show-Addresses", false)); settings.set("Use-Title-Messages", updated.getMap("Settings", new YAMLSection()).getBoolean("Use-Title-Messages", true)); + if (updated.getMap("Settings", new YAMLSection()).contains("Connect-Address")) settings.set("Connect-Address", updated.getMap("Settings").getRawString("Connect-Address")); YAMLSection subdata = new YAMLSection(); if (updated.getMap("Settings", new YAMLSection()).getMap("SubData", new YAMLSection()).contains("Name")) subdata.set("Name", updated.getMap("Settings").getMap("SubData").getRawString("Name")); diff --git a/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/Network/Packet/PacketLinkServer.java b/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/Network/Packet/PacketLinkServer.java index 7362b37e..ae2a86c8 100644 --- a/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/Network/Packet/PacketLinkServer.java +++ b/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/Network/Packet/PacketLinkServer.java @@ -44,7 +44,16 @@ public class PacketLinkServer implements InitialPacket, PacketObjectIn, public ObjectMap send(SubDataSender client) { ObjectMap json = new ObjectMap(); if (plugin.api.getName() != null) json.set(0x0000, plugin.api.getName()); - if (plugin.game.getServer().getBoundAddress().isPresent()) json.set(0x0001, plugin.game.getServer().getBoundAddress().get().getPort()); + if (plugin.game.getServer().getBoundAddress().isPresent()) { + String address = plugin.server_address; + if (address != null) { + if (address.indexOf(':') == -1) address += ":" + plugin.game.getServer().getBoundAddress().get().getPort(); + json.set(0x0001, address); + } else { + json.set(0x0001, plugin.game.getServer().getBoundAddress().get().getPort()); + } + } + json.set(0x0002, channel); return json; } diff --git a/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/SubCommand.java b/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/SubCommand.java index 1168c773..05e8c952 100644 --- a/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/SubCommand.java +++ b/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/SubCommand.java @@ -62,7 +62,7 @@ public final class SubCommand implements CommandExecutor { */ public CommandSpec spec() { SubCommand root = new SubCommand(plugin); - return CommandSpec.builder() + CommandSpec.Builder spec = CommandSpec.builder() .description(Text.of("The SubServers Command")) .executor(root) .arguments(GenericArguments.optional(GenericArguments.string(Text.of("Command"))), GenericArguments.optional(GenericArguments.remainingJoinedStrings(Text.of("...")))) @@ -121,11 +121,6 @@ public final class SubCommand implements CommandExecutor { .executor(new UPDATE()) .arguments(GenericArguments.optional(new ListArgument(Text.of("Subservers"))), GenericArguments.optional(GenericArguments.string(Text.of("Template"))), GenericArguments.optional(GenericArguments.string(Text.of("Version"))), GenericArguments.optional(GenericArguments.remainingJoinedStrings(Text.of("...")))) .build(), "update", "upgrade") - .child(CommandSpec.builder() - .description(Text.of("The SubServers Command - Delete")) - .executor(new DELETE()) - .arguments(GenericArguments.optional(new ListArgument(Text.of("Subservers"))), GenericArguments.optional(GenericArguments.remainingJoinedStrings(Text.of("...")))) - .build(), "remove", "del", "delete") .child(CommandSpec.builder() .description(Text.of("The SubServers Command - Teleport")) .executor(new TELEPORT()) @@ -135,8 +130,16 @@ public final class SubCommand implements CommandExecutor { .description(Text.of("The SubServers Command - Open Menu")) .executor(new OPEN()) .arguments(GenericArguments.optional(GenericArguments.string(Text.of("Menu"))), GenericArguments.optional(GenericArguments.allOf(GenericArguments.string(Text.of("Args"))))) - .build(), "open", "view") - .build(); + .build(), "open", "view"); + + if (plugin.config.get().getMap("Settings").getBoolean("Allow-Deletion", false)) spec + .child(CommandSpec.builder() + .description(Text.of("The SubServers Command - Delete")) + .executor(new DELETE()) + .arguments(GenericArguments.optional(new ListArgument(Text.of("Subservers"))), GenericArguments.optional(GenericArguments.remainingJoinedStrings(Text.of("...")))) + .build(), "remove", "del", "delete"); + + return spec.build(); } private boolean canRun(CommandSource sender) throws CommandException { diff --git a/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/SubPlugin.java b/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/SubPlugin.java index c542c95f..f9830c7d 100644 --- a/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/SubPlugin.java +++ b/SubServers.Client/Sponge/src/net/ME1312/SubServers/Client/Sponge/SubPlugin.java @@ -63,6 +63,7 @@ public final class SubPlugin { public UIHandler gui = null; public Version version; public SubAPI api; + public String server_address; @Inject public PluginContainer plugin; @Inject public Game game; @@ -114,6 +115,7 @@ public final class SubPlugin { subprotocol.registerCipher("DHE-192", DHE.get(192)); subprotocol.registerCipher("DHE-256", DHE.get(256)); api.name = config.get().getMap("Settings").getMap("SubData").getString("Name", System.getenv("name")); + server_address = config.get().getMap("Settings").getRawString("Connect-Address", System.getenv("address")); Logger log = LoggerFactory.getLogger("SubData"); if (config.get().getMap("Settings").getMap("SubData").getRawString("Password", "").length() > 0) { diff --git a/SubServers.Host/src/net/ME1312/SubServers/Host/Executable/SubCreatorImpl.java b/SubServers.Host/src/net/ME1312/SubServers/Host/Executable/SubCreatorImpl.java index 85ff6658..ac378c8e 100644 --- a/SubServers.Host/src/net/ME1312/SubServers/Host/Executable/SubCreatorImpl.java +++ b/SubServers.Host/src/net/ME1312/SubServers/Host/Executable/SubCreatorImpl.java @@ -580,7 +580,6 @@ public class SubCreatorImpl { if (installed) { YAMLSection config = new YAMLSection(); FileWriter writer = new FileWriter(new UniversalFile(dir, "subdata.json"), false); - config.set("Name", name); config.setAll(getSubData()); writer.write(config.toJSON().toString()); writer.close(); diff --git a/SubServers.Host/src/net/ME1312/SubServers/Host/Executable/SubServerImpl.java b/SubServers.Host/src/net/ME1312/SubServers/Host/Executable/SubServerImpl.java index d09ae761..f905b4bb 100644 --- a/SubServers.Host/src/net/ME1312/SubServers/Host/Executable/SubServerImpl.java +++ b/SubServers.Host/src/net/ME1312/SubServers/Host/Executable/SubServerImpl.java @@ -8,7 +8,8 @@ import net.ME1312.Galaxi.Library.Version.Version; import net.ME1312.SubData.Client.SubDataClient; import net.ME1312.SubServers.Host.ExHost; import net.ME1312.SubServers.Host.Library.Exception.InvalidServerException; -import net.ME1312.SubServers.Host.Network.Packet.PacketExEditServer; +import net.ME1312.SubServers.Host.Network.Packet.PacketExControlServer; +import net.ME1312.SubServers.Host.Network.Packet.PacketExControlServer.Response; import net.ME1312.SubServers.Host.SubAPI; import java.io.BufferedWriter; @@ -124,12 +125,12 @@ public class SubServerImpl { } catch (IOException | InterruptedException e) { host.log.error.println(e); allowrestart = false; - if (falsestart) ((SubDataClient) SubAPI.getInstance().getSubDataNetwork()[0]).sendPacket(new PacketExEditServer(this, PacketExEditServer.UpdateType.LAUNCH_EXCEPTION)); + if (falsestart) ((SubDataClient) SubAPI.getInstance().getSubDataNetwork()[0]).sendPacket(new PacketExControlServer(this, Response.LAUNCH_EXCEPTION)); } logger.destroy(); if (SubAPI.getInstance().getSubDataNetwork()[0] != null) { - ((SubDataClient) SubAPI.getInstance().getSubDataNetwork()[0]).sendPacket(new PacketExEditServer(this, PacketExEditServer.UpdateType.STOPPED, (Integer) process.exitValue(), (Boolean) allowrestart)); + ((SubDataClient) SubAPI.getInstance().getSubDataNetwork()[0]).sendPacket(new PacketExControlServer(this, Response.STOPPED, (Integer) process.exitValue(), (Boolean) allowrestart)); } host.log.info.println(name + " has stopped"); process = null; diff --git a/SubServers.Host/src/net/ME1312/SubServers/Host/Network/Packet/PacketExEditServer.java b/SubServers.Host/src/net/ME1312/SubServers/Host/Network/Packet/PacketExControlServer.java similarity index 78% rename from SubServers.Host/src/net/ME1312/SubServers/Host/Network/Packet/PacketExEditServer.java rename to SubServers.Host/src/net/ME1312/SubServers/Host/Network/Packet/PacketExControlServer.java index 8dc73f01..a75bbb87 100644 --- a/SubServers.Host/src/net/ME1312/SubServers/Host/Network/Packet/PacketExEditServer.java +++ b/SubServers.Host/src/net/ME1312/SubServers/Host/Network/Packet/PacketExControlServer.java @@ -12,15 +12,15 @@ import net.ME1312.SubServers.Host.Executable.SubServerImpl; import java.util.Arrays; /** - * Edit Server Packet + * Control Server Packet */ -public class PacketExEditServer implements PacketObjectIn, PacketObjectOut { +public class PacketExControlServer implements PacketObjectIn, PacketObjectOut { private ExHost host; private SubServerImpl server; - private UpdateType type; + private Response type; private Object[] args; - public enum UpdateType { + public enum Response { // Status LAUNCH_EXCEPTION(1), STOPPED(2, Integer.class, Boolean.class); @@ -28,7 +28,7 @@ public class PacketExEditServer implements PacketObjectIn, PacketObject private short value; private Class[] args; - UpdateType(int value, Class... args) { + Response(int value, Class... args) { this.value = (short) value; this.args = args; } @@ -43,29 +43,30 @@ public class PacketExEditServer implements PacketObjectIn, PacketObject } /** - * New PacketExEditServer (In) + * New PacketExControlServer (In) * @param host ExHost */ - public PacketExEditServer(ExHost host) { + public PacketExControlServer(ExHost host) { this.host = host; } /** - * New PacketExEditServer (Out) + * New PacketExControlServer (Out) * * @param type Update Type * @param arguments Arguments */ - public PacketExEditServer(SubServerImpl server, UpdateType type, Object... arguments) { - if (arguments.length != type.getArguments().length) throw new IllegalArgumentException(((arguments.length > type.getArguments().length)?"Too many":"Not enough") + " arguments for type: " + type.toString()); - int i = 0; - while (i < arguments.length) { - if (!type.getArguments()[i].isInstance(arguments[i])) throw new IllegalArgumentException("Argument " + (i+1) + " is not " + type.getArguments()[i].getCanonicalName()); - i++; - } + public PacketExControlServer(SubServerImpl server, Response type, Object... arguments) { + if (arguments.length < type.getArguments().length) throw new IllegalArgumentException("Not enough arguments for type: " + type); + this.server = server; this.type = type; - this.args = arguments; + this.args = new Object[type.getArguments().length]; + + for (int i = 0; i < type.getArguments().length; ++i) { + if (!type.getArguments()[i].isInstance(arguments[i])) throw new IllegalArgumentException("Argument " + (i+1) + " is not " + type.getArguments()[i].getCanonicalName()); + args[i] = arguments[i]; + } } @Override @@ -80,7 +81,7 @@ public class PacketExEditServer implements PacketObjectIn, PacketObject @Override public void receive(SubDataSender client, ObjectMap data) { try { - SubServerImpl server = host.servers.get(data.getString(0x0000).toLowerCase()); + SubServerImpl server = host.servers.get(data.getRawString(0x0000).toLowerCase()); switch (data.getInt(0x0001)) { case 0: server.setEnabled(data.getList(0x0002).get(0).asBoolean()); diff --git a/SubServers.Host/src/net/ME1312/SubServers/Host/Network/SubProtocol.java b/SubServers.Host/src/net/ME1312/SubServers/Host/Network/SubProtocol.java index bd3d4bfd..b3a7cbf2 100644 --- a/SubServers.Host/src/net/ME1312/SubServers/Host/Network/SubProtocol.java +++ b/SubServers.Host/src/net/ME1312/SubServers/Host/Network/SubProtocol.java @@ -1,12 +1,10 @@ package net.ME1312.SubServers.Host.Network; import net.ME1312.Galaxi.Library.Callback.Callback; -import net.ME1312.Galaxi.Library.Config.YAMLSection; import net.ME1312.Galaxi.Library.Container.Pair; import net.ME1312.Galaxi.Library.Map.ObjectMap; import net.ME1312.Galaxi.Library.Util; import net.ME1312.Galaxi.Library.Version.Version; -import net.ME1312.SubData.Client.Library.DataSize; import net.ME1312.SubData.Client.SubDataClient; import net.ME1312.SubData.Client.SubDataProtocol; import net.ME1312.SubServers.Client.Common.Network.Packet.*; @@ -101,7 +99,7 @@ public class SubProtocol extends SubDataProtocol { registerPacket(0x0053, PacketOutExRequestQueue.class); registerPacket(0x0054, PacketExCreateServer.class); registerPacket(0x0055, PacketExAddServer.class); - registerPacket(0x0056, PacketExEditServer.class); + registerPacket(0x0056, PacketExControlServer.class); registerPacket(0x0057, PacketOutExLogMessage.class); registerPacket(0x0058, PacketExRemoveServer.class); registerPacket(0x0059, PacketExDeleteServer.class); @@ -112,7 +110,7 @@ public class SubProtocol extends SubDataProtocol { //registerPacket(0x0053, new PacketOutExRequestQueue(host)); registerPacket(0x0054, new PacketExCreateServer(host)); registerPacket(0x0055, new PacketExAddServer(host)); - registerPacket(0x0056, new PacketExEditServer(host)); + registerPacket(0x0056, new PacketExControlServer(host)); //registerPacket(0x0057, new PacketOutExLogMessage()); registerPacket(0x0058, new PacketExRemoveServer(host)); registerPacket(0x0059, new PacketExDeleteServer(host)); diff --git a/SubServers.Sync/pom.xml b/SubServers.Sync/pom.xml index b18d42d6..ef91fa02 100644 --- a/SubServers.Sync/pom.xml +++ b/SubServers.Sync/pom.xml @@ -83,20 +83,6 @@ - - verify - verify - - run - - - - - - - - - @@ -108,31 +94,6 @@ ../Artifacts/Maven - - org.apache.maven.plugins - maven-assembly-plugin - 2.2-beta-5 - - SubServers.Sync - ../Artifacts - - src/META-INF/MANIFEST.MF - - - jar-with-dependencies - - false - - - - make-assembly - package - - single - - - - org.apache.maven.plugins maven-javadoc-plugin diff --git a/SubServers.Sync/src/net/ME1312/SubServers/Sync/ExProxy.java b/SubServers.Sync/src/net/ME1312/SubServers/Sync/ExProxy.java index a7bc56dd..f6f35668 100644 --- a/SubServers.Sync/src/net/ME1312/SubServers/Sync/ExProxy.java +++ b/SubServers.Sync/src/net/ME1312/SubServers/Sync/ExProxy.java @@ -429,14 +429,14 @@ public final class ExProxy extends BungeeCommon implements Listener { if ((dynamic = SmartFallback.getForcedHost(e.getConnection()) == null) && getReconnectHandler() instanceof SmartFallback && (override = SmartFallback.getDNS(e.getConnection())) != null) { if (!(override instanceof SubServerImpl) || ((SubServerImpl) override).isRunning()) { if (!e.getConnection().getListener().isPingPassthrough()) { - e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(override.getMotd()), null)); + e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(override.getMotd()), e.getResponse().getFaviconObject())); } else { Container lock = new Container<>(true); boolean mode = plugin != null; if (mode) e.registerIntent(plugin); ((BungeeServerInfo) override).ping((ping, error) -> { if (error != null) { - e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(getTranslation("ping_cannot_connect")), null)); + e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(getTranslation("ping_cannot_connect")), e.getResponse().getFaviconObject())); } else e.setResponse(ping); lock.value = false; if (mode) e.completeIntent(plugin); @@ -454,17 +454,17 @@ public final class ExProxy extends BungeeCommon implements Listener { ServerInfo override; if ((override = SmartFallback.getForcedHost(e.getConnection())) != null || (override = SmartFallback.getDNS(e.getConnection())) != null) { if (override instanceof SubServerImpl && !((SubServerImpl) override).isRunning()) { - e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(api.getLang("SubServers", "Bungee.Ping.Offline")), null)); + e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(api.getLang("SubServers", "Bungee.Ping.Offline")), e.getResponse().getFaviconObject())); } } else { int offline = 0; for (String name : e.getConnection().getListener().getServerPriority()) { ServerInfo server = getServerInfo(name); - if (server == null || (server instanceof SubServerImpl && !((SubServerImpl) server).isRunning())) offline++; + if (server instanceof SubServerImpl && !((SubServerImpl) server).isRunning()) offline++; } if (offline >= e.getConnection().getListener().getServerPriority().size()) { - e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(api.getLang("SubServers", "Bungee.Ping.Offline")), null)); + e.setResponse(new ServerPing(e.getResponse().getVersion(), e.getResponse().getPlayers(), new TextComponent(api.getLang("SubServers", "Bungee.Ping.Offline")), e.getResponse().getFaviconObject())); } } } @@ -501,14 +501,14 @@ public final class ExProxy extends BungeeCommon implements Listener { if (e.getPlayer().getServer() == null || fallback.containsKey(e.getPlayer().getUniqueId())) { if (!fallback.containsKey(e.getPlayer().getUniqueId()) || fallback.get(e.getPlayer().getUniqueId()).names.contains(e.getTarget().getName())) { ServerKickEvent kick = new ServerKickEvent(e.getPlayer(), e.getTarget(), new BaseComponent[]{ - new TextComponent(getTranslation("no_server_permission")) + new TextComponent(api.getLang("SubServers", "Bungee.Restricted")) }, null, ServerKickEvent.State.CONNECTING); fallback(kick); if (!kick.isCancelled()) e.getPlayer().disconnect(kick.getKickReasonComponent()); if (e.getPlayer().getServer() != null) e.setCancelled(true); } } else { - e.getPlayer().sendMessage(getTranslation("no_server_permission")); + e.getPlayer().sendMessage(api.getLang("SubServers", "Bungee.Restricted")); e.setCancelled(true); } } else if (e.getPlayer().getServer() != null && !fallback.containsKey(e.getPlayer().getUniqueId()) && e.getTarget() instanceof SubServerImpl && !((SubServerImpl) e.getTarget()).isRunning()) { @@ -560,7 +560,7 @@ public final class ExProxy extends BungeeCommon implements Listener { @SuppressWarnings("deprecation") @EventHandler(priority = Byte.MAX_VALUE) public void fallback(ServerKickEvent e) { - if (e.getPlayer().isConnected() && e.getPlayer() instanceof UserConnection && config.get().getMap("Settings").getMap("Smart-Fallback", new ObjectMap<>()).getBoolean("Fallback", true)) { + if (e.getPlayer().isConnected() && config.get().getMap("Settings").getMap("Smart-Fallback", new ObjectMap<>()).getBoolean("Fallback", true)) { FallbackState state; boolean init = !fallback.containsKey(e.getPlayer().getUniqueId()); if (init) { @@ -581,9 +581,6 @@ public final class ExProxy extends BungeeCommon implements Listener { if (init) fallback.put(e.getPlayer().getUniqueId(), state); e.setCancelServer(state.servers.getFirst()); - if (Util.isException(() -> ServerKickEvent.class.getMethod("setCancelServers", ServerInfo[].class).invoke(e, (Object) state.servers.toArray(new ServerInfo[0])))) { - ((UserConnection) e.getPlayer()).setServerJoinQueue(new LinkedList<>(state.names)); - } } } } @@ -635,7 +632,7 @@ public final class ExProxy extends BungeeCommon implements Listener { public Boolean merge(net.ME1312.SubServers.Client.Common.Network.API.Server server) { ServerImpl current = servers.get(server.getName().toLowerCase()); - if (current == null || server instanceof net.ME1312.SubServers.Client.Common.Network.API.SubServer || !(current instanceof SubServerImpl)) { + if (server instanceof net.ME1312.SubServers.Client.Common.Network.API.SubServer || !(current instanceof SubServerImpl)) { if (current == null || !current.getSignature().equals(server.getSignature())) { if (server instanceof net.ME1312.SubServers.Client.Common.Network.API.SubServer) { servers.put(server.getName().toLowerCase(), SubServerImpl.construct(server.getSignature(), server.getName(), server.getDisplayName(), server.getAddress(), @@ -668,45 +665,12 @@ public final class ExProxy extends BungeeCommon implements Listener { return null; } - @EventHandler(priority = Byte.MIN_VALUE) - public void edit(SubEditServerEvent e) { - if (servers.keySet().contains(e.getServer().toLowerCase())) { - ServerImpl server = servers.get(e.getServer().toLowerCase()); - switch (e.getEdit().key().toLowerCase()) { - case "display": - server.setDisplayName(e.getEdit().value().asString()); - break; - case "motd": - server.setMotd(ChatColor.translateAlternateColorCodes('&', e.getEdit().value().asString())); - break; - case "restricted": - server.setRestricted(e.getEdit().value().asBoolean()); - break; - case "hidden": - server.setHidden(e.getEdit().value().asBoolean()); - break; - } - } - } - @EventHandler(priority = Byte.MIN_VALUE) public void start(SubStartEvent e) { if (servers.keySet().contains(e.getServer().toLowerCase()) && servers.get(e.getServer().toLowerCase()) instanceof SubServerImpl) ((SubServerImpl) servers.get(e.getServer().toLowerCase())).setRunning(true); } - public void connect(ServerImpl server, int channel, UUID address) { - if (server != null) { - server.setSubData(address, channel); - } - } - - public void disconnect(ServerImpl server, int channel) { - if (server != null) { - server.setSubData(null, channel); - } - } - @EventHandler(priority = Byte.MIN_VALUE) public void stop(SubStoppedEvent e) { if (servers.keySet().contains(e.getServer().toLowerCase()) && servers.get(e.getServer().toLowerCase()) instanceof SubServerImpl) diff --git a/SubServers.Sync/src/net/ME1312/SubServers/Sync/Network/Packet/PacketInExEditServer.java b/SubServers.Sync/src/net/ME1312/SubServers/Sync/Network/Packet/PacketInExEditServer.java new file mode 100644 index 00000000..e4989d86 --- /dev/null +++ b/SubServers.Sync/src/net/ME1312/SubServers/Sync/Network/Packet/PacketInExEditServer.java @@ -0,0 +1,65 @@ +package net.ME1312.SubServers.Sync.Network.Packet; + +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubData.Client.Protocol.PacketObjectIn; +import net.ME1312.SubData.Client.SubDataSender; +import net.ME1312.SubServers.Sync.ExProxy; +import net.ME1312.SubServers.Sync.Server.ServerImpl; + +import java.util.ArrayList; + +/** + * Server Edit Notification Packet + */ +public class PacketInExEditServer implements PacketObjectIn { + private ExProxy plugin; + + /** + * New PacketExControlServer (In) + */ + public PacketInExEditServer(ExProxy plugin) { + this.plugin = plugin; + } + + @Override + public void receive(SubDataSender client, ObjectMap data) { + if (plugin.servers.containsKey(data.getRawString(0x0000).toLowerCase())) { + ServerImpl server = plugin.servers.get(data.getRawString(0x0000).toLowerCase()); + switch (data.getInt(0x0001)) { + case 0: + server.setDisplayName(data.getList(0x0002).get(0).asRawString()); + break; + case 1: + server.setMotd(data.getList(0x0002).get(0).asRawString()); + break; + case 2: + server.setRestricted(data.getList(0x0002).get(0).asBoolean()); + break; + case 3: + server.setHidden(data.getList(0x0002).get(0).asBoolean()); + break; + case 4: + server.setSubData(data.getList(0x0002).get(1).asUUID(), data.getList(0x0002).get(0).asInt()); + break; + case 5: + server.setSubData(null, data.getList(0x0002).get(0).asInt()); + break; + case 6: + server.whitelist = data.getList(0x0002).get(0).asUUIDList(); + break; + case 7: + server.whitelist(data.getList(0x0002).get(0).asUUID()); + break; + case 8: + server.unwhitelist(data.getList(0x0002).get(0).asUUID()); + break; + } + } + } + + @Override + public int version() { + return 0x0002; + } +} diff --git a/SubServers.Sync/src/net/ME1312/SubServers/Sync/Network/Packet/PacketInExRunEvent.java b/SubServers.Sync/src/net/ME1312/SubServers/Sync/Network/Packet/PacketInExRunEvent.java index 032d73d8..e2e2d4b1 100644 --- a/SubServers.Sync/src/net/ME1312/SubServers/Sync/Network/Packet/PacketInExRunEvent.java +++ b/SubServers.Sync/src/net/ME1312/SubServers/Sync/Network/Packet/PacketInExRunEvent.java @@ -90,20 +90,6 @@ public class PacketInExRunEvent implements PacketObjectIn { callback("SubStartedEvent", this); } }); - callback("SubNetworkConnectEvent", new Callback>() { - @Override - public void run(ObjectMap data) { - plugin.connect(plugin.servers.get(data.getRawString("server").toLowerCase()), data.getInt("channel"), data.getUUID("id")); - callback("SubNetworkConnectEvent", this); - } - }); - callback("SubNetworkDisconnectEvent", new Callback>() { - @Override - public void run(ObjectMap data) { - plugin.disconnect(plugin.servers.get(data.getRawString("server").toLowerCase()), data.getInt("channel")); - callback("SubNetworkDisconnectEvent", this); - } - }); callback("SubStopEvent", new Callback>() { @Override public void run(ObjectMap data) { diff --git a/SubServers.Sync/src/net/ME1312/SubServers/Sync/Network/Packet/PacketInExUpdateWhitelist.java b/SubServers.Sync/src/net/ME1312/SubServers/Sync/Network/Packet/PacketInExUpdateWhitelist.java deleted file mode 100644 index c961804e..00000000 --- a/SubServers.Sync/src/net/ME1312/SubServers/Sync/Network/Packet/PacketInExUpdateWhitelist.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.ME1312.SubServers.Sync.Network.Packet; - -import net.ME1312.Galaxi.Library.Map.ObjectMap; -import net.ME1312.SubData.Client.Protocol.PacketObjectIn; -import net.ME1312.SubData.Client.SubDataSender; -import net.ME1312.SubServers.Sync.ExProxy; - -/** - * Update External Whitelist Packet - */ -public class PacketInExUpdateWhitelist implements PacketObjectIn { - private ExProxy plugin; - - /** - * New PacketInExUpdateWhitelist - */ - public PacketInExUpdateWhitelist(ExProxy plugin) { - this.plugin = plugin; - } - - @Override - public void receive(SubDataSender client, ObjectMap data) { - if (data.getBoolean(0x0001)) { - plugin.servers.get(data.getRawString(0x0000)).whitelist(data.getUUID(0x0002)); - } else { - plugin.servers.get(data.getRawString(0x0000)).unwhitelist(data.getUUID(0x0002)); - } - } - - @Override - public int version() { - return 0x0001; - } -} diff --git a/SubServers.Sync/src/net/ME1312/SubServers/Sync/Network/SubProtocol.java b/SubServers.Sync/src/net/ME1312/SubServers/Sync/Network/SubProtocol.java index 7a5ced8c..b42ff053 100644 --- a/SubServers.Sync/src/net/ME1312/SubServers/Sync/Network/SubProtocol.java +++ b/SubServers.Sync/src/net/ME1312/SubServers/Sync/Network/SubProtocol.java @@ -114,7 +114,7 @@ public class SubProtocol extends SubDataProtocol { registerPacket(0x0070, new PacketInExRunEvent(plugin)); registerPacket(0x0071, new PacketInExReset()); - registerPacket(0x0073, new PacketInExUpdateWhitelist(plugin)); + registerPacket(0x0073, new PacketInExEditServer(plugin)); registerPacket(0x0074, new PacketExSyncPlayer(plugin)); registerPacket(0x0075, new PacketExTransferPlayer(plugin)); registerPacket(0x0076, new PacketExDisconnectPlayer(plugin)); diff --git a/SubServers.Sync/src/net/ME1312/SubServers/Sync/Server/ServerImpl.java b/SubServers.Sync/src/net/ME1312/SubServers/Sync/Server/ServerImpl.java index c051f0e3..67128d25 100644 --- a/SubServers.Sync/src/net/ME1312/SubServers/Sync/Server/ServerImpl.java +++ b/SubServers.Sync/src/net/ME1312/SubServers/Sync/Server/ServerImpl.java @@ -20,7 +20,7 @@ import java.util.*; */ public class ServerImpl extends BungeeServerInfo { private HashMap subdata = new HashMap(); - private List whitelist = new ArrayList(); + public List whitelist = new ArrayList(); private String nick = null; private boolean hidden; private final String signature; @@ -176,7 +176,7 @@ public class ServerImpl extends BungeeServerInfo { * @return Whitelisted Status */ public boolean canAccess(CommandSender player) { - return (player instanceof ProxiedPlayer && whitelist.contains(((ProxiedPlayer) player).getUniqueId())) || super.canAccess(player); + return super.canAccess(player) || (player instanceof ProxiedPlayer && whitelist.contains(((ProxiedPlayer) player).getUniqueId())); } /** diff --git a/SubServers.Sync/velocity/pom.xml b/SubServers.Sync/velocity/pom.xml new file mode 100644 index 00000000..843d783a --- /dev/null +++ b/SubServers.Sync/velocity/pom.xml @@ -0,0 +1,175 @@ + + + 4.0.0 + + net.ME1312.SubServers + SubServers.Sync.Velocity + -PLACEHOLDER + jar + + + + velocity-repo + https://nexus.velocitypowered.com/repository/maven-public/ + + + me1312-repo + https://dev.me1312.net/maven + + + + + + org.apache.logging.log4j + log4j-core + 2.14.1 + provided + + + com.velocitypowered + velocity-api + 3.0.0 + provided + + + net.ME1312.SubServers + SubServers.Sync + ${project.version} + runtime + + + net.ME1312.SubServers + SubServers.Client.Common + ${project.version} + compile + + + com.dosse.upnp + WaifUPnP + 1.1 + compile + + + + + ../../out/compile/target/SubServers.Sync.Velocity + src + + + src + + **/*.java + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-antrun-plugin + + + process + process-resources + + run + + + + + + + + + + verify + verify + + run + + + + + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + SubServers.Sync.Velocity + ../../Artifacts/Maven + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.2-beta-5 + + SubServers.Sync + ../../Artifacts + + ../src/META-INF/MANIFEST.MF + + + jar-with-dependencies + + false + + + + make-assembly + package + + single + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.0.0 + + + generate-sources + + javadoc + + + SubServers.Sync.Velocity + SubServers.Sync.Velocity ${project.version} + protected + ./ + ${basedir}/../../Javadoc/SubServers.Sync.Velocity + ${basedir}/../../Javadoc/SubServers.Sync.Velocity + -Xdoclint:none + + https://dev.me1312.net/jenkins/job/GalaxiEngine/javadoc/GalaxiUtil/ + https://dev.me1312.net/jenkins/job/SubData/javadoc/Client/ + https://jd.velocitypowered.com/3.0.0/ + + true + + net.ME1312.SubServers:SubServers.Client.Common:* + + + + + + + + \ No newline at end of file diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubAddHostEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubAddHostEvent.java new file mode 100644 index 00000000..3822e190 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubAddHostEvent.java @@ -0,0 +1,40 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubServers.Velocity.Library.SubEvent; + +import java.util.UUID; + +/** + * Add Server Event + */ +public class SubAddHostEvent implements SubEvent { + private UUID player; + private String host; + + /** + * Server Add Event + * + * @param player Player Adding Server + * @param host Host to be added + */ + public SubAddHostEvent(UUID player, String host) { + if (Util.isNull(host)) throw new NullPointerException(); + this.player = player; + this.host = host; + } + + /** + * Gets the Host to be Added + * + * @return The Host to be Added + */ + public String getHost() { return host; } + + /** + * Gets the player that triggered the Event + * + * @return The Player that triggered this Event or null if Console + */ + public UUID getPlayer() { return player; } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubAddProxyEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubAddProxyEvent.java new file mode 100644 index 00000000..bf3638c6 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubAddProxyEvent.java @@ -0,0 +1,28 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubServers.Velocity.Library.SubEvent; + +/** + * Proxy Add Event + */ +public class SubAddProxyEvent implements SubEvent { + private String proxy; + + /** + * Proxy Add Event + * + * @param proxy Host Being Added + */ + public SubAddProxyEvent(String proxy) { + if (Util.isNull(proxy)) throw new NullPointerException(); + this.proxy = proxy; + } + + /** + * Gets the Proxy to be Added + * + * @return The Proxy to be Added + */ + public String getProxy() { return proxy; } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubAddServerEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubAddServerEvent.java new file mode 100644 index 00000000..f31bd255 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubAddServerEvent.java @@ -0,0 +1,51 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubServers.Velocity.Library.SubEvent; + +import java.util.UUID; + +/** + * Add Server Event + */ +public class SubAddServerEvent implements SubEvent { + private UUID player; + private String host; + private String server; + + /** + * Server Add Event + * + * @param player Player Adding Server + * @param server Server Starting + */ + public SubAddServerEvent(UUID player, String host, String server) { + if (Util.isNull(server)) throw new NullPointerException(); + this.player = player; + this.host = host; + this.server = server; + } + + /** + * Gets the Server to be Added + * + * @return The Server to be Added + */ + public String getServer() { return server; } + + /** + * Gets the Host of the Server + * + * @return The Host of the Server or null if isn't a SubServer + */ + public String getHost() { + return host; + } + + /** + * Gets the player that triggered the Event + * + * @return The Player that triggered this Event or null if Console + */ + public UUID getPlayer() { return player; } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubCreateEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubCreateEvent.java new file mode 100644 index 00000000..a1d26697 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubCreateEvent.java @@ -0,0 +1,124 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Callback.Callback; +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.Galaxi.Library.Version.Version; +import net.ME1312.SubServers.Client.Common.Network.API.SubServer; +import net.ME1312.SubServers.Velocity.Library.SubEvent; +import net.ME1312.SubServers.Velocity.SubAPI; + +import java.lang.reflect.InvocationTargetException; +import java.util.UUID; + +/** + * Server Create Event + */ +public class SubCreateEvent implements SubEvent { + private UUID player; + private boolean update; + private String host; + private String name; + private String template; + private Version version; + private int port; + + /** + * Server Create Event + * + * @param player Player Creating + * @param host Potential Host + * @param name Server Name + * @param template Server Type + * @param version Server Version + * @param port Server Port Number + */ + public SubCreateEvent(UUID player, String host, String name, String template, Version version, int port, boolean update) { + if (Util.isNull(host, name, template, version, port)) throw new NullPointerException(); + this.player = player; + this.update = update; + this.host = host; + this.name = name; + this.template = template; + this.version = version; + this.port = port; + } + + /** + * Get the Host the SubServer will run on + * + * @return Potential Host + */ + public String getHost() { + return host; + } + + /** + * Get if SubCreator is being run in update mode + * + * @return Update Mode Status + */ + public boolean isUpdate() { + return update; + } + + /** + * Get the Server that's being updated + * + * @param callback Updating Server + */ + public void getUpdatingServer(Callback callback) { + if (!update) { + try { + callback.run(null); + } catch (Throwable e) { + Throwable ew = new InvocationTargetException(e); + ew.printStackTrace(); + } + } else { + SubAPI.getInstance().getSubServer(name, callback); + } + } + + /** + * Get the name the SubServer will use + * + * @return SubServer Name + */ + public String getName() { + return name; + } + + /** + * Get the Template to Use + * + * @return Server Template + */ + public String getTemplate() { + return template; + } + + /** + * Get the Version the Server will use + * + * @return Server Version + */ + public Version getVersion() { + return version; + } + + /** + * Get the Port the Server will use + * + * @return Port Number + */ + public int getPort() { + return port; + } + + /** + * Gets the player that triggered the Event + * + * @return The Player that triggered this Event or null if Console + */ + public UUID getPlayer() { return player; } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubCreatedEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubCreatedEvent.java new file mode 100644 index 00000000..534acf39 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubCreatedEvent.java @@ -0,0 +1,135 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Callback.Callback; +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.Galaxi.Library.Version.Version; +import net.ME1312.SubServers.Client.Common.Network.API.SubServer; +import net.ME1312.SubServers.Velocity.Library.SubEvent; +import net.ME1312.SubServers.Velocity.SubAPI; + +import java.lang.reflect.InvocationTargetException; +import java.util.UUID; + +/** + * Server Created Event + */ +public class SubCreatedEvent implements SubEvent { + private UUID player; + private boolean success; + private boolean update; + private String host; + private String name; + private String template; + private Version version; + private int port; + + /** + * Server Created Event + * + * @param player Player Creating + * @param host Potential Host + * @param name Server Name + * @param template Server Type + * @param version Server Version + * @param port Server Port Number + */ + public SubCreatedEvent(UUID player, String host, String name, String template, Version version, int port, boolean update, boolean success) { + if (Util.isNull(host, name, template, port)) throw new NullPointerException(); + this.player = player; + this.success = success; + this.update = update; + this.host = host; + this.name = name; + this.template = template; + this.version = version; + this.port = port; + } + + /** + * Get the Host the SubServer runs on + * + * @return Host + */ + public String getHost() { + return host; + } + + /** + * Get if SubCreator was being run in update mode + * + * @return Update Mode Status + */ + public boolean wasUpdate() { + return update; + } + + /** + * Get if the operation was a success + * + * @return Success Status + */ + public boolean wasSuccessful() { + return success; + } + + /** + * Get the Server that's being updated + * + * @param callback Updating Server + */ + public void getServer(Callback callback) { + if (!update && !success) { + try { + callback.run(null); + } catch (Throwable e) { + Throwable ew = new InvocationTargetException(e); + ew.printStackTrace(); + } + } else { + SubAPI.getInstance().getSubServer(name, callback); + } + } + + /** + * Get the name the SubServer used + * + * @return SubServer Name + */ + public String getName() { + return name; + } + + /** + * Get the Template that was used + * + * @return Server Template + */ + public String getTemplate() { + return template; + } + + /** + * Get the Version the Server used + * + * @return Server Version + */ + public Version getVersion() { + return version; + } + + /** + * Get the Port the Server used + * + * @return Port Number + */ + public int getPort() { + return port; + } + + /** + * Gets the player that triggered the Event + * + * @return The Player that triggered this Event or null if Console + */ + public UUID getPlayer() { return player; } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubEditServerEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubEditServerEvent.java new file mode 100644 index 00000000..8845be3e --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubEditServerEvent.java @@ -0,0 +1,58 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Container.ContainedPair; +import net.ME1312.Galaxi.Library.Container.Pair; +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.Galaxi.Library.Map.ObjectMapValue; +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubServers.Velocity.Library.SubEvent; + +import java.util.UUID; + +/** + * Server Edit Event + */ +public class SubEditServerEvent implements SubEvent { + private UUID player; + private String server; + private Pair> edit; + + /** + * Server Edit Event + * + * @param player Player Adding Server + * @param server Server to be Edited + * @param edit Edit to make + */ + public SubEditServerEvent(UUID player, String server, Pair edit) { + if (Util.isNull(server, edit)) throw new NullPointerException(); + ObjectMap section = new ObjectMap(); + section.set(".", edit.value()); + this.player = player; + this.server = server; + this.edit = new ContainedPair>(edit.key(), section.get(".")); + } + + /** + * Gets the Server to be Edited + * + * @return The Server to be Edited + */ + public String getServer() { return server; } + + /** + * Gets the player that triggered the Event + * + * @return The Player that triggered this Event or null if Console + */ + public UUID getPlayer() { return player; } + + /** + * Gets the edit to be made + * + * @return Edit to be made + */ + public Pair> getEdit() { + return edit; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubNetworkConnectEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubNetworkConnectEvent.java new file mode 100644 index 00000000..01a213a7 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubNetworkConnectEvent.java @@ -0,0 +1,29 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubData.Client.DataClient; +import net.ME1312.SubServers.Velocity.Library.SubEvent; + +/** + * SubData Network Connect Event + */ +public class SubNetworkConnectEvent implements SubEvent { + private DataClient network; + + /** + * SubData Network Connect Event + */ + public SubNetworkConnectEvent(DataClient network) { + if (Util.isNull(network)) throw new NullPointerException(); + this.network = network; + } + + /** + * Get the SubData network + * + * @return SubData Network + */ + public DataClient getNetwork() { + return network; + } +} \ No newline at end of file diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubNetworkDisconnectEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubNetworkDisconnectEvent.java new file mode 100644 index 00000000..3189cfd1 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubNetworkDisconnectEvent.java @@ -0,0 +1,41 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubData.Client.DataClient; +import net.ME1312.SubData.Client.Library.DisconnectReason; +import net.ME1312.SubServers.Velocity.Library.SubEvent; + +/** + * SubData Network Disconnect Event + */ +public class SubNetworkDisconnectEvent implements SubEvent { + private DataClient network; + private DisconnectReason reason; + + /** + * SubData Network Disconnect Event + */ + public SubNetworkDisconnectEvent(DataClient network, DisconnectReason reason) { + if (Util.isNull(network, reason)) throw new NullPointerException(); + this.network = network; + this.reason = reason; + } + + /** + * Get the SubData network + * + * @return SubData Network + */ + public DataClient getNetwork() { + return network; + } + + /** + * Get the reason the client disconnected + * + * @return Disconnect Reason + */ + public DisconnectReason getReason() { + return reason; + } +} \ No newline at end of file diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubRemoveHostEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubRemoveHostEvent.java new file mode 100644 index 00000000..bca27fd4 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubRemoveHostEvent.java @@ -0,0 +1,40 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubServers.Velocity.Library.SubEvent; + +import java.util.UUID; + +/** + * Remove Host Event + */ +public class SubRemoveHostEvent implements SubEvent { + private UUID player; + private String host; + + /** + * Host Remove Event + * + * @param player Player Adding Host + * @param host Server Starting + */ + public SubRemoveHostEvent(UUID player, String host) { + if (Util.isNull(host)) throw new NullPointerException(); + this.player = player; + this.host = host; + } + + /** + * Gets the Host to be Removed + * + * @return The Host to be Removed + */ + public String getHost() { return host; } + + /** + * Gets the player that triggered the Event + * + * @return The Player that triggered this Event or null if Console + */ + public UUID getPlayer() { return player; } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubRemoveProxyEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubRemoveProxyEvent.java new file mode 100644 index 00000000..497f369d --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubRemoveProxyEvent.java @@ -0,0 +1,28 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubServers.Velocity.Library.SubEvent; + +/** + * Proxy Remove Event + */ +public class SubRemoveProxyEvent implements SubEvent { + private String proxy; + + /** + * Proxy Remove Event + * + * @param proxy Host Being Added + */ + public SubRemoveProxyEvent(String proxy) { + if (Util.isNull(proxy)) throw new NullPointerException(); + this.proxy = proxy; + } + + /** + * Gets the Proxy to be Removed + * + * @return The Proxy to be Removed + */ + public String getProxy() { return proxy; } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubRemoveServerEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubRemoveServerEvent.java new file mode 100644 index 00000000..06246094 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubRemoveServerEvent.java @@ -0,0 +1,51 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubServers.Velocity.Library.SubEvent; + +import java.util.UUID; + +/** + * Remove Server Event + */ +public class SubRemoveServerEvent implements SubEvent { + private UUID player; + private String host; + private String server; + + /** + * Server Remove Event + * + * @param player Player Adding Server + * @param server Server Starting + */ + public SubRemoveServerEvent(UUID player, String host, String server) { + if (Util.isNull(server)) throw new NullPointerException(); + this.player = player; + this.host = host; + this.server = server; + } + + /** + * Gets the Server to be Removed + * + * @return The Server to be Removed + */ + public String getServer() { return server; } + + /** + * Gets the Host of the Server + * + * @return The Host of the Server or null if isn't a SubServer + */ + public String getHost() { + return host; + } + + /** + * Gets the player that triggered the Event + * + * @return The Player that triggered this Event or null if Console + */ + public UUID getPlayer() { return player; } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubSendCommandEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubSendCommandEvent.java new file mode 100644 index 00000000..e93f5fd4 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubSendCommandEvent.java @@ -0,0 +1,51 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubServers.Velocity.Library.SubEvent; + +import java.util.UUID; + +/** + * Send Command Event + */ +public class SubSendCommandEvent implements SubEvent { + private UUID player; + private String server; + private String command; + + /** + * Server Command Event + * + * @param player Player Commanding Server + * @param server Server being Commanded + */ + public SubSendCommandEvent(UUID player, String server, String command) { + if (Util.isNull(server, command)) throw new NullPointerException(); + this.player = player; + this.server = server; + this.command = command; + } + + /** + * Gets the Server Effected + * + * @return The Server Effected + */ + public String getServer() { return server; } + + /** + * Gets the player that triggered the Event + * + * @return The Player that triggered this Event or null if Console + */ + public UUID getPlayer() { return player; } + + /** + * Gets the Command to Send + * + * @return Command to Send + */ + public String getCommand() { + return command; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubStartEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubStartEvent.java new file mode 100644 index 00000000..98c54ec3 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubStartEvent.java @@ -0,0 +1,41 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubServers.Velocity.Library.SubEvent; + +import java.util.UUID; + +/** + * Start Server Event + */ +public class SubStartEvent implements SubEvent { + private boolean cancelled = false; + private UUID player; + private String server; + + /** + * Server Start Event + * + * @param player Player Starting Server + * @param server Server Starting + */ + public SubStartEvent(UUID player, String server) { + if (Util.isNull(server)) throw new NullPointerException(); + this.player = player; + this.server = server; + } + + /** + * Gets the Server Effected + * + * @return The Server Effected + */ + public String getServer() { return server; } + + /** + * Gets the player that triggered the Event + * + * @return The Player that triggered this Event or null if Console + */ + public UUID getPlayer() { return player; } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubStartedEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubStartedEvent.java new file mode 100644 index 00000000..7d35e9ea --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubStartedEvent.java @@ -0,0 +1,31 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubServers.Velocity.Library.SubEvent; + +/** + * Server Started Event + */ +public class SubStartedEvent implements SubEvent { + private String server; + + /** + * Server Started Event
+ * This event can only be called when a SubData connection is made! + * + * @param server Server that Started + */ + public SubStartedEvent(String server) { + if (Util.isNull(server)) throw new NullPointerException(); + this.server = server; + } + + /** + * Gets the Server Effected + * + * @return The Server Effected + */ + public String getServer() { + return server; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubStopEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubStopEvent.java new file mode 100644 index 00000000..34f804d7 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubStopEvent.java @@ -0,0 +1,56 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubServers.Velocity.Library.SubEvent; + +import java.util.UUID; + +/** + * Server Stop Event + */ +public class SubStopEvent implements SubEvent { + private UUID player; + private String server; + private boolean force; + + /** + * Server Stop Event + * + * @param player Player Stopping Server + * @param server Server Stopping + * @param force If it was a Forced Shutdown + */ + public SubStopEvent(UUID player, String server, boolean force) { + if (Util.isNull(server, force)) throw new NullPointerException(); + this.player = player; + this.server = server; + this.force = force; + } + + /** + * Gets the Server Effected + * + * @return The Server Effected + */ + public String getServer() { + return server; + } + + /** + * Gets the player that triggered the Event + * + * @return The Player that triggered this Event or null if Console + */ + public UUID getPlayer() { + return player; + } + + /** + * Gets if it was a forced shutdown + * + * @return Forced Shutdown Status + */ + public boolean isForced() { + return force; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubStoppedEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubStoppedEvent.java new file mode 100644 index 00000000..537db23b --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Event/SubStoppedEvent.java @@ -0,0 +1,28 @@ +package net.ME1312.SubServers.Velocity.Event; + +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubServers.Velocity.Library.SubEvent; + +/** + * Server Shell Exit Event + */ +public class SubStoppedEvent implements SubEvent { + private String server; + + /** + * Server Shell Exit Event + * + * @param server Server that Stopped + */ + public SubStoppedEvent(String server) { + if (Util.isNull(server)) throw new NullPointerException(); + this.server = server; + } + + /** + * Gets the Server Effected + * + * @return The Server Effected + */ + public String getServer() { return server; } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/ExProxy.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/ExProxy.java new file mode 100644 index 00000000..afcc5c93 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/ExProxy.java @@ -0,0 +1,640 @@ +package net.ME1312.SubServers.Velocity; + +import net.ME1312.Galaxi.Library.Config.YAMLConfig; +import net.ME1312.Galaxi.Library.Container.Container; +import net.ME1312.Galaxi.Library.Container.Pair; +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.Galaxi.Library.UniversalFile; +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.Galaxi.Library.Version.Version; +import net.ME1312.SubData.Client.DataClient; +import net.ME1312.SubData.Client.Encryption.AES; +import net.ME1312.SubData.Client.Encryption.DHE; +import net.ME1312.SubData.Client.Encryption.RSA; +import net.ME1312.SubData.Client.Library.DisconnectReason; +import net.ME1312.SubData.Client.SubDataClient; +import net.ME1312.SubServers.Client.Common.Network.Packet.PacketDisconnectPlayer; +import net.ME1312.SubServers.Velocity.Event.*; +import net.ME1312.SubServers.Velocity.Library.Compatibility.ChatColor; +import net.ME1312.SubServers.Velocity.Library.Compatibility.Logger; +import net.ME1312.SubServers.Velocity.Library.ConfigUpdater; +import net.ME1312.SubServers.Velocity.Library.Fallback.FallbackState; +import net.ME1312.SubServers.Velocity.Library.Fallback.SmartFallback; +import net.ME1312.SubServers.Velocity.Library.Metrics; +import net.ME1312.SubServers.Velocity.Network.Packet.PacketExSyncPlayer; +import net.ME1312.SubServers.Velocity.Network.SubProtocol; +import net.ME1312.SubServers.Velocity.Server.CachedPlayer; +import net.ME1312.SubServers.Velocity.Server.ServerData; +import net.ME1312.SubServers.Velocity.Server.SubServerData; + +import com.dosse.upnp.UPnP; +import com.google.gson.Gson; +import com.google.inject.Inject; +import com.velocitypowered.api.command.CommandMeta; +import com.velocitypowered.api.event.PostOrder; +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.player.KickedFromServerEvent; +import com.velocitypowered.api.event.player.ServerPostConnectEvent; +import com.velocitypowered.api.event.player.ServerPreConnectEvent; +import com.velocitypowered.api.event.proxy.*; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.proxy.server.ServerInfo; +import com.velocitypowered.api.proxy.server.ServerPing; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.TextReplacementConfig; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.jetbrains.annotations.NotNull; + +import java.io.*; +import java.net.InetAddress; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.*; +import java.util.concurrent.TimeUnit; + +@Plugin(id = "subservers-sync", name = "SubServers-Sync", authors = "ME1312", version = "2.17.1a/pr1", url = "https://github.com/ME1312/SubServers-2", description = "Dynamically sync player and server connection info over multiple proxy instances") +public class ExProxy { + + HashMap subdata = new HashMap(); + Pair>> lang = null; + public final Map servers = new TreeMap(); + public final HashMap rPlayerLinkS = new HashMap(); + public final HashMap rPlayerLinkP = new HashMap(); + public final HashMap rPlayers = new HashMap(); + private final HashMap fallback = new HashMap(); + + public final org.apache.logging.log4j.Logger out; + public final UniversalFile dir = new UniversalFile(new File(System.getProperty("user.dir"))); + public YAMLConfig config; + public final PluginDescription plugin; + private final ProxyServer proxy; + private final Metrics.Factory metrics; + public final SubAPI api = new SubAPI(this); + public SubProtocol subprotocol; + public final Version version; + + public boolean running = false; + public long lastReload = -1; + private long resetDate = 0; + private boolean reconnect = false; + private boolean posted = false; + + @Inject + private ExProxy(ProxyServer proxy, PluginContainer plugin, Metrics.Factory metrics) throws Exception { + this.proxy = proxy; + this.metrics = metrics; + this.version = Version.fromString((this.plugin = plugin.getDescription()).getVersion().get()); + Util.reflect(Logger.class.getDeclaredField("parent"), null, (this.out = Util.reflect(proxy.getClass().getDeclaredField("logger"), proxy))); + + Logger.get("SubServers").info("Loading SubServers.Sync v" + version.toString() + " Libraries (for Minecraft " + api.getGameVersion()[api.getGameVersion().length - 1] + ")"); + Util.isException(() -> new CachedPlayer((Player) null)); // runs + + UniversalFile dir = new UniversalFile(this.dir, "SubServers"); + dir.mkdir(); + + ConfigUpdater.updateConfig(new UniversalFile(dir, "sync.yml")); + config = new YAMLConfig(new UniversalFile(dir, "sync.yml")); + + SmartFallback.addInspector((player, info) -> { + ServerData server = getData(info.getServerInfo()); + double confidence = 0; + if (server != null) { + if (!server.isHidden()) confidence++; + if (!server.isRestricted()) confidence++; + if (server.getSubData()[0] != null) confidence++; + + if (player != null) { + if (server.canAccess(player)) confidence++; + } + } if (server instanceof SubServerData) { + if (!((SubServerData) server).isRunning()) return null; + } + return confidence; + }); + + subprotocol = SubProtocol.get(); + subprotocol.registerCipher("DHE", DHE.get(128)); + subprotocol.registerCipher("DHE-128", DHE.get(128)); + subprotocol.registerCipher("DHE-192", DHE.get(192)); + subprotocol.registerCipher("DHE-256", DHE.get(256)); + } + + /** + * Load Hosts, Servers, SubServers, and SubData Direct + */ + @Subscribe + public void initialize(ProxyInitializeEvent e) { + try { + running = true; + SmartFallback.dns_forward = config.get().getMap("Settings").getMap("Smart-Fallback", new ObjectMap<>()).getBoolean("DNS-Forward", false); + + resetDate = Calendar.getInstance().getTime().getTime(); + ConfigUpdater.updateConfig(new UniversalFile(dir, "SubServers:sync.yml")); + config.reload(); + + subprotocol.unregisterCipher("AES"); + subprotocol.unregisterCipher("AES-128"); + subprotocol.unregisterCipher("AES-192"); + subprotocol.unregisterCipher("AES-256"); + subprotocol.unregisterCipher("RSA"); + + api.name = config.get().getMap("Settings").getMap("SubData").getString("Name", null); + + if (config.get().getMap("Settings").getMap("SubData").getRawString("Password", "").length() > 0) { + subprotocol.registerCipher("AES", new AES(128, config.get().getMap("Settings").getMap("SubData").getRawString("Password"))); + subprotocol.registerCipher("AES-128", new AES(128, config.get().getMap("Settings").getMap("SubData").getRawString("Password"))); + subprotocol.registerCipher("AES-192", new AES(192, config.get().getMap("Settings").getMap("SubData").getRawString("Password"))); + subprotocol.registerCipher("AES-256", new AES(256, config.get().getMap("Settings").getMap("SubData").getRawString("Password"))); + + Logger.get("SubData").info("AES Encryption Available"); + } + if (new UniversalFile(dir, "SubServers:subdata.rsa.key").exists()) { + try { + subprotocol.registerCipher("RSA", new RSA(new UniversalFile(dir, "SubServers:subdata.rsa.key"))); + Logger.get("SubData").info("RSA Encryption Available"); + } catch (Exception x) { + x.printStackTrace(); + } + } + + reconnect = true; + Logger.get("SubData").info(""); + Logger.get("SubData").info("Connecting to /" + config.get().getMap("Settings").getMap("SubData").getRawString("Address", "127.0.0.1:4391")); + connect(Logger.get("SubData"), null); + + if (!posted) { + posted = true; + post(); + } + } catch (IOException x) { + x.printStackTrace(); + } + } + + private void connect(java.util.logging.Logger log, Pair disconnect) throws IOException { + int reconnect = config.get().getMap("Settings").getMap("SubData").getInt("Reconnect", 60); + if (disconnect == null || (this.reconnect && reconnect > 0 && disconnect.key() != DisconnectReason.PROTOCOL_MISMATCH && disconnect.key() != DisconnectReason.ENCRYPTION_MISMATCH)) { + long reset = resetDate; + Timer timer = new Timer("SubServers.Sync::SubData_Reconnect_Handler"); + if (disconnect != null) log.info("Attempting reconnect in " + reconnect + " seconds"); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + try { + if (reset == resetDate && (subdata.getOrDefault(0, null) == null || subdata.get(0).isClosed())) { + SubDataClient open = subprotocol.open(InetAddress.getByName(config.get().getMap("Settings").getMap("SubData").getRawString("Address", "127.0.0.1:4391").split(":")[0]), + Integer.parseInt(config.get().getMap("Settings").getMap("SubData").getRawString("Address", "127.0.0.1:4391").split(":")[1])); + + if (subdata.getOrDefault(0, null) != null) subdata.get(0).reconnect(open); + subdata.put(0, open); + } + timer.cancel(); + } catch (IOException e) { + Logger.get("SubData").info("Connection was unsuccessful, retrying in " + reconnect + " seconds"); + } + } + }, (disconnect == null)?0:TimeUnit.SECONDS.toMillis(reconnect), TimeUnit.SECONDS.toMillis(reconnect)); + } + } + + private void post() { + if (!config.get().getMap("Settings").getRawStringList("Disabled-Overrides", Collections.emptyList()).contains("/server")) + proxy.getCommandManager().register("server", new SubCommand.BungeeServer(this)); + if (!config.get().getMap("Settings").getRawStringList("Disabled-Overrides", Collections.emptyList()).contains("/glist")) + proxy.getCommandManager().register("glist", new SubCommand.BungeeList(this)); + + proxy.getCommandManager().register("subservers", new SubCommand(this), "subserver", "sub"); + + if (config.get().getMap("Settings").getMap("Smart-Fallback", new ObjectMap<>()).getBoolean("Enabled", true)) + proxy.getEventManager().register(this, new SmartFallback(config.get().getMap("Settings").getMap("Smart-Fallback", new ObjectMap<>()))); + + metrics.make(this, 11953); + new Timer("SubServers.Sync::Routine_Update_Check").schedule(new TimerTask() { + @SuppressWarnings("unchecked") + @Override + public void run() { + try { + ObjectMap tags = new ObjectMap(new Gson().fromJson("{\"tags\":" + Util.readAll(new BufferedReader(new InputStreamReader(new URL("https://api.github.com/repos/ME1312/SubServers-2/git/refs/tags").openStream(), Charset.forName("UTF-8")))) + '}', Map.class)); + List versions = new LinkedList(); + + Version updversion = version; + int updcount = 0; + for (ObjectMap tag : tags.getMapList("tags")) versions.add(Version.fromString(tag.getString("ref").substring(10))); + Collections.sort(versions); + for (Version version : versions) { + if (version.compareTo(updversion) > 0) { + updversion = version; + updcount++; + } + } + if (updcount > 0) Logger.get("SubServers").info("SubServers.Sync v" + updversion + " is available. You are " + updcount + " version" + ((updcount == 1)?"":"s") + " behind."); + } catch (Exception e) {} + } + }, 0, TimeUnit.DAYS.toMillis(2)); + + int rpec_i = config.get().getMap("Settings").getInt("RPEC-Check-Interval", 300); + int rpec_s = rpec_i - new Random().nextInt((rpec_i / 3) + 1); + new Timer("SubServers.Sync::RemotePlayer_Error_Checking").schedule(new TimerTask() { + @Override + public void run() { + if (api.getName() != null && api.getSubDataNetwork()[0] != null && !api.getSubDataNetwork()[0].isClosed()) { + api.getProxy(api.getName(), proxy -> { + synchronized (rPlayers) { + ArrayList add = new ArrayList(); + for (Player player : ExProxy.this.proxy.getAllPlayers()) { + if (!rPlayers.containsKey(player.getUniqueId())) { // Add players that don't exist + CachedPlayer p = new CachedPlayer(player); + rPlayerLinkP.put(player.getUniqueId(), p.getProxyName().toLowerCase()); + rPlayers.put(player.getUniqueId(), p); + ServerInfo server = player.getCurrentServer().map(ServerConnection::getServerInfo).orElse(null); + if (servers.containsKey(server)) rPlayerLinkS.put(player.getUniqueId(), servers.get(server)); + add.add(p); + } + } + ArrayList remove = new ArrayList(); + for (Pair player : proxy.getPlayers()) { // Remove players that shouldn't exist + if (!ExProxy.this.proxy.getPlayer(player.value()).isPresent()) { + remove.add(rPlayers.get(player.value())); + rPlayerLinkS.remove(player.value()); + rPlayerLinkP.remove(player.value()); + rPlayers.remove(player.value()); + } + } + for (UUID player : Util.getBackwards(rPlayerLinkP, api.getName().toLowerCase())) { // Remove players that shouldn't exist (internally) + if (!ExProxy.this.proxy.getPlayer(player).isPresent()) { + rPlayerLinkS.remove(player); + rPlayerLinkP.remove(player); + rPlayers.remove(player); + } + } + LinkedList packets = new LinkedList(); // Compile change data for external proxies + if (add.size() > 0) packets.add(new PacketExSyncPlayer(true, add.toArray(new CachedPlayer[0]))); + if (remove.size() > 0) packets.add(new PacketExSyncPlayer(false, remove.toArray(new CachedPlayer[0]))); + if (packets.size() > 0) { + PacketExSyncPlayer[] packet = packets.toArray(new PacketExSyncPlayer[0]); + if (api.getSubDataNetwork()[0] != null) { + ((SubDataClient) api.getSubDataNetwork()[0]).sendPacket(packet); + } + } + } + }); + } + } + }, TimeUnit.SECONDS.toMillis(rpec_s), TimeUnit.SECONDS.toMillis(rpec_i)); + } + + /** + * Port-forward Listeners + */ + @Subscribe + public void registerListener(ListenerBoundEvent e) { + if (UPnP.isUPnPAvailable()) { + if (config.get().getMap("Settings").getMap("UPnP", new ObjectMap()).getBoolean("Forward-Proxy", true)) { + UPnP.openPortTCP(e.getAddress().getPort()); + } + } else { + out.warn("UPnP is currently unavailable. Ports may not be automatically forwarded on this device."); + } + } + + /** + * Get the Proxy Instance + * + * @return Proxy Instance + */ + @SuppressWarnings("deprecation") + public static ProxyServer getInstance() { + return SubAPI.getInstance().getInternals().proxy; + } + + /** + * Get Internal Server Data + * + * @param server ServerInfo + * @return ServerData + */ + public ServerData getData(ServerInfo server) { + return (server == null)? null : servers.getOrDefault(server, null); + } + + /** + * Simulate Proxy Reload + */ + @Subscribe + public void reload(ProxyReloadEvent e) { + shutdown(null); + initialize(null); + } + + /** + * Un-port-forward Listeners + */ + @Subscribe + public void unregisterListener(ListenerCloseEvent e) { + if (UPnP.isUPnPAvailable()) { + if (UPnP.isMappedTCP(e.getAddress().getPort())) UPnP.closePortTCP(e.getAddress().getPort()); + } + } + + /** + * Reset all changes made by initialize() + * + * @see ExProxy#initialize(ProxyInitializeEvent) + */ + @Subscribe + public void shutdown(ProxyShutdownEvent e) { + try { + running = false; + Logger.get("SubServers").info("Removing synced data"); + servers.clear(); + + reconnect = false; + ArrayList tmp = new ArrayList(); + tmp.addAll(subdata.values()); + for (SubDataClient client : tmp) if (client != null) { + client.close(); + Util.isException(client::waitFor); + } + subdata.clear(); + subdata.put(0, null); + + rPlayerLinkS.clear(); + rPlayerLinkP.clear(); + rPlayers.clear(); + } catch (Exception x) { + x.printStackTrace(); + } + } + + @Subscribe(order = PostOrder.LAST) + public void ping(ProxyPingEvent e) { + int offline = 0; + RegisteredServer[] overrides; + if ((overrides = SmartFallback.getForcedHosts(e.getConnection())) != null || (overrides = SmartFallback.getDNS(e.getConnection())) != null) { + for (RegisteredServer server : overrides) + if (server instanceof SubServerData && !((SubServerData) server).isRunning()) offline++; + + if (offline >= overrides.length) { + e.setPing(new ServerPing(e.getPing().getVersion(), e.getPing().getPlayers().orElse(null), ChatColor.convertColor(api.getLang("SubServers", "Bungee.Ping.Offline")), + e.getPing().getFavicon().orElse(null), e.getPing().getModinfo().orElse(null))); + } + } else { + for (String name : proxy.getConfiguration().getAttemptConnectionOrder()) { + ServerData server = proxy.getServer(name).map(RegisteredServer::getServerInfo).map(this::getData).orElse(null); + if (server instanceof SubServerData && !((SubServerData) server).isRunning()) offline++; + } + + if (offline >= proxy.getConfiguration().getAttemptConnectionOrder().size()) { + e.setPing(new ServerPing(e.getPing().getVersion(), e.getPing().getPlayers().orElse(null), ChatColor.convertColor(api.getLang("SubServers", "Bungee.Ping.Offline")), + e.getPing().getFavicon().orElse(null), e.getPing().getModinfo().orElse(null))); + } + } + } + + @Subscribe(order = PostOrder.FIRST) + public void login(LoginEvent e) { + out.info("UUID of player " + e.getPlayer().getGameProfile().getName() + " is " + e.getPlayer().getUniqueId()); + if (rPlayers.containsKey(e.getPlayer().getUniqueId())) { + Logger.get("SubServers").warning(e.getPlayer().getGameProfile().getName() + " connected, but already had a database entry"); + CachedPlayer player = rPlayers.get(e.getPlayer().getUniqueId()); + if (player.getProxyName() != null && player.getProxyName().equalsIgnoreCase(api.getName())) { + proxy.getPlayer(player.getUniqueId()).ifPresent(p -> p.disconnect(Component.translatable("velocity.error.already-connected-proxy", NamedTextColor.RED))); + } else { + ((SubDataClient) api.getSubDataNetwork()[0]).sendPacket(new PacketDisconnectPlayer(new UUID[]{ player.getUniqueId() }, LegacyComponentSerializer.legacySection().serialize(Component.translatable("velocity.error.already-connected-proxy", NamedTextColor.RED)))); + } + } + } + + @Subscribe(order = PostOrder.LAST) + public void validate(ServerPreConnectEvent e) { + if (e.getPlayer().isActive()) { + ServerData server = getData(e.getOriginalServer().getServerInfo()); + if (server == null || !server.canAccess(e.getPlayer())) { + if (!e.getPlayer().getCurrentServer().isPresent() || fallback.containsKey(e.getPlayer().getUniqueId())) { + if (!fallback.containsKey(e.getPlayer().getUniqueId()) || fallback.get(e.getPlayer().getUniqueId()).names.contains(e.getOriginalServer().getServerInfo().getName())) { + Component text = ChatColor.convertColor(api.getLang("SubServers", "Bungee.Restricted")); + KickedFromServerEvent kick = new KickedFromServerEvent(e.getPlayer(), e.getOriginalServer(), text, true, KickedFromServerEvent.DisconnectPlayer.create(text)); + fallback(kick); + if (e.getPlayer().getCurrentServer().isPresent()) e.setResult(ServerPreConnectEvent.ServerResult.denied()); + if (kick.getResult() instanceof KickedFromServerEvent.DisconnectPlayer) e.getPlayer().disconnect(text); + else if (kick.getResult() instanceof KickedFromServerEvent.RedirectPlayer) e.getPlayer().createConnectionRequest(((KickedFromServerEvent.RedirectPlayer) kick.getResult()).getServer()).fireAndForget(); + } + } else { + e.getPlayer().sendMessage(ChatColor.convertColor(api.getLang("SubServers", "Bungee.Restricted"))); + e.setResult(ServerPreConnectEvent.ServerResult.denied()); + } + } else if (e.getPlayer().getCurrentServer().isPresent() && !fallback.containsKey(e.getPlayer().getUniqueId()) && server instanceof SubServerData && !((SubServerData) server).isRunning()) { + e.getPlayer().sendMessage(ChatColor.convertColor(api.getLang("SubServers", "Bungee.Server.Offline"))); + e.setResult(ServerPreConnectEvent.ServerResult.denied()); + } + + if (fallback.containsKey(e.getPlayer().getUniqueId())) { + FallbackState state = fallback.get(e.getPlayer().getUniqueId()); + if (state.names.contains(e.getOriginalServer().getServerInfo().getName())) { + state.remove(e.getOriginalServer().getServerInfo().getName()); + } else if (e.getPlayer().getCurrentServer().isPresent()) { + fallback.remove(e.getPlayer().getUniqueId()); + } + } + } else { + e.setResult(ServerPreConnectEvent.ServerResult.denied()); + } + } + + @SuppressWarnings("UnstableApiUsage") + @Subscribe(order = PostOrder.LAST) + public void connected(ServerPostConnectEvent e) { + ServerData server = getData(e.getPlayer().getCurrentServer().get().getServerInfo()); + if (server != null) { + if (e.getPlayer().isActive()) { + synchronized (rPlayers) { + ObjectMap raw = CachedPlayer.translate(e.getPlayer()); + raw.set("server", server.getName()); + CachedPlayer player = new CachedPlayer(raw); + rPlayerLinkP.put(player.getUniqueId(), player.getProxyName().toLowerCase()); + rPlayers.put(player.getUniqueId(), player); + rPlayerLinkS.put(player.getUniqueId(), server); + if (api.getSubDataNetwork()[0] != null) { + ((SubDataClient) api.getSubDataNetwork()[0]).sendPacket(new PacketExSyncPlayer(true, player)); + } + } + } + + + if (fallback.containsKey(e.getPlayer().getUniqueId())) { + fallback.get(e.getPlayer().getUniqueId()).done(() -> { + if (fallback.containsKey(e.getPlayer().getUniqueId()) && e.getPlayer().getCurrentServer().get().getServerInfo().getName().equals(server.get().getName())) { + fallback.remove(e.getPlayer().getUniqueId()); + } + }, proxy.getConfiguration().getConnectTimeout() + 500); + } + } + } + + @Subscribe(order = PostOrder.LAST) + public void fallback(KickedFromServerEvent e) { + if (e.getPlayer().isActive() && config.get().getMap("Settings").getMap("Smart-Fallback", new ObjectMap<>()).getBoolean("Fallback", true)) { + FallbackState state; + boolean init = !fallback.containsKey(e.getPlayer().getUniqueId()); + if (init) { + Map map = SmartFallback.getFallbackServers(e.getPlayer()); + map.remove(e.getServer().getServerInfo().getName()); + state = new FallbackState(e.getPlayer().getUniqueId(), map, e.getServerKickReason().orElse(Component.text(""))); + if (e.getPlayer().getCurrentServer().isPresent()) + state.remove(e.getPlayer().getCurrentServer().get().getServerInfo().getName()); + } else { + state = fallback.get(e.getPlayer().getUniqueId()); + LinkedList tmp = new LinkedList<>(state.servers); + for (RegisteredServer server : tmp) if (server.getServerInfo().equals(e.getServer().getServerInfo())) + state.remove(server); + } + + RegisteredServer rs = e.getServer(); + ServerData server = getData(rs.getServerInfo()); + e.getPlayer().sendMessage(ChatColor.convertColor(api.getLang("SubServers", "Bungee.Feature.Smart-Fallback") + .replace("$str$", (server != null)? server.getDisplayName() : rs.getServerInfo().getName())) + .replaceText(TextReplacementConfig.builder().match("\\$msg\\$").replacement(e.getServerKickReason().orElse(Component.text(""))).build()) + ); + + if (state.servers.isEmpty()) { + if (e.getPlayer().getCurrentServer().isPresent()) { + fallback.remove(e.getPlayer().getUniqueId()); + rs = e.getPlayer().getCurrentServer().get().getServer(); + server = getData(rs.getServerInfo()); + e.setResult(KickedFromServerEvent.Notify.create(ChatColor.convertColor(api.getLang("SubServers", "Bungee.Feature.Smart-Fallback.Result").replace("$str$", (server != null)? server.getDisplayName() : rs.getServerInfo().getName())))); + } else { + e.setResult(KickedFromServerEvent.DisconnectPlayer.create(state.reason)); + } + } else { + if (init) fallback.put(e.getPlayer().getUniqueId(), state); + rs = state.servers.getFirst(); + server = getData(rs.getServerInfo()); + + e.setResult(KickedFromServerEvent.RedirectPlayer.create(rs, ChatColor.convertColor(api.getLang("SubServers", "Bungee.Feature.Smart-Fallback.Result").replace("$str$", (server != null)? server.getDisplayName() : rs.getServerInfo().getName())))); + } + } + } + + @Subscribe(order = PostOrder.LAST) + public void disconnected(DisconnectEvent e) { + UUID id = e.getPlayer().getUniqueId(); + fallback.remove(id); + SubCommand.permitted.remove(id); + + synchronized (rPlayers) { + if (rPlayers.containsKey(id) && (!rPlayerLinkP.containsKey(id) || rPlayerLinkP.get(id).equalsIgnoreCase(api.getName()))) { + CachedPlayer player = rPlayers.get(id); + rPlayerLinkS.remove(id); + rPlayerLinkP.remove(id); + rPlayers.remove(id); + + if (api.getSubDataNetwork()[0] != null) { + ((SubDataClient) api.getSubDataNetwork()[0]).sendPacket(new PacketExSyncPlayer(false, player)); + } + } + } + } + + @SuppressWarnings("unchecked") + private Map getSubDataAsMap(net.ME1312.SubServers.Client.Common.Network.API.Server server) { + HashMap map = new HashMap(); + ObjectMap subdata = new ObjectMap((Map) server.getRaw().getObject("subdata")); + for (Integer channel : subdata.getKeys()) map.put(channel, subdata.getUUID(channel)); + return map; + } + + @Subscribe(order = PostOrder.FIRST) + public void add(SubAddServerEvent e) { + api.getServer(e.getServer(), server -> { + if (server != null) { + ServerData data; + if (server instanceof net.ME1312.SubServers.Client.Common.Network.API.SubServer) { + data = new SubServerData(server.getSignature(), server.getName(), server.getDisplayName(), server.getAddress(), + getSubDataAsMap(server), server.getMotd(), server.isHidden(), server.isRestricted(), server.getWhitelist(), ((net.ME1312.SubServers.Client.Common.Network.API.SubServer) server).isRunning()); + Logger.get("SubServers").info("Added SubServer: " + e.getServer()); + } else { + data = new ServerData(server.getSignature(), server.getName(), server.getDisplayName(), server.getAddress(), + getSubDataAsMap(server), server.getMotd(), server.isHidden(), server.isRestricted(), server.getWhitelist()); + Logger.get("SubServers").info("Added Server: " + e.getServer()); + } + ServerData old = servers.put(data.get(), data); + if (old != null && proxy.getServer(data.getName()).isPresent()) + proxy.unregisterServer(old.get()); + proxy.registerServer(data.get()); + + } else System.out.println("PacketDownloadServerInfo(" + e.getServer() + ") returned with an invalid response"); + }); + } + + public Boolean merge(net.ME1312.SubServers.Client.Common.Network.API.Server server) { + ServerData current = proxy.getServer(server.getName()).map(RegisteredServer::getServerInfo).map(this::getData).orElse(null); + if (server instanceof net.ME1312.SubServers.Client.Common.Network.API.SubServer || !(current instanceof SubServerData)) { + if (current == null || !current.getSignature().equals(server.getSignature())) { + ServerData data; + if (server instanceof net.ME1312.SubServers.Client.Common.Network.API.SubServer) { + data = new SubServerData(server.getSignature(), server.getName(), server.getDisplayName(), server.getAddress(), + getSubDataAsMap(server), server.getMotd(), server.isHidden(), server.isRestricted(), server.getWhitelist(), ((net.ME1312.SubServers.Client.Common.Network.API.SubServer) server).isRunning()); + } else { + data = new ServerData(server.getSignature(), server.getName(), server.getDisplayName(), server.getAddress(), + getSubDataAsMap(server), server.getMotd(), server.isHidden(), server.isRestricted(), server.getWhitelist()); + } + ServerData old = servers.put(data.get(), data); + if (old != null && proxy.getServer(data.getName()).isPresent()) + proxy.unregisterServer(old.get()); + proxy.registerServer(data.get()); + + Logger.get("SubServers").info("Added "+((server instanceof net.ME1312.SubServers.Client.Common.Network.API.SubServer)?"Sub":"")+"Server: " + server.getName()); + return true; + } else { + if (server instanceof net.ME1312.SubServers.Client.Common.Network.API.SubServer) { + if (((net.ME1312.SubServers.Client.Common.Network.API.SubServer) server).isRunning() != ((SubServerData) current).isRunning()) + ((SubServerData) current).setRunning(((net.ME1312.SubServers.Client.Common.Network.API.SubServer) server).isRunning()); + } + if (!server.getMotd().equals(current.getMotd())) + current.setMotd(server.getMotd()); + if (server.isHidden() != current.isHidden()) + current.setHidden(server.isHidden()); + if (server.isRestricted() != current.isRestricted()) + current.setRestricted(server.isRestricted()); + if (!server.getDisplayName().equals(current.getDisplayName())) + current.setDisplayName(server.getDisplayName()); + + Logger.get("SubServers").info("Re-added "+((server instanceof net.ME1312.SubServers.Client.Common.Network.API.SubServer)?"Sub":"")+"Server: " + server.getName()); + return false; + } + } + return null; + } + + @Subscribe(order = PostOrder.FIRST) + public void start(SubStartEvent e) { + ServerData server = proxy.getServer(e.getServer()).map(RegisteredServer::getServerInfo).map(this::getData).orElse(null); + if (server instanceof SubServerData) ((SubServerData) server).setRunning(true); + } + + @Subscribe(order = PostOrder.FIRST) + public void stop(SubStoppedEvent e) { + ServerData server = proxy.getServer(e.getServer()).map(RegisteredServer::getServerInfo).map(this::getData).orElse(null); + if (server instanceof SubServerData) ((SubServerData) server).setRunning(false); + } + + @Subscribe(order = PostOrder.FIRST) + public void remove(SubRemoveServerEvent e) { + ServerData server = proxy.getServer(e.getServer()).map(RegisteredServer::getServerInfo).map(this::getData).orElse(null); + if (server != null) { + servers.remove(server.get()); + proxy.unregisterServer(server.get()); + Logger.get("SubServers").info("Removed Server: " + e.getServer()); + } + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Compatibility/ChatColor.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Compatibility/ChatColor.java new file mode 100644 index 00000000..2b1ba6b2 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Compatibility/ChatColor.java @@ -0,0 +1,102 @@ +package net.ME1312.SubServers.Velocity.Library.Compatibility; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.util.Arrays; + +/** + * Color Code Converter Enum + */ +public enum ChatColor { + AQUA('b'), + BLACK('0'), + BLUE('9'), + BOLD('l'), + DARK_AQUA('3'), + DARK_BLUE('1'), + DARK_GRAY('8'), + DARK_GREEN('2'), + DARK_PURPLE('5'), + DARK_RED('4'), + GOLD('6'), + GRAY('7'), + GREEN('a'), + ITALIC('o'), + LIGHT_PURPLE('d'), + MAGIC('k'), + RED('c'), + RESET('r'), + STRIKETHROUGH('m'), + UNDERLINE('n'), + WHITE('f'), + YELLOW('e'); + + private final Character minecraft; + ChatColor(Character minecraft) { + this.minecraft = minecraft; + } + + /** + * Get this color as a Minecraft Color Code + * + * @return Minecraft Color Code + */ + public String asMinecraftCode() { + return new String(new char[]{'\u00A7', minecraft}); + } + + @Override + public String toString() { + return asMinecraftCode(); + } + + /** + * Parse Minecraft color codes starting with character + * + * @param character Character + * @param str String to parse + * @return Minecraft colored string + */ + public static String parseColor(char character, String str) { + for (ChatColor color : Arrays.asList(ChatColor.values())) { + str = str.replace(new String(new char[]{character, color.minecraft}), color.asMinecraftCode()); + } + return str; + } + + /** + * Convert Minecraft color codes to Sponge Text + * + * @param str Minecraft colored string + * @return Sponge Text + */ + public static TextComponent convertColor(String str) { + return LegacyComponentSerializer.legacySection().deserialize(str); + } + + /** + * Convert Minecraft color codes starting with character to Sponge Text + * + * @param character Character + * @param str String to parse + * @return Sponge Text + */ + public static TextComponent convertColor(char character, String str) { + return LegacyComponentSerializer.legacy(character).deserialize(str); + } + + /** + * Removes all Minecraft color codes from a string + * + * @param str String to parse + * @return String without color + */ + public static String stripColor(String str) { + for (ChatColor color : Arrays.asList(ChatColor.values())) { + str = str.replace(color.asMinecraftCode(), ""); + } + return str; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Compatibility/Logger.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Compatibility/Logger.java new file mode 100644 index 00000000..bbda1306 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Compatibility/Logger.java @@ -0,0 +1,50 @@ +package net.ME1312.SubServers.Velocity.Library.Compatibility; + +import java.util.HashMap; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +/** + * Logger Compatibility Class + */ +public class Logger { + private static final HashMap existing = new HashMap(); + private static org.apache.logging.log4j.Logger parent; + + /** + * Get a logger + * + * @param prefix Prefix + * @return Logger + */ + public static java.util.logging.Logger get(String prefix) { + if (!existing.keySet().contains(prefix)) { + java.util.logging.Logger log = java.util.logging.Logger.getAnonymousLogger(); + log.setUseParentHandlers(false); + log.addHandler(new Handler() { + @Override + public void publish(LogRecord record) { + String message = prefix + " > " + record.getMessage(); + if (record.getLevel() == Level.INFO) { + parent.info(message, record.getParameters()); + } else if (record.getLevel() == Level.WARNING) { + parent.warn(message, record.getParameters()); + } else if (record.getLevel() == Level.SEVERE) { + parent.error(message, record.getParameters()); + } else if (record.getLevel().intValue() < Level.FINE.intValue()) { + parent.trace(message, record.getParameters()); + } else if (record.getLevel().intValue() < Level.INFO.intValue()) { + parent.debug(message, record.getParameters()); + } + } + + @Override + public void flush() {} + public void close() {} + }); + existing.put(prefix, log); + } + return existing.get(prefix); + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/ConfigUpdater.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/ConfigUpdater.java new file mode 100644 index 00000000..1be00023 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/ConfigUpdater.java @@ -0,0 +1,114 @@ +package net.ME1312.SubServers.Velocity.Library; + +import net.ME1312.Galaxi.Library.Config.YAMLConfig; +import net.ME1312.Galaxi.Library.Config.YAMLSection; +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.Galaxi.Library.Version.Version; +import net.ME1312.SubServers.Velocity.Library.Compatibility.Logger; +import net.ME1312.SubServers.Velocity.SubAPI; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * SubServers Configuration Updater + */ +public class ConfigUpdater { + private static final Version UNSIGNED = new Version(new SimpleDateFormat("yy'w'ww'zz'").format(Calendar.getInstance().getTime())); + + /** + * Update SubServers' config.yml + * + * @param file File to bring up-to-date + */ + public static void updateConfig(File file) throws IOException { + YAMLConfig config = new YAMLConfig(file); + YAMLSection existing = config.get().clone(); + YAMLSection updated = existing.clone(); + YAMLSection rewritten = new YAMLSection(); + + Version was = existing.getMap("Settings", new ObjectMap<>()).getVersion("Version", new Version(0)); + Version now = SubAPI.getInstance().getPluginBuild(); + + int i = 0; + if (now == null) now = UNSIGNED; + if (!existing.contains("Settings") || !existing.getMap("Settings").contains("Version")) { + + i++; + Logger.get("SubServers").info("Created ./SubServers/sync.yml"); + } else { + if (was.compareTo(new Version("19w17a")) <= 0) { + + i++; + } if (was.compareTo(new Version("20w34a")) <= 0) { + if (existing.getMap("Settings", new YAMLSection()).contains("Smart-Fallback") && existing.getMap("Settings").isBoolean("Smart-Fallback")) { + YAMLSection smart_fallback = new YAMLSection(); + smart_fallback.set("Enabled", existing.getMap("Settings").getBoolean("Smart-Fallback")); + smart_fallback.set("Fallback", existing.getMap("Settings").getBoolean("Smart-Fallback")); + updated.getMap("Settings").set("Smart-Fallback", smart_fallback); + } + if (existing.getMap("Settings", new YAMLSection()).contains("Override-Bungee-Commands") && existing.getMap("Settings").isBoolean("Override-Bungee-Commands")) { + List overrides = new LinkedList<>(); + if (!existing.getMap("Settings").getBoolean("Override-Bungee-Commands")) { + overrides.add("/server"); + overrides.add("/glist"); + } + updated.getMap("Settings").set("Disabled-Overrides", overrides); + } + + existing = updated.clone(); + i++; + }// if (was.compareTo(new Version("99w99a")) <= 0) { + // // do something + // i++ + //} + + if (i > 0) Logger.get("SubServers").info("Updated ./SubServers/sync.yml (" + i + " pass" + ((i != 1)?"es":"") + ")"); + } + + if (i > 0) { + YAMLSection settings = new YAMLSection(); + settings.set("Version", ((now.compareTo(was) <= 0)?was:now).toString()); + if (updated.getMap("Settings", new YAMLSection()).contains("RPEC-Check-Interval")) settings.set("RPEC-Check-Interval", updated.getMap("Settings").getRawString("RPEC-Check-Interval")); + settings.set("Disabled-Overrides", updated.getMap("Settings", new YAMLSection()).getRawStringList("Disabled-Overrides", Collections.emptyList())); + + YAMLSection smart_fallback = new YAMLSection(); + smart_fallback.set("Enabled", updated.getMap("Settings", new YAMLSection()).getMap("Smart-Fallback", new YAMLSection()).getBoolean("Enabled", true)); + smart_fallback.set("Fallback", updated.getMap("Settings", new YAMLSection()).getMap("Smart-Fallback", new YAMLSection()).getBoolean("Fallback", true)); + smart_fallback.set("Reconnect", updated.getMap("Settings", new YAMLSection()).getMap("Smart-Fallback", new YAMLSection()).getBoolean("Reconnect", false)); + smart_fallback.set("DNS-Forward", updated.getMap("Settings", new YAMLSection()).getMap("Smart-Fallback", new YAMLSection()).getBoolean("DNS-Forward", false)); + settings.set("Smart-Fallback", smart_fallback); + + YAMLSection upnp = new YAMLSection(); + upnp.set("Forward-Proxy", updated.getMap("Settings", new YAMLSection()).getMap("UPnP", new YAMLSection()).getBoolean("Forward-Proxy", true)); + settings.set("UPnP", upnp); + + YAMLSection subdata = new YAMLSection(); + if (updated.getMap("Settings", new YAMLSection()).getMap("SubData", new YAMLSection()).contains("Name")) subdata.set("Name", updated.getMap("Settings").getMap("SubData").getRawString("Name")); + subdata.set("Address", updated.getMap("Settings", new YAMLSection()).getMap("SubData", new YAMLSection()).getRawString("Address", "127.0.0.1:4391")); + if (updated.getMap("Settings", new YAMLSection()).getMap("SubData", new YAMLSection()).contains("Password")) subdata.set("Password", updated.getMap("Settings").getMap("SubData").getRawString("Password")); + if (updated.getMap("Settings", new YAMLSection()).getMap("SubData", new YAMLSection()).contains("Reconnect")) subdata.set("Reconnect", updated.getMap("Settings").getMap("SubData").getInt("Reconnect")); + settings.set("SubData", subdata); + + rewritten.set("Settings", settings); + + /* + YAMLSection sync = new YAMLSection(); + sync.set("Disabled-Commands", updated.getMap("Settings", new YAMLSection()).getBoolean("Disabled-Commands", false)); + sync.set("Forced-Hosts", updated.getMap("Settings", new YAMLSection()).getBoolean("Forced-Hosts", true)); + sync.set("Motd", updated.getMap("Settings", new YAMLSection()).getBoolean("Motd", false)); + sync.set("Player-Limit", updated.getMap("Settings", new YAMLSection()).getBoolean("Player-Limit", false)); + sync.set("Server-Priorities", updated.getMap("Settings", new YAMLSection()).getBoolean("Server-Priorities", true)); + + rewritten.set("Sync", sync); */ + + config.set(rewritten); + config.save(); + } + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Fallback/FallbackInspector.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Fallback/FallbackInspector.java new file mode 100644 index 00000000..f7f5309f --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Fallback/FallbackInspector.java @@ -0,0 +1,19 @@ +package net.ME1312.SubServers.Velocity.Library.Fallback; + +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.server.RegisteredServer; + +/** + * Fallback Server Inspector Layout Class + */ +public interface FallbackInspector { + + /** + * Inspect a fallback server and modify its confidence score + * + * @param player Player that requested (may be null) + * @param server Server to inspect + * @return A Positive Value to add points, a Negative Value to subtract points, a Null Value to invalidate the server, or a Zero Value to do nothing + */ + Double inspect(Player player, RegisteredServer server); +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Fallback/FallbackState.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Fallback/FallbackState.java new file mode 100644 index 00000000..b3f59eaa --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Fallback/FallbackState.java @@ -0,0 +1,72 @@ +package net.ME1312.SubServers.Velocity.Library.Fallback; + +import com.velocitypowered.api.proxy.server.RegisteredServer; +import net.kyori.adventure.text.Component; + +import java.util.*; + +/** + * Fallback Player State Class + */ +public class FallbackState { + public final UUID player; + public final LinkedList names; + public final LinkedList servers; + public final Component reason; + private Map map; + private Timer finish; + + /** + * Smart Fallback State Container + * + * @param player Player + * @param servers Fallback Servers + * @param reason Original Disconnect Reason + */ + public FallbackState(UUID player, Map servers, Component reason) { + this.player = player; + this.map = servers; + this.names = new LinkedList<>(servers.keySet()); + this.servers = new LinkedList<>(servers.values()); + this.reason = reason; + } + + /** + * Use a server + * + * @param name Server name to remove + */ + public void remove(String name) { + servers.remove(map.get(name)); + names.remove(name); + map.remove(name); + } + + /** + * Use a server + * + * @param server Server to remove + */ + public void remove(RegisteredServer server) { + map.remove(server.getServerInfo().getName()); + names.remove(server.getServerInfo().getName()); + servers.remove(server); + } + + /** + * Finish the process + * + * @param callback Finishing callback + * @param delay Delay for determining stability + */ + public void done(Runnable callback, long delay) { + if (finish != null) finish.cancel(); + (finish = new Timer("SubServers.Bungee::Fallback_Limbo_Timer(" + player + ')')).schedule(new TimerTask() { + @Override + public void run() { + if (callback != null) callback.run(); + finish.cancel(); + } + }, delay); + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Fallback/SmartFallback.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Fallback/SmartFallback.java new file mode 100644 index 00000000..1d69dbb2 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Fallback/SmartFallback.java @@ -0,0 +1,183 @@ +package net.ME1312.SubServers.Velocity.Library.Fallback; + +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubServers.Velocity.ExProxy; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent; +import com.velocitypowered.api.proxy.InboundConnection; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.server.RegisteredServer; + +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Smart Fallback Handler Class + */ +public class SmartFallback { + private static List inspectors = new CopyOnWriteArrayList(); + public static boolean dns_forward = false; + + public SmartFallback(ObjectMap settings) { + dns_forward = settings.getBoolean("DNS-Forward", false); + } + + @Subscribe + public void getServer(PlayerChooseInitialServerEvent e) { + RegisteredServer[] overrides; + if ((overrides = getForcedHosts(e.getPlayer())) != null + || (overrides = getDNS(e.getPlayer())) != null) { + e.setInitialServer(overrides[0]); + } else { + Map fallbacks = getFallbackServers(e.getPlayer()); + + if (/*(override = getReconnectServer(player)) != null || */!fallbacks.isEmpty()) { + e.setInitialServer((overrides != null)? overrides[0] : new LinkedList<>(fallbacks.values()).getFirst()); + } else { + e.setInitialServer(null); + } + } + } + + /** + * Grabs the Forced Host Server for this connection + * + * @param connection Connection to check + * @return Forced Host Servers (or null if there are none) + */ + public static RegisteredServer[] getForcedHosts(InboundConnection connection) { + if (!connection.getVirtualHost().isPresent()) { + return null; + } else { + List names = ExProxy.getInstance().getConfiguration().getForcedHosts().get(connection.getVirtualHost().get().getHostString()); + if (names == null) { + return null; + } else { + LinkedList forced = new LinkedList<>(); + for (String name : names) { + Optional server = ExProxy.getInstance().getServer(name); + server.ifPresent(forced::add); + } + return (forced.size() == 0)? null : forced.toArray(new RegisteredServer[0]); + } + } + } + + /** + * Grabs the Server that a connection's DNS matches + * + * @param connection Connection to check + * @return DNS Forward Server + */ + public static RegisteredServer[] getDNS(InboundConnection connection) { + if (!connection.getVirtualHost().isPresent() || !dns_forward) { + return null; + } else { + RegisteredServer server = null; + String dns = connection.getVirtualHost().get().getHostString().toLowerCase(); + for (RegisteredServer s : ExProxy.getInstance().getAllServers()) { + if (dns.startsWith(s.getServerInfo().getName().toLowerCase() + '.')) + if (server == null || server.getServerInfo().getName().length() < s.getServerInfo().getName().length()) + server = s; + } + + return (server == null)? null : new RegisteredServer[]{ server }; + } + } + + /** + * Grabs the Server that a player was last connected to + * + * @param player Player + * @return Reconnect Server + *//* + public static ServerInfo getReconnectServer(Player player) { + if (reconnect == null) { + return null; + } else try { + return Util.reflect(reconnect.getClass().getDeclaredMethod("getStoredServer", ProxiedPlayer.class), reconnect, player); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + e.printStackTrace(); + return null; + } + } */ + + /** + * Generates a smart sorted map of fallback servers using a generated confidence score + * + * @return Fallback Server Map (with legacy bungee case-sensitive keys) + */ + public static Map getFallbackServers() { + return getFallbackServers(null); + } + + /** + * Generates a smart sorted map of fallback servers using a generated confidence score + * + * @param player Player that is requesting fallback servers + * @return Fallback Server Map (with legacy bungee case-sensitive keys) + */ + public static Map getFallbackServers(Player player) { + TreeMap> score = new TreeMap>(Collections.reverseOrder()); + for (String name : ExProxy.getInstance().getConfiguration().getAttemptConnectionOrder()) { + RegisteredServer server = ExProxy.getInstance().getServer(name).orElse(null); + if (server != null) { + boolean valid = true; + double confidence = 0; + + List inspectors = new ArrayList(); + inspectors.addAll(SmartFallback.inspectors); + for (FallbackInspector inspector : inspectors) try { + Double response = inspector.inspect(player, server); + if (response == null) { + valid = false; + } else { + confidence += response; + } + } catch (Throwable e) { + new InvocationTargetException(e, "Exception while running inspecting fallback server: " + server.getServerInfo().getName()).printStackTrace(); + } + + if (valid) { + List servers = (score.keySet().contains(confidence))?score.get(confidence):new LinkedList(); + servers.add(server); + score.put(confidence, servers); + } + } + } + + Random random = new Random(); + LinkedHashMap map = new LinkedHashMap(); + for (List servers : score.values()) { + while (!servers.isEmpty()) { + RegisteredServer next = servers.get(random.nextInt(servers.size())); + map.put(next.getServerInfo().getName(), next); + servers.remove(next); + } + } + return map; + } + + /** + * Add a Fallback Server Inspector + * + * @param inspector Inspector + */ + public static void addInspector(FallbackInspector inspector) { + if (Util.isNull(inspector)) throw new NullPointerException(); + inspectors.add(inspector); + } + + /** + * Remove a Fallback Server Inspector + * + * @param inspector Inspector + */ + public static void removeInspector(FallbackInspector inspector) { + if (Util.isNull(inspector)) throw new NullPointerException(); + Util.isException(() -> inspectors.remove(inspector)); + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Metrics.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Metrics.java new file mode 100644 index 00000000..0516849e --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/Metrics.java @@ -0,0 +1,1026 @@ +package net.ME1312.SubServers.Velocity.Library; + +import com.google.inject.Inject; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.zip.GZIPOutputStream; +import javax.net.ssl.HttpsURLConnection; +import org.slf4j.Logger; + +public class Metrics { + + /** A factory to create new Metrics classes. */ + public static class Factory { + + private final ProxyServer server; + + private final Logger logger; + + private final Path dataDirectory; + + // The constructor is not meant to be called by the user. + // The instance is created using Dependency Injection + @Inject + private Factory(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { + this.server = server; + this.logger = logger; + this.dataDirectory = dataDirectory; + } + + /** + * Creates a new Metrics class. + * + * @param plugin The plugin instance. + * @param serviceId The id of the service. It can be found at What is my plugin id? + *

Not to be confused with Velocity's {@link PluginDescription#getId()} method! + * @return A Metrics instance that can be used to register custom charts. + *

The return value can be ignored, when you do not want to register custom charts. + */ + public Metrics make(Object plugin, int serviceId) { + return new Metrics(plugin, server, logger, dataDirectory, serviceId); + } + } + + private final PluginContainer pluginContainer; + + private final ProxyServer server; + + private MetricsBase metricsBase; + + private Metrics( + Object plugin, ProxyServer server, Logger logger, Path dataDirectory, int serviceId) { + pluginContainer = + server + .getPluginManager() + .fromInstance(plugin) + .orElseThrow( + () -> new IllegalArgumentException("The provided instance is not a plugin")); + this.server = server; + File configFile = dataDirectory.getParent().resolve("bStats").resolve("config.txt").toFile(); + MetricsConfig config; + try { + config = new MetricsConfig(configFile, true); + } catch (IOException e) { + logger.error("Failed to create bStats config", e); + return; + } + metricsBase = + new MetricsBase( + "velocity", + config.getServerUUID(), + serviceId, + config.isEnabled(), + this::appendPlatformData, + this::appendServiceData, + task -> server.getScheduler().buildTask(plugin, task).schedule(), + () -> true, + logger::warn, + logger::info, + config.isLogErrorsEnabled(), + config.isLogSentDataEnabled(), + config.isLogResponseStatusTextEnabled()); + if (!config.didExistBefore()) { + // Send an info message when the bStats config file gets created for the first time + logger.info( + "Velocity and some of its plugins collect metrics and send them to bStats (https://bStats.org)."); + logger.info( + "bStats collects some basic information for plugin authors, like how many people use"); + logger.info( + "their plugin and their total player count. It's recommend to keep bStats enabled, but"); + logger.info( + "if you're not comfortable with this, you can opt-out by editing the config.txt file in"); + logger.info("the '/plugins/bStats/' folder and setting enabled to false."); + } + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + if (metricsBase != null) { + metricsBase.addCustomChart(chart); + } + } + + private void appendPlatformData(JsonObjectBuilder builder) { + builder.appendField("playerAmount", server.getPlayerCount()); + builder.appendField("managedServers", server.getAllServers().size()); + builder.appendField("onlineMode", server.getConfiguration().isOnlineMode() ? 1 : 0); + builder.appendField("velocityVersionVersion", server.getVersion().getVersion()); + builder.appendField("velocityVersionName", server.getVersion().getName()); + builder.appendField("velocityVersionVendor", server.getVersion().getVendor()); + builder.appendField("javaVersion", System.getProperty("java.version")); + builder.appendField("osName", System.getProperty("os.name")); + builder.appendField("osArch", System.getProperty("os.arch")); + builder.appendField("osVersion", System.getProperty("os.version")); + builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); + } + + private void appendServiceData(JsonObjectBuilder builder) { + builder.appendField( + "pluginVersion", pluginContainer.getDescription().getVersion().orElse("unknown")); + } + + public static class MetricsBase { + + /** The version of the Metrics class. */ + public static final String METRICS_VERSION = "2.2.1"; + + private static final ScheduledExecutorService scheduler = + Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); + + private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; + + private final String platform; + + private final String serverUuid; + + private final int serviceId; + + private final Consumer appendPlatformDataConsumer; + + private final Consumer appendServiceDataConsumer; + + private final Consumer submitTaskConsumer; + + private final Supplier checkServiceEnabledSupplier; + + private final BiConsumer errorLogger; + + private final Consumer infoLogger; + + private final boolean logErrors; + + private final boolean logSentData; + + private final boolean logResponseStatusText; + + private final Set customCharts = new HashSet<>(); + + private final boolean enabled; + + /** + * Creates a new MetricsBase class instance. + * + * @param platform The platform of the service. + * @param serviceId The id of the service. + * @param serverUuid The server uuid. + * @param enabled Whether or not data sending is enabled. + * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all platform-specific data. + * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all service-specific data. + * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be + * used to delegate the data collection to a another thread to prevent errors caused by + * concurrency. Can be {@code null}. + * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. + * @param errorLogger A consumer that accepts log message and an error. + * @param infoLogger A consumer that accepts info log messages. + * @param logErrors Whether or not errors should be logged. + * @param logSentData Whether or not the sent data should be logged. + * @param logResponseStatusText Whether or not the response status text should be logged. + */ + public MetricsBase( + String platform, + String serverUuid, + int serviceId, + boolean enabled, + Consumer appendPlatformDataConsumer, + Consumer appendServiceDataConsumer, + Consumer submitTaskConsumer, + Supplier checkServiceEnabledSupplier, + BiConsumer errorLogger, + Consumer infoLogger, + boolean logErrors, + boolean logSentData, + boolean logResponseStatusText) { + this.platform = platform; + this.serverUuid = serverUuid; + this.serviceId = serviceId; + this.enabled = enabled; + this.appendPlatformDataConsumer = appendPlatformDataConsumer; + this.appendServiceDataConsumer = appendServiceDataConsumer; + this.submitTaskConsumer = submitTaskConsumer; + this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; + this.errorLogger = errorLogger; + this.infoLogger = infoLogger; + this.logErrors = logErrors; + this.logSentData = logSentData; + this.logResponseStatusText = logResponseStatusText; + checkRelocation(); + if (enabled) { + startSubmitting(); + } + } + + public void addCustomChart(CustomChart chart) { + this.customCharts.add(chart); + } + + private void startSubmitting() { + final Runnable submitTask = + () -> { + if (!enabled || !checkServiceEnabledSupplier.get()) { + // Submitting data or service is disabled + scheduler.shutdown(); + return; + } + if (submitTaskConsumer != null) { + submitTaskConsumer.accept(this::submitData); + } else { + this.submitData(); + } + }; + // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution + // of requests on the + // bStats backend. To circumvent this problem, we introduce some randomness into the initial + // and second delay. + // WARNING: You must not modify and part of this Metrics class, including the submit delay or + // frequency! + // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! + long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); + long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); + scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate( + submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); + } + + private void submitData() { + final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); + appendPlatformDataConsumer.accept(baseJsonBuilder); + final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); + appendServiceDataConsumer.accept(serviceJsonBuilder); + JsonObjectBuilder.JsonObject[] chartData = + customCharts.stream() + .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) + .filter(Objects::nonNull) + .toArray(JsonObjectBuilder.JsonObject[]::new); + serviceJsonBuilder.appendField("id", serviceId); + serviceJsonBuilder.appendField("customCharts", chartData); + baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); + baseJsonBuilder.appendField("serverUUID", serverUuid); + baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); + JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); + scheduler.execute( + () -> { + try { + // Send the data + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logErrors) { + errorLogger.accept("Could not submit bStats metrics data", e); + } + } + }); + } + + private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { + if (logSentData) { + infoLogger.accept("Sent bStats metrics data: " + data.toString()); + } + String url = String.format(REPORT_URL, platform); + HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("User-Agent", "Metrics-Service/1"); + connection.setDoOutput(true); + try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { + outputStream.write(compressedData); + } + StringBuilder builder = new StringBuilder(); + try (BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + } + if (logResponseStatusText) { + infoLogger.accept("Sent data to bStats and received response: " + builder); + } + } + + /** Checks that the class was properly relocated. */ + private void checkRelocation() { + // You can use the property to disable the check in your test environment + if (System.getProperty("bstats.relocatecheck") == null + || !System.getProperty("bstats.relocatecheck").equals("false")) { + // Maven's Relocate is clever and changes strings, too. So we have to use this little + // "trick" ... :D + final String defaultPackage = + new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); + final String examplePackage = + new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); + // We want to make sure no one just copy & pastes the example and uses the wrong package + // names + if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) + || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + + /** + * Gzips the given string. + * + * @param str The string to gzip. + * @return The gzipped string. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + } + return outputStream.toByteArray(); + } + } + + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public abstract static class CustomChart { + + private final String chartId; + + protected CustomChart(String chartId) { + if (chartId == null) { + throw new IllegalArgumentException("chartId must not be null"); + } + this.chartId = chartId; + } + + public JsonObjectBuilder.JsonObject getRequestJsonObject( + BiConsumer errorLogger, boolean logErrors) { + JsonObjectBuilder builder = new JsonObjectBuilder(); + builder.appendField("chartId", chartId); + try { + JsonObjectBuilder.JsonObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + builder.appendField("data", data); + } catch (Throwable t) { + if (logErrors) { + errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return builder.build(); + } + + protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; + } + + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + /** + * An extremely simple JSON builder. + * + *

While this class is neither feature-rich nor the most performant one, it's sufficient enough + * for its use-case. + */ + public static class JsonObjectBuilder { + + private StringBuilder builder = new StringBuilder(); + + private boolean hasAtLeastOneField = false; + + public JsonObjectBuilder() { + builder.append("{"); + } + + /** + * Appends a null field to the JSON. + * + * @param key The key of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendNull(String key) { + appendFieldUnescaped(key, "null"); + return this; + } + + /** + * Appends a string field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String value) { + if (value == null) { + throw new IllegalArgumentException("JSON value must not be null"); + } + appendFieldUnescaped(key, "\"" + escape(value) + "\""); + return this; + } + + /** + * Appends an integer field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int value) { + appendFieldUnescaped(key, String.valueOf(value)); + return this; + } + + /** + * Appends an object to the JSON. + * + * @param key The key of the field. + * @param object The object. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject object) { + if (object == null) { + throw new IllegalArgumentException("JSON object must not be null"); + } + appendFieldUnescaped(key, object.toString()); + return this; + } + + /** + * Appends a string array to the JSON. + * + * @param key The key of the field. + * @param values The string array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values) + .map(value -> "\"" + escape(value) + "\"") + .collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an integer array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an object array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends a field to the object. + * + * @param key The key of the field. + * @param escapedValue The escaped value of the field. + */ + private void appendFieldUnescaped(String key, String escapedValue) { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + if (key == null) { + throw new IllegalArgumentException("JSON key must not be null"); + } + if (hasAtLeastOneField) { + builder.append(","); + } + builder.append("\"").append(escape(key)).append("\":").append(escapedValue); + hasAtLeastOneField = true; + } + + /** + * Builds the JSON string and invalidates this builder. + * + * @return The built JSON string. + */ + public JsonObject build() { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + JsonObject object = new JsonObject(builder.append("}").toString()); + builder = null; + return object; + } + + /** + * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. + * + *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. + * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). + * + * @param value The value to escape. + * @return The escaped value. + */ + private static String escape(String value) { + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '"') { + builder.append("\\\""); + } else if (c == '\\') { + builder.append("\\\\"); + } else if (c <= '\u000F') { + builder.append("\\u000").append(Integer.toHexString(c)); + } else if (c <= '\u001F') { + builder.append("\\u00").append(Integer.toHexString(c)); + } else { + builder.append(c); + } + } + return builder.toString(); + } + + /** + * A super simple representation of a JSON object. + * + *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not + * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, + * JsonObject)}. + */ + public static class JsonObject { + + private final String value; + + private JsonObject(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + } + } + + /** + * A simple config for bStats. + * + *

This class is not used by every platform. + */ + public static class MetricsConfig { + + private final File file; + + private final boolean defaultEnabled; + + private String serverUUID; + + private boolean enabled; + + private boolean logErrors; + + private boolean logSentData; + + private boolean logResponseStatusText; + + private boolean didExistBefore = true; + + public MetricsConfig(File file, boolean defaultEnabled) throws IOException { + this.file = file; + this.defaultEnabled = defaultEnabled; + setupConfig(); + } + + public String getServerUUID() { + return serverUUID; + } + + public boolean isEnabled() { + return enabled; + } + + public boolean isLogErrorsEnabled() { + return logErrors; + } + + public boolean isLogSentDataEnabled() { + return logSentData; + } + + public boolean isLogResponseStatusTextEnabled() { + return logResponseStatusText; + } + + /** + * Checks whether the config file did exist before or not. + * + * @return If the config did exist before. + */ + public boolean didExistBefore() { + return didExistBefore; + } + + /** Creates the config file if it does not exist and read its content. */ + private void setupConfig() throws IOException { + if (!file.exists()) { + // Looks like it's the first time we create it (or someone deleted it). + didExistBefore = false; + writeConfig(); + } + readConfig(); + if (serverUUID == null) { + // Found a malformed config file with no UUID. Let's recreate it. + writeConfig(); + readConfig(); + } + } + + /** Creates a config file with teh default content. */ + private void writeConfig() throws IOException { + List configContent = new ArrayList<>(); + configContent.add( + "# bStats (https://bStats.org) collects some basic information for plugin authors, like"); + configContent.add( + "# how many people use their plugin and their total player count. It's recommended to keep"); + configContent.add( + "# bStats enabled, but if you're not comfortable with this, you can turn this setting off."); + configContent.add( + "# There is no performance penalty associated with having metrics enabled, and data sent to"); + configContent.add("# bStats is fully anonymous."); + configContent.add("enabled=" + defaultEnabled); + configContent.add("server-uuid=" + UUID.randomUUID().toString()); + configContent.add("log-errors=false"); + configContent.add("log-sent-data=false"); + configContent.add("log-response-status-text=false"); + writeFile(file, configContent); + } + + /** Reads the content of the config file. */ + private void readConfig() throws IOException { + List lines = readFile(file); + if (lines == null) { + throw new AssertionError("Content of newly created file is null"); + } + enabled = getConfigValue("enabled", lines).map("true"::equals).orElse(true); + serverUUID = getConfigValue("server-uuid", lines).orElse(null); + logErrors = getConfigValue("log-errors", lines).map("true"::equals).orElse(false); + logSentData = getConfigValue("log-sent-data", lines).map("true"::equals).orElse(false); + logResponseStatusText = + getConfigValue("log-response-status-text", lines).map("true"::equals).orElse(false); + } + + /** + * Gets a config setting from the given list of lines of the file. + * + * @param key The key for the setting. + * @param lines The lines of the file. + * @return The value of the setting. + */ + private Optional getConfigValue(String key, List lines) { + return lines.stream() + .filter(line -> line.startsWith(key + "=")) + .map(line -> line.replaceFirst(Pattern.quote(key + "="), "")) + .findFirst(); + } + + /** + * Reads the text content of the given file. + * + * @param file The file to read. + * @return The lines of the given file. + */ + private List readFile(File file) throws IOException { + if (!file.exists()) { + return null; + } + try (FileReader fileReader = new FileReader(file); + BufferedReader bufferedReader = new BufferedReader(fileReader)) { + return bufferedReader.lines().collect(Collectors.toList()); + } + } + + /** + * Writes the given lines to the given file. + * + * @param file The file to write to. + * @param lines The lines to write. + */ + private void writeFile(File file, List lines) throws IOException { + if (!file.exists()) { + file.getParentFile().mkdirs(); + file.createNewFile(); + } + try (FileWriter fileWriter = new FileWriter(file); + BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) { + for (String line : lines) { + bufferedWriter.write(line); + bufferedWriter.newLine(); + } + } + } + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/SubEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/SubEvent.java new file mode 100644 index 00000000..38d746a3 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Library/SubEvent.java @@ -0,0 +1,7 @@ +package net.ME1312.SubServers.Velocity.Library; + +/** + * SubEvent Layout Class + */ +public interface SubEvent { +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketCheckPermission.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketCheckPermission.java new file mode 100644 index 00000000..38b33572 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketCheckPermission.java @@ -0,0 +1,61 @@ +package net.ME1312.SubServers.Velocity.Network.Packet; + +import net.ME1312.Galaxi.Library.Callback.Callback; +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubData.Client.Protocol.Forwardable; +import net.ME1312.SubData.Client.Protocol.PacketObjectIn; +import net.ME1312.SubData.Client.Protocol.PacketObjectOut; +import net.ME1312.SubData.Client.SubDataSender; + +import java.util.HashMap; +import java.util.UUID; + +/** + * Check Permission Packet + */ +public class PacketCheckPermission implements Forwardable, PacketObjectIn, PacketObjectOut { + static HashMap[]> callbacks = new HashMap[]>(); + private UUID player; + private String permission; + private UUID tracker; + + /** + * New PacketCheckPermission (In) + */ + public PacketCheckPermission() {} + + /** + * New PacketCheckPermission (Out) + * + * @param player Player to check on + * @param permission Permission to check + * @param callback Callbacks + */ + @SafeVarargs + public PacketCheckPermission(UUID player, String permission, Callback... callback) { + this.player = player; + this.permission = permission; + this.tracker = Util.getNew(callbacks.keySet(), UUID::randomUUID); + callbacks.put(tracker, callback); + } + + @Override + public ObjectMap send(SubDataSender client) throws Throwable { + ObjectMap data = new ObjectMap(); + data.set(0x0000, tracker); + data.set(0x0001, player); + data.set(0x0002, permission); + return data; + } + + @Override + public void receive(SubDataSender client, ObjectMap data) throws Throwable { + client.sendPacket(new PacketCheckPermissionResponse(data.getUUID(0x0001), data.getRawString(0x0002), (data.contains(0x0000))?data.getUUID(0x0000):null)); + } + + @Override + public int version() { + return 0x0001; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketCheckPermissionResponse.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketCheckPermissionResponse.java new file mode 100644 index 00000000..85de7798 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketCheckPermissionResponse.java @@ -0,0 +1,59 @@ +package net.ME1312.SubServers.Velocity.Network.Packet; + +import net.ME1312.Galaxi.Library.Callback.Callback; +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubData.Client.Protocol.Forwardable; +import net.ME1312.SubData.Client.Protocol.PacketObjectIn; +import net.ME1312.SubData.Client.Protocol.PacketObjectOut; +import net.ME1312.SubData.Client.SubDataSender; +import net.ME1312.SubServers.Velocity.ExProxy; + +import java.util.UUID; + +import static net.ME1312.SubServers.Velocity.Network.Packet.PacketCheckPermission.callbacks; + + +/** + * Check Permission Response Packet + */ +public class PacketCheckPermissionResponse implements Forwardable, PacketObjectIn, PacketObjectOut { + private boolean result; + private UUID tracker; + + /** + * New PacketCheckPermissionResponse (In) + */ + public PacketCheckPermissionResponse() {} + + /** + * New PacketCheckPermissionResponse (Out) + * + * @param player Player to check on + * @param permission Permission to check + * @param tracker Receiver ID + */ + public PacketCheckPermissionResponse(UUID player, String permission, UUID tracker) { + this.result = Util.getDespiteException(() -> ExProxy.getInstance().getPlayer(player).get().hasPermission(permission), false); + this.tracker = tracker; + } + + @Override + public ObjectMap send(SubDataSender client) throws Throwable { + ObjectMap data = new ObjectMap(); + data.set(0x0000, tracker); + data.set(0x0001, result); + return data; + } + + @Override + public void receive(SubDataSender client, ObjectMap data) throws Throwable { + for (Callback callback : callbacks.get(data.getUUID(0x0000))) callback.run(data.getBoolean(0x0001)); + callbacks.remove(data.getUUID(0x0000)); + } + + @Override + public int version() { + return 0x0001; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketDownloadLang.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketDownloadLang.java new file mode 100644 index 00000000..c4042177 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketDownloadLang.java @@ -0,0 +1,49 @@ +package net.ME1312.SubServers.Velocity.Network.Packet; + +import net.ME1312.Galaxi.Library.Container.ContainedPair; +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubData.Client.Protocol.PacketObjectIn; +import net.ME1312.SubData.Client.Protocol.PacketOut; +import net.ME1312.SubData.Client.SubDataSender; +import net.ME1312.SubServers.Velocity.ExProxy; +import net.ME1312.SubServers.Velocity.Library.Compatibility.Logger; + +import java.util.Calendar; + +/** + * Download Lang Packet + */ +public class PacketDownloadLang implements PacketObjectIn, PacketOut { + private ExProxy plugin; + + /** + * New PacketDownloadLang (In) + * + * @param plugin SubServers.Client + */ + public PacketDownloadLang(ExProxy plugin) { + if (Util.isNull(plugin)) throw new NullPointerException(); + this.plugin = plugin; + } + + /** + * New PacketDownloadLang (Out) + */ + public PacketDownloadLang() {} + + @Override + public void receive(SubDataSender client, ObjectMap data) { + try { + Util.reflect(ExProxy.class.getDeclaredField("lang"), plugin, new ContainedPair<>(Calendar.getInstance().getTime().getTime(), data.getObject(0x0001))); + Logger.get("SubData").info("Lang Settings Downloaded"); + } catch (IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + } + } + + @Override + public int version() { + return 0x0001; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketExDisconnectPlayer.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketExDisconnectPlayer.java new file mode 100644 index 00000000..8e66a0f8 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketExDisconnectPlayer.java @@ -0,0 +1,79 @@ +package net.ME1312.SubServers.Velocity.Network.Packet; + +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.SubData.Client.Protocol.PacketObjectIn; +import net.ME1312.SubData.Client.Protocol.PacketObjectOut; +import net.ME1312.SubData.Client.SubDataSender; +import net.ME1312.SubServers.Velocity.ExProxy; +import net.ME1312.SubServers.Velocity.Library.Compatibility.ChatColor; + +import com.velocitypowered.api.proxy.Player; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Disconnect External Player Packet + */ +public class PacketExDisconnectPlayer implements PacketObjectIn, PacketObjectOut { + private ExProxy plugin; + private int response; + private UUID tracker; + + /** + * New PacketExDisconnectPlayer (In) + */ + public PacketExDisconnectPlayer(ExProxy plugin) { + this.plugin = plugin; + } + + /** + * New PacketExDisconnectPlayer (Out) + * + * @param response Response ID + * @param tracker Receiver ID + */ + public PacketExDisconnectPlayer(int response, UUID tracker) { + this.response = response; + this.tracker = tracker; + } + + @Override + public ObjectMap send(SubDataSender client) { + ObjectMap json = new ObjectMap(); + if (tracker != null) json.set(0x0000, tracker); + json.set(0x0001, response); + return json; + } + + @SuppressWarnings("deprecation") + @Override + public void receive(SubDataSender client, ObjectMap data) { + UUID tracker = (data.contains(0x0000)?data.getUUID(0x0000):null); + List ids = data.getUUIDList(0x0001); + try { + Component message = (data.contains(0x0002))? ChatColor.convertColor(data.getRawString(0x0002)) : Component.text().build(); + int failures = 0; + for (UUID id : ids) { + Optional local = ExProxy.getInstance().getPlayer(id); + if (local.isPresent()) { + local.get().disconnect(message); + } else { + ++failures; + } + } + client.sendPacket(new PacketExDisconnectPlayer(failures, tracker)); + } catch (Throwable e) { + client.sendPacket(new PacketExDisconnectPlayer(ids.size(), tracker)); + e.printStackTrace(); + } + } + + @Override + public int version() { + return 0x0001; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketExMessagePlayer.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketExMessagePlayer.java new file mode 100644 index 00000000..791eda38 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketExMessagePlayer.java @@ -0,0 +1,103 @@ +package net.ME1312.SubServers.Velocity.Network.Packet; + +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.SubData.Client.Protocol.PacketObjectIn; +import net.ME1312.SubData.Client.Protocol.PacketObjectOut; +import net.ME1312.SubData.Client.SubDataSender; +import net.ME1312.SubServers.Velocity.ExProxy; +import net.ME1312.SubServers.Velocity.Library.Compatibility.ChatColor; + +import com.velocitypowered.api.proxy.Player; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Message External Player Packet + */ +public class PacketExMessagePlayer implements PacketObjectIn, PacketObjectOut { + private ExProxy plugin; + private int response; + private UUID tracker; + + /** + * New PacketExMessagePlayer (In) + */ + public PacketExMessagePlayer(ExProxy plugin) { + this.plugin = plugin; + } + + /** + * New PacketExMessagePlayer (Out) + * + * @param response Response ID + * @param tracker Receiver ID + */ + public PacketExMessagePlayer(int response, UUID tracker) { + this.response = response; + this.tracker = tracker; + } + + @Override + public ObjectMap send(SubDataSender client) { + ObjectMap json = new ObjectMap(); + if (tracker != null) json.set(0x0000, tracker); + json.set(0x0001, response); + return json; + } + + @SuppressWarnings("deprecation") + @Override + public void receive(SubDataSender client, ObjectMap data) { + UUID tracker = (data.contains(0x0000)?data.getUUID(0x0000):null); + List ids = (data.contains(0x0001)?data.getUUIDList(0x0001):null); + try { + Component[] legacy = null; + Component[] components = null; + + if (data.contains(0x0002)) { + List messages = data.getRawStringList(0x0002); + legacy = new Component[messages.size()]; + for (int i = 0; i < legacy.length; ++i) legacy[i] = ChatColor.convertColor(messages.get(i)); + } + if (data.contains(0x0003)) { + List messages = data.getRawStringList(0x0003); + components = new Component[messages.size()]; + for (int i = 0; i < components.length; ++i) components[i] = GsonComponentSerializer.gson().deserialize(messages.get(i)); + } + + int failures = 0; + if (ids == null || ids.size() == 0) { + if (legacy != null) for (Component c : legacy) + ExProxy.getInstance().sendMessage(c); + if (components != null) for (Component c : components) + ExProxy.getInstance().sendMessage(c); + } else { + for (UUID id : ids) { + Optional local = ExProxy.getInstance().getPlayer(id); + if (local.isPresent()) { + if (legacy != null) for (Component c : legacy) + local.get().sendMessage(c); + if (components != null) for (Component c : components) + local.get().sendMessage(c); + } else { + ++failures; + } + } + } + client.sendPacket(new PacketExMessagePlayer(failures, tracker)); + } catch (Throwable e) { + client.sendPacket(new PacketExMessagePlayer((ids == null || ids.size() == 0)? 1 : ids.size(), tracker)); + e.printStackTrace(); + } + } + + @Override + public int version() { + return 0x0001; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketExSyncPlayer.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketExSyncPlayer.java new file mode 100644 index 00000000..bed8da72 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketExSyncPlayer.java @@ -0,0 +1,100 @@ +package net.ME1312.SubServers.Velocity.Network.Packet; + +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubData.Client.Protocol.PacketObjectIn; +import net.ME1312.SubData.Client.Protocol.PacketObjectOut; +import net.ME1312.SubData.Client.SubDataSender; +import net.ME1312.SubServers.Velocity.ExProxy; +import net.ME1312.SubServers.Velocity.Server.CachedPlayer; +import net.ME1312.SubServers.Velocity.Server.ServerData; + +import com.velocitypowered.api.proxy.server.RegisteredServer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * External Player Sync Packet + */ +public class PacketExSyncPlayer implements PacketObjectIn, PacketObjectOut { + private ExProxy plugin; + private Boolean mode; + private CachedPlayer[] values; + + /** + * New PacketExSyncPlayer (In) + * + * @param plugin SubPlugin + */ + public PacketExSyncPlayer(ExProxy plugin) { + if (Util.isNull(plugin)) throw new NullPointerException(); + this.plugin = plugin; + } + + /** + * New PacketExSyncPlayer (Out) + * + * @param mode Update Mode (true for add, false for remove, null for reset) + * @param values RemotePlayers + */ + public PacketExSyncPlayer(Boolean mode, CachedPlayer... values) { + this.mode = mode; + this.values = values; + } + + @Override + public ObjectMap send(SubDataSender client) { + ObjectMap data = new ObjectMap(); + data.set(0x0001, mode); + if (values != null) { + ArrayList> list = new ArrayList>(); + for (CachedPlayer value : values) list.add(value.getRaw()); + data.set(0x0002, list); + } + return data; + } + + @SuppressWarnings("unchecked") + @Override + public void receive(SubDataSender client, ObjectMap data) { + String proxy = (data.contains(0x0000)?data.getRawString(0x0000).toLowerCase():null); + synchronized (plugin.rPlayers) { + if (data.getBoolean(0x0001) == null) { + for (UUID id : Util.getBackwards(plugin.rPlayerLinkP, proxy)) { + plugin.rPlayerLinkS.remove(id); + plugin.rPlayerLinkP.remove(id); + plugin.rPlayers.remove(id); + } + } + if (data.getBoolean(0x0001) != Boolean.FALSE) { + if (data.contains(0x0002)) for (Map object : (List>) data.getObjectList(0x0002)) { + ServerData server = (object.getOrDefault("server", null) != null)?ExProxy.getInstance().getServer(object.get("server").toString()).map(RegisteredServer::getServerInfo).map(plugin::getData).orElse(null):null; + CachedPlayer player = new CachedPlayer(new ObjectMap<>(object)); + + plugin.rPlayerLinkP.put(player.getUniqueId(), proxy); + plugin.rPlayers.put(player.getUniqueId(), player); + if (server != null) plugin.rPlayerLinkS.put(player.getUniqueId(), server); + } + } else { + if (data.contains(0x0002)) for (Map object : (List>) data.getObjectList(0x0002)) { + UUID id = UUID.fromString(object.get("id").toString()); + + // Don't accept removal requests when we're managing players + if ((!plugin.rPlayerLinkP.containsKey(id) || !plugin.rPlayerLinkP.get(id).equalsIgnoreCase(plugin.api.getName().toLowerCase()))) { + plugin.rPlayerLinkS.remove(id); + plugin.rPlayerLinkP.remove(id); + plugin.rPlayers.remove(id); + } + } + } + } + } + + @Override + public int version() { + return 0x0001; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketExTransferPlayer.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketExTransferPlayer.java new file mode 100644 index 00000000..addcd2df --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketExTransferPlayer.java @@ -0,0 +1,80 @@ +package net.ME1312.SubServers.Velocity.Network.Packet; + +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.SubData.Client.Protocol.PacketObjectIn; +import net.ME1312.SubData.Client.Protocol.PacketObjectOut; +import net.ME1312.SubData.Client.SubDataSender; +import net.ME1312.SubServers.Velocity.ExProxy; + +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.server.RegisteredServer; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Transfer External Player Packet + */ +public class PacketExTransferPlayer implements PacketObjectIn, PacketObjectOut { + private ExProxy plugin; + private int response; + private UUID tracker; + + /** + * New PacketExTransferPlayer (In) + */ + public PacketExTransferPlayer(ExProxy plugin) { + this.plugin = plugin; + } + + /** + * New PacketExTransferPlayer (Out) + * + * @param response Response ID + * @param tracker Receiver ID + */ + public PacketExTransferPlayer(int response, UUID tracker) { + this.response = response; + this.tracker = tracker; + } + + @Override + public ObjectMap send(SubDataSender client) { + ObjectMap json = new ObjectMap(); + if (tracker != null) json.set(0x0000, tracker); + json.set(0x0001, response); + return json; + } + + @Override + public void receive(SubDataSender client, ObjectMap data) { + UUID tracker = (data.contains(0x0000)?data.getUUID(0x0000):null); + List ids = data.getUUIDList(0x0001); + try { + Optional server = ExProxy.getInstance().getServer(data.getRawString(0x0002).toLowerCase()); + if (server.isPresent()) { + int failures = 0; + for (UUID id : ids) { + Optional local = ExProxy.getInstance().getPlayer(id); + if (local.isPresent()) { + local.get().createConnectionRequest(server.get()).fireAndForget(); + } else { + ++failures; + } + } + client.sendPacket(new PacketExTransferPlayer(failures, tracker)); + } else { + client.sendPacket(new PacketExTransferPlayer(ids.size(), tracker)); + } + } catch (Throwable e) { + client.sendPacket(new PacketExTransferPlayer(ids.size(), tracker)); + e.printStackTrace(); + } + } + + @Override + public int version() { + return 0x0001; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketInExEditServer.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketInExEditServer.java new file mode 100644 index 00000000..f996b564 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketInExEditServer.java @@ -0,0 +1,65 @@ +package net.ME1312.SubServers.Velocity.Network.Packet; + +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.SubData.Client.Protocol.PacketObjectIn; +import net.ME1312.SubData.Client.SubDataSender; +import net.ME1312.SubServers.Velocity.ExProxy; +import net.ME1312.SubServers.Velocity.Library.Compatibility.ChatColor; +import net.ME1312.SubServers.Velocity.Server.ServerData; + +import com.velocitypowered.api.proxy.server.RegisteredServer; + +/** + * Server Edit Notification Packet + */ +public class PacketInExEditServer implements PacketObjectIn { + private ExProxy plugin; + + /** + * New PacketExControlServer (In) + */ + public PacketInExEditServer(ExProxy plugin) { + this.plugin = plugin; + } + + @Override + public void receive(SubDataSender client, ObjectMap data) { + ServerData server = ExProxy.getInstance().getServer(data.getRawString(0x0000)).map(RegisteredServer::getServerInfo).map(plugin::getData).orElse(null); + if (server != null) { + switch (data.getInt(0x0001)) { + case 0: + server.setDisplayName(data.getList(0x0002).get(0).asRawString()); + break; + case 1: + server.setMotd(ChatColor.parseColor('&', data.getList(0x0002).get(0).asRawString())); + break; + case 2: + server.setRestricted(data.getList(0x0002).get(0).asBoolean()); + break; + case 3: + server.setHidden(data.getList(0x0002).get(0).asBoolean()); + break; + case 4: + server.setSubData(data.getList(0x0002).get(1).asUUID(), data.getList(0x0002).get(0).asInt()); + break; + case 5: + server.setSubData(null, data.getList(0x0002).get(0).asInt()); + break; + case 6: + server.whitelist = data.getList(0x0002).get(0).asUUIDList(); + break; + case 7: + server.whitelist(data.getList(0x0002).get(0).asUUID()); + break; + case 8: + server.unwhitelist(data.getList(0x0002).get(0).asUUID()); + break; + } + } + } + + @Override + public int version() { + return 0x0002; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketInExReset.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketInExReset.java new file mode 100644 index 00000000..e5db8684 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketInExReset.java @@ -0,0 +1,21 @@ +package net.ME1312.SubServers.Velocity.Network.Packet; + +import net.ME1312.SubData.Client.Protocol.PacketIn; +import net.ME1312.SubData.Client.SubDataSender; +import net.ME1312.SubServers.Velocity.SubAPI; + +/** + * Reset Packet + */ +public class PacketInExReset implements PacketIn { + + @Override + public void receive(SubDataSender client) { + SubAPI.getInstance().getInternals().servers.clear(); + } + + @Override + public int version() { + return 0x0001; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketInExRunEvent.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketInExRunEvent.java new file mode 100644 index 00000000..df45a701 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketInExRunEvent.java @@ -0,0 +1,150 @@ +package net.ME1312.SubServers.Velocity.Network.Packet; + +import net.ME1312.Galaxi.Library.Callback.Callback; +import net.ME1312.Galaxi.Library.Container.ContainedPair; +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.SubData.Client.Protocol.PacketObjectIn; +import net.ME1312.SubData.Client.SubDataSender; +import net.ME1312.SubServers.Velocity.Event.*; +import net.ME1312.SubServers.Velocity.ExProxy; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Run Event Packet + */ +public class PacketInExRunEvent implements PacketObjectIn { + private static HashMap>>> callbacks = new HashMap>>>(); + + /** + * New PacketInExRunEvent + */ + public PacketInExRunEvent(ExProxy plugin) { + callback("SubAddHostEvent", new Callback>() { + @Override + public void run(ObjectMap data) { + ExProxy.getInstance().getEventManager().fire(new SubAddHostEvent((data.contains("player"))?data.getUUID("player"):null, data.getRawString("host"))); + callback("SubAddHostEvent", this); + } + }); + callback("SubAddProxyEvent", new Callback>() { + @Override + public void run(ObjectMap data) { + ExProxy.getInstance().getEventManager().fire(new SubAddProxyEvent(data.getRawString("proxy"))); + callback("SubAddProxyEvent", this); + } + }); + callback("SubAddServerEvent", new Callback>() { + @Override + public void run(ObjectMap data) { + ExProxy.getInstance().getEventManager().fire(new SubAddServerEvent((data.contains("player"))?data.getUUID("player"):null, (data.contains("host"))?data.getRawString("host"):null, data.getRawString("server"))); + callback("SubAddServerEvent", this); + } + }); + callback("SubCreateEvent", new Callback>() { + @Override + public void run(ObjectMap data) { + ExProxy.getInstance().getEventManager().fire(new SubCreateEvent((data.contains("player"))?data.getUUID("player"):null, data.getRawString("host"), data.getRawString("name"), + data.getRawString("template"), data.getVersion("version"), data.getInt("port"), data.getBoolean("update"))); + callback("SubCreateEvent", this); + } + }); + callback("SubCreatedEvent", new Callback>() { + @Override + public void run(ObjectMap data) { + ExProxy.getInstance().getEventManager().fire(new SubCreatedEvent((data.contains("player"))?data.getUUID("player"):null, data.getRawString("host"), data.getRawString("name"), + data.getRawString("template"), data.getVersion("version"), data.getInt("port"), data.getBoolean("update"), data.getBoolean("success"))); + callback("SubCreatedEvent", this); + } + }); + callback("SubSendCommandEvent", new Callback>() { + @Override + public void run(ObjectMap data) { + ExProxy.getInstance().getEventManager().fire(new SubSendCommandEvent((data.contains("player"))?data.getUUID("player"):null, data.getRawString("server"), data.getRawString("command"))); + callback("SubSendCommandEvent", this); + } + }); + callback("SubEditServerEvent", new Callback>() { + @Override + public void run(ObjectMap data) { + ExProxy.getInstance().getEventManager().fire(new SubEditServerEvent((data.contains("player"))?data.getUUID("player"):null, data.getRawString("server"), new ContainedPair(data.getRawString("edit"), data.get("value")))); + callback("SubEditServerEvent", this); + } + }); + callback("SubStartEvent", new Callback>() { + @Override + public void run(ObjectMap data) { + ExProxy.getInstance().getEventManager().fire(new SubStartEvent((data.contains("player"))?data.getUUID("player"):null, data.getRawString("server"))); + callback("SubStartEvent", this); + } + }); + callback("SubStartedEvent", new Callback>() { + @Override + public void run(ObjectMap data) { + ExProxy.getInstance().getEventManager().fire(new SubStartedEvent(data.getRawString("server"))); + callback("SubStartedEvent", this); + } + }); + callback("SubStopEvent", new Callback>() { + @Override + public void run(ObjectMap data) { + ExProxy.getInstance().getEventManager().fire(new SubStopEvent((data.contains("player"))?data.getUUID("player"):null, data.getRawString("server"), data.getBoolean("force"))); + callback("SubStopEvent", this); + } + }); + callback("SubStoppedEvent", new Callback>() { + @Override + public void run(ObjectMap data) { + ExProxy.getInstance().getEventManager().fire(new SubStoppedEvent(data.getRawString("server"))); + callback("SubStoppedEvent", this); + } + }); + callback("SubRemoveServerEvent", new Callback>() { + @Override + public void run(ObjectMap data) { + ExProxy.getInstance().getEventManager().fire(new SubRemoveServerEvent((data.contains("player"))?data.getUUID("player"):null, (data.contains("host"))?data.getRawString("host"):null, data.getRawString("server"))); + callback("SubRemoveServerEvent", this); + } + }); + callback("SubRemoveProxyEvent", new Callback>() { + @Override + public void run(ObjectMap data) { + ExProxy.getInstance().getEventManager().fire(new SubAddProxyEvent(data.getRawString("proxy"))); + callback("SubRemoveProxyEvent", this); + } + }); + callback("SubRemoveHostEvent", new Callback>() { + @Override + public void run(ObjectMap data) { + ExProxy.getInstance().getEventManager().fire(new SubRemoveHostEvent((data.contains("player"))?data.getUUID("player"):null, data.getRawString("host"))); + callback("SubRemoveHostEvent", this); + } + }); + } + + @SuppressWarnings("unchecked") + @Override + public void receive(SubDataSender client, ObjectMap data) { + if (callbacks.keySet().contains(data.getString(0x0000))) { + List>> callbacks = PacketInExRunEvent.callbacks.get(data.getString(0x0000)); + PacketInExRunEvent.callbacks.remove(data.getString(0x0000)); + for (Callback> callback : callbacks) { + callback.run(new ObjectMap<>((Map) data.getObject(0x0001))); + } + } + } + + @Override + public int version() { + return 0x0001; + } + + public static void callback(String event, Callback> callback) { + List>> callbacks = (PacketInExRunEvent.callbacks.keySet().contains(event))? PacketInExRunEvent.callbacks.get(event):new ArrayList>>(); + callbacks.add(callback); + PacketInExRunEvent.callbacks.put(event, callbacks); + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketLinkProxy.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketLinkProxy.java new file mode 100644 index 00000000..9946c28d --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/Packet/PacketLinkProxy.java @@ -0,0 +1,78 @@ +package net.ME1312.SubServers.Velocity.Network.Packet; + +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubData.Client.Protocol.Initial.InitialPacket; +import net.ME1312.SubData.Client.Protocol.PacketObjectIn; +import net.ME1312.SubData.Client.Protocol.PacketObjectOut; +import net.ME1312.SubData.Client.SubDataSender; +import net.ME1312.SubServers.Velocity.ExProxy; +import net.ME1312.SubServers.Velocity.Library.Compatibility.Logger; +import net.ME1312.SubServers.Velocity.SubAPI; + +/** + * Link Proxy Packet + */ +public class PacketLinkProxy implements InitialPacket, PacketObjectIn, PacketObjectOut { + private ExProxy plugin; + private int channel; + + /** + * New PacketLinkProxy (In) + * + * @param plugin SubServers.Client + */ + public PacketLinkProxy(ExProxy plugin) { + if (Util.isNull(plugin)) throw new NullPointerException(); + this.plugin = plugin; + } + /** + * New PacketLinkProxy (Out) + * + * @param plugin SubServers.Client + * @param channel Channel ID + */ + public PacketLinkProxy(ExProxy plugin, int channel) { + if (Util.isNull(plugin)) throw new NullPointerException(); + this.plugin = plugin; + this.channel = channel; + } + + @Override + public ObjectMap send(SubDataSender client) { + ObjectMap json = new ObjectMap(); + json.set(0x0000, plugin.api.getName()); + json.set(0x0001, channel); + return json; + } + + @Override + public void receive(SubDataSender client, ObjectMap data) { + if (data.getInt(0x0001) == 0) { + try { + if (data.contains(0x0000)) Util.reflect(SubAPI.class.getDeclaredField("name"), plugin.api, data.getRawString(0x0000)); + setReady(client.getConnection()); + } catch (Throwable e) { + e.printStackTrace(); + } + } else { + Logger.get("SubData").info("Could not link name with proxy" + ((data.contains(0x0002))?": "+data.getRawString(0x0002):'.')); + try { + if (data.getInt(0x0001) == 2) { + if (!plugin.config.get().getMap("Settings").getMap("SubData").contains("Name")) { + plugin.config.get().getMap("Settings").getMap("SubData").set("Name", ""); + plugin.config.save(); + } + if (plugin.config.get().getMap("Settings").getMap("SubData").getRawString("Name").length() <= 0) + Logger.get("SubData").info("Use the proxy \"Name\" option to override auto-linking"); + } + } catch (Exception e) {} + new IllegalStateException().printStackTrace(); + } + } + + @Override + public int version() { + return 0x0001; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/SubProtocol.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/SubProtocol.java new file mode 100644 index 00000000..5429ea9c --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Network/SubProtocol.java @@ -0,0 +1,225 @@ +package net.ME1312.SubServers.Velocity.Network; + +import net.ME1312.Galaxi.Library.Callback.Callback; +import net.ME1312.Galaxi.Library.Container.Pair; +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.Galaxi.Library.Version.Version; +import net.ME1312.SubData.Client.SubDataClient; +import net.ME1312.SubData.Client.SubDataProtocol; +import net.ME1312.SubServers.Client.Common.Network.API.RemotePlayer; +import net.ME1312.SubServers.Client.Common.Network.API.Server; +import net.ME1312.SubServers.Client.Common.Network.Packet.*; +import net.ME1312.SubServers.Velocity.Event.SubNetworkConnectEvent; +import net.ME1312.SubServers.Velocity.Event.SubNetworkDisconnectEvent; +import net.ME1312.SubServers.Velocity.ExProxy; +import net.ME1312.SubServers.Velocity.Network.Packet.*; +import net.ME1312.SubServers.Velocity.Server.CachedPlayer; +import net.ME1312.SubServers.Velocity.Server.ServerData; +import net.ME1312.SubServers.Velocity.SubAPI; + +import com.velocitypowered.api.proxy.config.ProxyConfig; +import com.velocitypowered.api.proxy.server.RegisteredServer; + +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.UUID; +import java.util.logging.Logger; + +/** + * SubServers Protocol Class + */ +public class SubProtocol extends SubDataProtocol { + private static SubProtocol instance; + + @SuppressWarnings("deprecation") + protected SubProtocol() { + ExProxy plugin = SubAPI.getInstance().getInternals(); + + setName("SubServers 2"); + addVersion(new Version("2.16a+")); + + + // 00-0F: Object Link Packets + registerPacket(0x0000, PacketLinkProxy.class); + registerPacket(0x0000, new PacketLinkProxy(plugin)); + + + // 10-2F: Download Packets + registerPacket(0x0010, PacketDownloadLang.class); + registerPacket(0x0011, PacketDownloadPlatformInfo.class); + registerPacket(0x0012, PacketDownloadProxyInfo.class); + registerPacket(0x0013, PacketDownloadHostInfo.class); + registerPacket(0x0014, PacketDownloadGroupInfo.class); + registerPacket(0x0015, PacketDownloadServerInfo.class); + registerPacket(0x0016, PacketDownloadPlayerInfo.class); + registerPacket(0x0017, PacketCheckPermission.class); + registerPacket(0x0018, PacketCheckPermissionResponse.class); + + registerPacket(0x0010, new PacketDownloadLang(plugin)); + registerPacket(0x0011, new PacketDownloadPlatformInfo()); + registerPacket(0x0012, new PacketDownloadProxyInfo()); + registerPacket(0x0013, new PacketDownloadHostInfo()); + registerPacket(0x0014, new PacketDownloadGroupInfo()); + registerPacket(0x0015, new PacketDownloadServerInfo()); + registerPacket(0x0016, new PacketDownloadPlayerInfo()); + registerPacket(0x0017, new PacketCheckPermission()); + registerPacket(0x0018, new PacketCheckPermissionResponse()); + + + // 30-4F: Control Packets + registerPacket(0x0030, PacketCreateServer.class); + registerPacket(0x0031, PacketAddServer.class); + registerPacket(0x0032, PacketStartServer.class); + registerPacket(0x0033, PacketUpdateServer.class); + registerPacket(0x0034, PacketEditServer.class); + registerPacket(0x0035, PacketRestartServer.class); + registerPacket(0x0036, PacketCommandServer.class); + registerPacket(0x0037, PacketStopServer.class); + registerPacket(0x0038, PacketRemoveServer.class); + registerPacket(0x0039, PacketDeleteServer.class); + registerPacket(0x003B, PacketTransferPlayer.class); + registerPacket(0x003C, PacketDisconnectPlayer.class); + registerPacket(0x003D, PacketMessagePlayer.class); + + registerPacket(0x0030, new PacketCreateServer()); + registerPacket(0x0031, new PacketAddServer()); + registerPacket(0x0032, new PacketStartServer()); + registerPacket(0x0033, new PacketUpdateServer()); + registerPacket(0x0034, new PacketEditServer()); + registerPacket(0x0035, new PacketRestartServer()); + registerPacket(0x0036, new PacketCommandServer()); + registerPacket(0x0037, new PacketStopServer()); + registerPacket(0x0038, new PacketRemoveServer()); + registerPacket(0x0039, new PacketDeleteServer()); + registerPacket(0x003B, new PacketTransferPlayer()); + registerPacket(0x003C, new PacketDisconnectPlayer()); + registerPacket(0x003D, new PacketMessagePlayer()); + + + // 70-7F: External Sync Packets + //registerPacket(0x0070, PacketInExRunEvent.class); + //registerPacket(0x0071, PacketInExReset.class); + //registerPacket(0x0073, PacketInExReload.class); + registerPacket(0x0074, PacketExSyncPlayer.class); + registerPacket(0x0075, PacketExTransferPlayer.class); + registerPacket(0x0076, PacketExDisconnectPlayer.class); + registerPacket(0x0077, PacketExMessagePlayer.class); + + registerPacket(0x0070, new PacketInExRunEvent(plugin)); + registerPacket(0x0071, new PacketInExReset()); + registerPacket(0x0073, new PacketInExEditServer(plugin)); + registerPacket(0x0074, new PacketExSyncPlayer(plugin)); + registerPacket(0x0075, new PacketExTransferPlayer(plugin)); + registerPacket(0x0076, new PacketExDisconnectPlayer(plugin)); + registerPacket(0x0077, new PacketExMessagePlayer(plugin)); + } + + public static SubProtocol get() { + if (instance == null) + instance = new SubProtocol(); + + return instance; + } + + private Logger getLogger(int channel) { + return net.ME1312.SubServers.Velocity.Library.Compatibility.Logger.get("SubData" + ((channel != 0)?File.separator+"+"+channel:"")); + } + + @Override + protected SubDataClient sub(Callback scheduler, Logger logger, InetAddress address, int port, ObjectMap login) throws IOException { + ExProxy plugin = SubAPI.getInstance().getInternals(); + HashMap map = Util.getDespiteException(() -> Util.reflect(ExProxy.class.getDeclaredField("subdata"), plugin), null); + + int channel = 1; + while (map.keySet().contains(channel)) channel++; + final int fc = channel; + + SubDataClient subdata = super.open(scheduler, getLogger(fc), address, port, login); + map.put(fc, subdata); + subdata.sendPacket(new PacketLinkProxy(plugin, fc)); + subdata.on.closed(client -> map.remove(fc)); + + return subdata; + } + + @SuppressWarnings("deprecation") + @Override + public SubDataClient open(Callback scheduler, Logger logger, InetAddress address, int port) throws IOException { + ExProxy plugin = SubAPI.getInstance().getInternals(); + SubDataClient subdata = super.open(scheduler, logger, address, port); + HashMap map = Util.getDespiteException(() -> Util.reflect(ExProxy.class.getDeclaredField("subdata"), plugin), null); + + subdata.sendPacket(new PacketLinkProxy(plugin, 0)); + subdata.sendPacket(new PacketDownloadLang()); + subdata.sendPacket(new PacketDownloadPlatformInfo(platform -> { + if (plugin.lastReload != platform.getMap("subservers").getLong("last-reload")) { + net.ME1312.SubServers.Velocity.Library.Compatibility.Logger.get("SubServers").info("Resetting Server Data"); + for (RegisteredServer server : ExProxy.getInstance().getAllServers()) ExProxy.getInstance().unregisterServer(server.getServerInfo()); + plugin.servers.clear(); + plugin.lastReload = platform.getMap("subservers").getLong("last-reload"); + } /* + try { + ProxyConfig config = ExProxy.getInstance().getConfiguration(); // TODO maybe? + if (plugin.config.get().getMap("Sync", new ObjectMap<>()).getBoolean("Forced-Hosts", true)) Util.reflect(ListenerInfo.class.getDeclaredField("forcedHosts"), listeners.get(i), platform.getMap("bungee").getMapList("listeners").get(i).getMap("forced-hosts").get()); + if (plugin.config.get().getMap("Sync", new ObjectMap<>()).getBoolean("Motd", false)) Util.reflect(ListenerInfo.class.getDeclaredField("motd"), listeners.get(i), platform.getMap("bungee").getMapList("listeners").get(i).getRawString("motd")); + if (plugin.config.get().getMap("Sync", new ObjectMap<>()).getBoolean("Player-Limit", false)) Util.reflect(ListenerInfo.class.getDeclaredField("maxPlayers"), listeners.get(i), platform.getMap("bungee").getMapList("listeners").get(i).getInt("player-limit")); + if (plugin.config.get().getMap("Sync", new ObjectMap<>()).getBoolean("Server-Priorities", true)) Util.reflect(ListenerInfo.class.getDeclaredField("serverPriority"), listeners.get(i), platform.getMap("bungee").getMapList("listeners").get(i).getRawStringList("priorities")); + if (plugin.config.get().getMap("Sync", new ObjectMap<>()).getBoolean("Disabled-Commands", false)) Util.reflect(Configuration.class.getDeclaredField("disabledCommands"), plugin.getConfig(), platform.getMap("bungee").getRawStringList("disabled-cmds")); + if (plugin.config.get().getMap("Sync", new ObjectMap<>()).getBoolean("Player-Limit", false)) Util.reflect(Configuration.class.getDeclaredField("playerLimit"), plugin.getConfig(), platform.getMap("bungee").getInt("player-limit")); + } catch (Exception e) { + net.ME1312.SubServers.Velocity.Library.Compatibility.Logger.get("SubServers").info("Problem converting synced BungeeCord configuration options"); + e.printStackTrace(); + } */ + + ArrayList localPlayers = new ArrayList(); + for (UUID id : new ArrayList(plugin.rPlayers.keySet())) { + if (ExProxy.getInstance().getPlayer(id).isPresent()) { + localPlayers.add(plugin.rPlayers.get(id)); + } else { + plugin.rPlayerLinkS.remove(id); + plugin.rPlayerLinkP.remove(id); + plugin.rPlayers.remove(id); + } + } + subdata.sendPacket(new PacketExSyncPlayer(null, localPlayers.toArray(new CachedPlayer[0]))); + + plugin.api.getServers(servers -> { + for (Server server : servers.values()) { + plugin.merge(server); + } + + plugin.api.getRemotePlayers(players -> { + for (RemotePlayer player : players.values()) { + plugin.rPlayerLinkP.put(player.getUniqueId(), player.getProxyName().toLowerCase()); + plugin.rPlayers.put(player.getUniqueId(), (CachedPlayer) player); + + ExProxy.getInstance().getServer(player.getServerName()).map(RegisteredServer::getServerInfo).map(plugin::getData).ifPresent(server -> + plugin.rPlayerLinkS.put(player.getUniqueId(), server) + ); + } + }); + }); + + })); + subdata.on.ready(client -> ExProxy.getInstance().getEventManager().fire(new SubNetworkConnectEvent((SubDataClient) client))); + subdata.on.closed(client -> { + SubNetworkDisconnectEvent event = new SubNetworkDisconnectEvent(client.value(), client.key()); + ExProxy.getInstance().getEventManager().fire(event); + + if (plugin.running) { + Logger log = net.ME1312.SubServers.Velocity.Library.Compatibility.Logger.get("SubData"); + Util.isException(() -> Util.reflect(ExProxy.class.getDeclaredMethod("connect", Logger.class, Pair.class), plugin, log, client)); + } else map.put(0, null); + }); + + return subdata; + } + + public SubDataClient open(InetAddress address, int port) throws IOException { + return open(getLogger(0), address, port); + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Server/CachedPlayer.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Server/CachedPlayer.java new file mode 100644 index 00000000..1d0d2b37 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Server/CachedPlayer.java @@ -0,0 +1,209 @@ +package net.ME1312.SubServers.Velocity.Server; + +import net.ME1312.Galaxi.Library.Callback.Callback; +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.SubData.Client.DataClient; +import net.ME1312.SubData.Client.SubDataClient; +import net.ME1312.SubServers.Client.Common.Network.API.RemotePlayer; +import net.ME1312.SubServers.Velocity.ExProxy; +import net.ME1312.SubServers.Velocity.Library.Compatibility.ChatColor; +import net.ME1312.SubServers.Velocity.SubAPI; + +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.proxy.server.ServerInfo; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; + +import java.util.ArrayList; +import java.util.Optional; +import java.util.UUID; + +/** + * Cached RemotePlayer Data Class + */ +public class CachedPlayer extends RemotePlayer { + + /** + * Convert a Local Player to a Cached Remote Player + * + * @param player Local Player + * @return Raw representation of the Remote Player + */ + public static ObjectMap translate(Player player) { + ObjectMap raw = new ObjectMap(); + raw = new ObjectMap(); + raw.set("name", player.getGameProfile().getName()); + raw.set("id", player.getUniqueId()); + raw.set("address", player.getRemoteAddress().getAddress().getHostAddress() + ':' + player.getRemoteAddress().getPort()); + if (player.getCurrentServer().isPresent()) raw.set("server", player.getCurrentServer().get().getServerInfo().getName()); + if (SubAPI.getInstance().getName() != null) raw.set("proxy", SubAPI.getInstance().getName()); + return raw; + } + + /** + * Convert a Local Player to a Cached Remote Player + * + * @param player Local Player + */ + public CachedPlayer(Player player) { + this(translate(player)); + } + + /** + * Create a Cached Remote Player + * + * @param raw Raw representation of the Remote Player + */ + public CachedPlayer(ObjectMap raw) { + this(null, raw); + } + + /** + * Create a Cached Remote Player + * + * @param client SubData connection + * @param raw Raw representation of the Remote Player + */ + CachedPlayer(DataClient client, ObjectMap raw) { + super(client, raw); + } + + /** + * Get Local Player + * + * @return Local Player (or null when not local) + */ + public Player get() { + return get(getUniqueId()); + } + + private static Player get(UUID player) { + return ExProxy.getInstance().getPlayer(player).orElse(null); + } + + /** + * Gets the server this player is connected to. + * + * @return the server this player is connected to + */ + public ServerInfo getServer() { + String name = getServerName(); + if (name == null) { + return null; + } else { + Optional server = ExProxy.getInstance().getServer(name); + return server.map(RegisteredServer::getServerInfo).orElse(null); + } + } + + static { + // These overrides prevent sending unnecessary packets in ClientCommon + instance = new StaticImpl() { + @Override + protected RemotePlayer construct(DataClient client, ObjectMap raw) { + return new CachedPlayer(client, raw); + } + + @Override + protected void sendMessage(SubDataClient client, UUID[] players, String[] messages, Callback response) { + if (players != null && players.length > 0) { + ArrayList ids = new ArrayList(); + for (UUID id : players) { + Player local = get(id); + if (local != null) { + for (String s : messages) { + local.sendMessage(Component.text(s)); + } + } else { + ids.add(id); + } + } + + if (ids.size() == 0) { + response.run(0); + } else { + super.sendMessage(client, ids.toArray(new UUID[0]), messages, response); + } + } else { + super.sendMessage(client, players, messages, response); + } + } + + @Override + protected void sendRawMessage(SubDataClient client, UUID[] players, String[] messages, Callback response) { + if (players != null && players.length > 0) { + ArrayList ids = new ArrayList(); + Component[] components = null; + for (UUID id : players) { + Player local = get(id); + if (local != null) { + if (components == null) { + components = new Component[messages.length]; + for (int i = 0; i < components.length; ++i) components[i] = GsonComponentSerializer.gson().deserialize(messages[i]); + } + for (Component c : components) { + local.sendMessage(c); + } + } else { + ids.add(id); + } + } + + if (ids.size() == 0) { + response.run(0); + } else { + super.sendRawMessage(client, ids.toArray(new UUID[0]), messages, response); + } + } else { + super.sendRawMessage(client, players, messages, response); + } + } + + @Override + protected void transfer(SubDataClient client, UUID[] players, String server, Callback response) { + ArrayList ids = new ArrayList(); + Optional rs = ExProxy.getInstance().getServer(server.toLowerCase()); + int failures = 0; + for (UUID id : players) { + Player local = get(id); + if (local != null) { + if (rs.isPresent()) { + local.createConnectionRequest(rs.get()).fireAndForget(); + } else ++failures; + } else { + ids.add(id); + } + } + + if (ids.size() == 0) { + response.run(failures); + } else { + final int ff = failures; + super.transfer(client, ids.toArray(new UUID[0]), server, i -> response.run(i + ff)); + } + } + + @Override + protected void disconnect(SubDataClient client, UUID[] players, String reason, Callback response) { + Component message = (reason == null)? Component.text().build() : ChatColor.convertColor(reason); + ArrayList ids = new ArrayList(); + for (UUID id : players) { + Player local = get(id); + if (local != null) { + local.disconnect(message); + } else { + ids.add(id); + } + } + + if (ids.size() == 0) { + response.run(0); + } else { + super.disconnect(client, ids.toArray(new UUID[0]), reason, response); + } + } + }; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Server/ServerData.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Server/ServerData.java new file mode 100644 index 00000000..51cf2aa0 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Server/ServerData.java @@ -0,0 +1,213 @@ +package net.ME1312.SubServers.Velocity.Server; + +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.SubData.Client.DataSender; +import net.ME1312.SubData.Client.Library.ForwardedDataSender; +import net.ME1312.SubData.Client.SubDataClient; +import net.ME1312.SubServers.Velocity.SubAPI; + +import com.velocitypowered.api.permission.PermissionSubject; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.server.ServerInfo; + +import java.net.InetSocketAddress; +import java.util.*; + +/** + * Server Class + */ +public class ServerData { + private final ServerInfo info; + private String motd; + private boolean restricted; + private HashMap subdata = new HashMap(); + public List whitelist = new ArrayList(); + private String nick = null; + private boolean hidden; + private final String signature; + + public ServerData(String signature, String name, String display, InetSocketAddress address, Map subdata, String motd, boolean hidden, boolean restricted, Collection whitelist) { + this.info = new ServerInfo(name, address); + this.motd = motd; + this.restricted = restricted; + this.signature = signature; + this.whitelist.addAll(whitelist); + this.hidden = hidden; + setDisplayName(display); + + for (int channel : subdata.keySet()) + setSubData(subdata.get(channel), channel); + } + + /** + * Gets the SubData Client Channel IDs + * + * @return SubData Client Channel ID Array + */ + public DataSender[] getSubData() { + Integer[] keys = subdata.keySet().toArray(new Integer[0]); + DataSender[] channels = new DataSender[keys.length]; + Arrays.sort(keys); + for (int i = 0; i < keys.length; ++i) channels[i] = (subdata.getOrDefault(keys[i], null) == null)? null : new ForwardedDataSender((SubDataClient) SubAPI.getInstance().getSubDataNetwork()[0], subdata.get(keys[i])); + return channels; + } + + /** + * Link a SubData Client to this Object + * + * @param client Client to Link + * @param channel Channel ID + */ + public void setSubData(UUID client, int channel) { + if (channel < 0) throw new IllegalArgumentException("Subchannel ID cannot be less than zero"); + if (client != null || channel == 0) { + if (!subdata.keySet().contains(channel) || (channel == 0 && (client == null || subdata.get(channel) == null))) { + subdata.put(channel, client); + } + } else { + subdata.remove(channel); + } + } + + /** + * Get the underlying ServerInfo of this Server + * + * @return ServerInfo + */ + public ServerInfo get() { + return info; + } + + /** + * Get the Address of this Server + * + * @return Server Address + */ + public InetSocketAddress getAddress() { + return info.getAddress(); + } + + /** + * Get the Name of this Server + * + * @return Server Name + */ + public String getName() { + return info.getName(); + } + + /** + * Get the Display Name of this Server + * + * @return Display Name + */ + public String getDisplayName() { + return (nick == null)?getName():nick; + } + + /** + * Sets the Display Name for this Server + * + * @param value Value (or null to reset) + */ + public void setDisplayName(String value) { + if (value == null || value.length() == 0 || getName().equals(value)) { + this.nick = null; + } else { + this.nick = value; + } + } + + /** + * If the server is hidden from players + * + * @return Hidden Status + */ + public boolean isHidden() { + return hidden; + } + + /** + * Set if the server is hidden from players + * + * @param value Value + */ + public void setHidden(boolean value) { + if (Util.isNull(value)) throw new NullPointerException(); + this.hidden = value; + } + + /** + * Gets the MOTD of the Server + * + * @return Server MOTD + */ + public String getMotd() { + return motd; + } + + /** + * Sets the MOTD of the Server + * + * @param value Value + */ + public void setMotd(String value) { + this.motd = value; + } + + /** + * Gets if the Server is Restricted + * + * @return Restricted Status + */ + public boolean isRestricted() { + return restricted; + } + + /** + * Sets if the Server is Restricted + * + * @param value Value + */ + public void setRestricted(boolean value) { + this.restricted = value; + } + + /** + * See if a player is whitelisted + * + * @param player Player + * @return Whitelisted Status + */ + public boolean canAccess(PermissionSubject player) { + return !restricted || player.hasPermission("bungeecord.server." + getName()) || (player instanceof Player && whitelist.contains(((Player) player).getUniqueId())); + } + + /** + * Add a player to the whitelist (for use with restricted servers) + * + * @param player Player to add + */ + public void whitelist(UUID player) { + if (Util.isNull(player)) throw new NullPointerException(); + whitelist.add(player); + } + + /** + * Remove a player to the whitelist + * + * @param player Player to remove + */ + public void unwhitelist(UUID player) { + whitelist.remove(player); + } + + /** + * Get the Signature of this Object + * + * @return Object Signature + */ + public final String getSignature() { + return signature; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Server/SubServerData.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Server/SubServerData.java new file mode 100644 index 00000000..4a9d7ce7 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/Server/SubServerData.java @@ -0,0 +1,37 @@ +package net.ME1312.SubServers.Velocity.Server; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Collection; +import java.util.Map; +import java.util.UUID; + +/** + * SubServer Class + */ +public class SubServerData extends ServerData { + private boolean running; + + public SubServerData(String signature, String name, String display, InetSocketAddress address, Map subdata, String motd, boolean hidden, boolean restricted, Collection whitelist, boolean running) { + super(signature, name, display, address, subdata, motd, hidden, restricted, whitelist); + this.running = running; + } + + /** + * Gets the Running Status + * + * @return Running Status + */ + public boolean isRunning() { + return running; + } + + /** + * Sets the Running Status + * + * @param running Running Status + */ + public void setRunning(boolean running) { + this.running = running; + } +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/SubAPI.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/SubAPI.java new file mode 100644 index 00000000..2ae1b256 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/SubAPI.java @@ -0,0 +1,222 @@ +package net.ME1312.SubServers.Velocity; + +import net.ME1312.Galaxi.Library.UniversalFile; +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.Galaxi.Library.Version.Version; +import net.ME1312.SubData.Client.DataClient; +import net.ME1312.SubData.Client.DataProtocol; +import net.ME1312.SubData.Client.SubDataClient; +import net.ME1312.SubServers.Client.Common.ClientAPI; +import net.ME1312.SubServers.Velocity.Server.CachedPlayer; +import net.ME1312.SubServers.Velocity.Server.ServerData; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.proxy.server.ServerInfo; + +import java.util.*; + +/** + * SubAPI Class + */ +public final class SubAPI extends ClientAPI { + private final ExProxy plugin; + private static SubAPI api; + String name; + + SubAPI(ExProxy plugin) { + this.plugin = plugin; + GAME_VERSION = getGameVersion(); + api = this; + } + + /** + * Gets the SubAPI Methods + * + * @return SubAPI + */ + public static SubAPI getInstance() { + return api; + } + + /** + * Gets the SubServers Internals + * + * @deprecated Use SubAPI Methods when available + * @return SubPlugin Internals + */ + @Deprecated + public ExProxy getInternals() { + return plugin; + } + + /** + * Get the Proxy Name + * + * @return Proxy Name + */ + public String getName() { + return name; + } + + /** + * Get the number of players on this network across all known proxies + * + * @return Remote Player Count + */ + public int getRemotePlayerCount() { + return plugin.rPlayers.size(); + } + + /** + * Get players on this server across all known proxies (Cached) + * + * @param server Server to search + * @return Remote Player Map + */ + public Map getRemotePlayers(ServerInfo server) { + ServerData sd = plugin.getData(server); + if (sd != null) { + HashMap players = new HashMap(); + for (UUID id : Util.getBackwards(plugin.rPlayerLinkS, sd)) + players.put(id, plugin.rPlayers.get(id)); + return players; + } else { + return new HashMap<>(); + } + } + + /** + * Gets players on this network across all known proxies (Cached) + * + * @return Remote Player Map + */ + public Map getRemotePlayers() { + return new HashMap(plugin.rPlayers); + } + + /** + * Gets a player on this network by searching across all known proxies (Cached) + * + * @param name Player name + * @return Remote Player + */ + public CachedPlayer getRemotePlayer(String name) { + if (Util.isNull(name)) throw new NullPointerException(); + for (CachedPlayer player : getRemotePlayers().values()) { + if (player.getName().equalsIgnoreCase(name)) return player; + } + return null; + } + + /** + * Gets a player on this network by searching across all known proxies (Cached) + * + * @param id Player UUID + * @return Remote Player + */ + public CachedPlayer getRemotePlayer(UUID id) { + if (Util.isNull(id)) throw new NullPointerException(); + return getRemotePlayers().getOrDefault(id, null); + } + + /** + * Gets the SubData Network Connections + * + * @return SubData Network Connections + */ + public DataClient[] getSubDataNetwork() { + LinkedList keys = new LinkedList(plugin.subdata.keySet()); + LinkedList channels = new LinkedList(); + Collections.sort(keys); + for (Integer channel : keys) channels.add(plugin.subdata.get(channel)); + return channels.toArray(new DataClient[0]); + } + + /** + * Gets the SubData Network Protocol + * + * @return SubData Network Protocol + */ + public DataProtocol getSubDataProtocol() { + return plugin.subprotocol; + } + + /** + * Gets the current SubServers Lang Channels + * + * @return SubServers Lang Channel list + */ + public Collection getLangChannels() { + return plugin.lang.value().keySet(); + } + + /** + * Gets values from the SubServers Lang + * + * @param channel Lang Channel + * @return Lang Value + */ + public Map getLang(String channel) { + if (Util.isNull(channel)) throw new NullPointerException(); + return new LinkedHashMap<>(plugin.lang.value().get(channel.toLowerCase())); + } + + /** + * Gets the Runtime Directory + * + * @return Directory + */ + public UniversalFile getRuntimeDirectory() { + return plugin.dir; + } + + /** + * Gets the SubServers Version + * + * @return SubServers Version + */ + public Version getPluginVersion() { + return plugin.version; + } + + /** + * Gets the SubServers Build Version + * + * @return SubServers Build Version (or null if unsigned) + */ + public Version getPluginBuild() { + return (ExProxy.class.getPackage().getSpecificationTitle() != null)?new Version(ExProxy.class.getPackage().getSpecificationTitle()):null; + } + + /** + * Gets the BungeeCord Version + * + * @return BungeeCord Version + */ + public Version getProxyVersion() { + return new Version(ExProxy.getInstance().getVersion().getVersion()); + } + + /** + * Get an array of compatible Minecraft Versions + * + * @return Minecraft Versions + */ + public Version[] getGameVersion() { + if (GAME_VERSION == null) { + if (System.getProperty("subservers.minecraft.version", "").length() > 0) { + return new Version[]{new Version(System.getProperty("subservers.minecraft.version"))}; + } else if (Util.getDespiteException(() -> ProtocolVersion.SUPPORTED_VERSIONS != null, false)) { + List versions = new LinkedList(); + for (ProtocolVersion protocol : ProtocolVersion.SUPPORTED_VERSIONS) for (String version : protocol.getVersionsSupportedBy()) versions.add(new Version(version)); + Collections.sort(versions); + return versions.toArray(new Version[versions.size()]); + } else { + plugin.out.warn("Could not determine compatible Minecraft version(s); Now using 1.x.x as a placeholder."); + plugin.out.warn("Use this launch argument to specify a compatible Minecraft version: -Dsubservers.minecraft.version=1.x.x"); + return new Version[]{new Version("1.x.x")}; + } + } else return GAME_VERSION; + } + private final Version[] GAME_VERSION; +} diff --git a/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/SubCommand.java b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/SubCommand.java new file mode 100644 index 00000000..d4ec9860 --- /dev/null +++ b/SubServers.Sync/velocity/src/net/ME1312/SubServers/Velocity/SubCommand.java @@ -0,0 +1,1540 @@ +package net.ME1312.SubServers.Velocity; + +import net.ME1312.Galaxi.Library.AsyncConsolidator; +import net.ME1312.Galaxi.Library.Callback.Callback; +import net.ME1312.Galaxi.Library.Callback.ReturnRunnable; +import net.ME1312.Galaxi.Library.Container.ContainedPair; +import net.ME1312.Galaxi.Library.Container.Container; +import net.ME1312.Galaxi.Library.Container.Pair; +import net.ME1312.Galaxi.Library.Container.Value; +import net.ME1312.Galaxi.Library.Map.ObjectMap; +import net.ME1312.Galaxi.Library.Platform; +import net.ME1312.Galaxi.Library.Util; +import net.ME1312.Galaxi.Library.Version.Version; +import net.ME1312.SubData.Client.SubDataClient; +import net.ME1312.SubData.Client.SubDataSender; +import net.ME1312.SubServers.Client.Common.Network.API.*; +import net.ME1312.SubServers.Client.Common.Network.Packet.PacketCreateServer; +import net.ME1312.SubServers.Client.Common.Network.Packet.PacketUpdateServer; +import net.ME1312.SubServers.Velocity.Library.Compatibility.ChatColor; +import net.ME1312.SubServers.Velocity.Network.Packet.PacketCheckPermission; +import net.ME1312.SubServers.Velocity.Network.Packet.PacketInExRunEvent; +import net.ME1312.SubServers.Velocity.Server.CachedPlayer; +import net.ME1312.SubServers.Velocity.Server.ServerData; +import net.ME1312.SubServers.Velocity.Server.SubServerData; + +import com.google.gson.Gson; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.RawCommand; +import com.velocitypowered.api.command.SimpleCommand; +import com.velocitypowered.api.proxy.ConsoleCommandSource; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.proxy.server.ServerInfo; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.*; +import java.util.concurrent.TimeUnit; + +@SuppressWarnings("deprecation") +public final class SubCommand implements SimpleCommand { + static HashMap>> permitted = new HashMap>>(); + private TreeMap proxyCache = new TreeMap(); + private TreeMap hostCache = new TreeMap(); + private TreeMap> groupCache = new TreeMap>(); + private Proxy proxyMasterCache = null; + private long cacheDate = 0; + private ExProxy plugin; + + SubCommand(ExProxy plugin) { + this.plugin = plugin; + } + + @SuppressWarnings("unchecked") + @Override + public void execute(Invocation invocation) { + CommandSource sender = invocation.source(); + String label = '/' + invocation.alias(); + String[] args = invocation.arguments(); + + if (!(sender instanceof Player)) { + if (plugin.api.getSubDataNetwork()[0] == null || plugin.api.getSubDataNetwork()[0].isClosed()) { + new IllegalStateException("SubData is not connected").printStackTrace(); + if (!(sender instanceof ConsoleCommandSource)) sender.sendMessage(Component.text("An exception has occurred while running this command", NamedTextColor.RED)); + } else { + if (args.length > 0) { + if (args[0].equalsIgnoreCase("help") || args[0].equalsIgnoreCase("?")) { + for (String s : printHelp()) sender.sendMessage(Component.text(s)); + } else if (args[0].equalsIgnoreCase("version") || args[0].equalsIgnoreCase("ver")) { + sender.sendMessage(Component.text("SubServers > These are the platforms and versions that are running SubServers.Sync:")); + sender.sendMessage(Component.text(" " + Platform.getSystemName() + ' ' + Platform.getSystemVersion() + ((Platform.getSystemBuild() != null)?" (" + Platform.getSystemBuild() + ')':"") + ((!Platform.getSystemArchitecture().equals("unknown"))?" [" + Platform.getSystemArchitecture() + ']':"") + ',')); + sender.sendMessage(Component.text(" Java " + Platform.getJavaVersion() + ((!Platform.getJavaArchitecture().equals("unknown"))?" [" + Platform.getJavaArchitecture() + ']':"") + ',')); + sender.sendMessage(Component.text(" " + ExProxy.getInstance().getVersion().getName() + ' ' + ExProxy.getInstance().getVersion().getVersion() + ',')); + sender.sendMessage(Component.text(" SubServers.Sync v" + plugin.version.toExtendedString() + ((plugin.api.getPluginBuild() != null)?" (" + plugin.api.getPluginBuild() + ')':""))); + sender.sendMessage(Component.text("")); + new Thread(() -> { + try { + ObjectMap tags = new ObjectMap(new Gson().fromJson("{\"tags\":" + Util.readAll(new BufferedReader(new InputStreamReader(new URL("https://api.github.com/repos/ME1312/SubServers-2/git/refs/tags").openStream(), Charset.forName("UTF-8")))) + '}', Map.class)); + List versions = new LinkedList(); + + Version updversion = plugin.version; + int updcount = 0; + for (ObjectMap tag : tags.getMapList("tags")) versions.add(Version.fromString(tag.getString("ref").substring(10))); + Collections.sort(versions); + for (Version version : versions) { + if (version.compareTo(updversion) > 0) { + updversion = version; + updcount++; + } + } + if (updcount == 0) { + sender.sendMessage(Component.text("You are on the latest version.")); + } else { + sender.sendMessage(Component.text("SubServers.Sync v" + updversion + " is available. You are " + updcount + " version" + ((updcount == 1)?"":"s") + " behind.")); + } + } catch (Exception e) { + } + }, "SubServers.Sync::Update_Check").start(); + } else if (args[0].equalsIgnoreCase("list")) { + plugin.api.getGroups(groups -> plugin.api.getHosts(hosts -> plugin.api.getServers(servers -> plugin.api.getMasterProxy(proxymaster -> plugin.api.getProxies(proxies -> { + int i = 0; + boolean sent = false; + String div = ChatColor.RESET + ", "; + if (groups.keySet().size() > 0) { + sender.sendMessage(Component.text("Group/Server List:")); + for (String group : groups.keySet()) { + String message = " "; + message += ChatColor.GOLD + group + ChatColor.RESET + ": "; + for (Server server : groups.get(group)) { + if (i != 0) message += div; + if (!(server instanceof SubServer)) { + message += ChatColor.WHITE; + } else if (((SubServer) server).isRunning()) { + if (((SubServer) server).getStopAction() == SubServer.StopAction.REMOVE_SERVER || ((SubServer) server).getStopAction() == SubServer.StopAction.RECYCLE_SERVER || ((SubServer) server).getStopAction() == SubServer.StopAction.DELETE_SERVER) { + message += ChatColor.AQUA; + } else { + message += ChatColor.GREEN; + } + } else if (((SubServer) server).isAvailable() && ((SubServer) server).isEnabled() && ((SubServer) server).getCurrentIncompatibilities().size() == 0) { + message += ChatColor.YELLOW; + } else { + message += ChatColor.RED; + } + message += server.getDisplayName() + ((server.getName().equals(server.getDisplayName()))?"":" ["+server.getName()+']'); + i++; + } + if (i == 0) message += ChatColor.RESET + "(none)"; + sender.sendMessage(ChatColor.convertColor(message)); + i = 0; + sent = true; + } + if (!sent) sender.sendMessage(Component.text("(none)")); + sent = false; + } + sender.sendMessage(Component.text("Host/SubServer List:")); + for (Host host : hosts.values()) { + String message = " "; + if (host.isAvailable() && host.isEnabled()) { + message += ChatColor.AQUA; + } else { + message += ChatColor.RED; + } + message += host.getDisplayName() + " [" + ((host.getName().equals(host.getDisplayName()))?"":host.getName()+ChatColor.stripColor(div)) + host.getAddress().getHostAddress() + "]" + ChatColor.RESET + ": "; + for (SubServer subserver : host.getSubServers().values()) { + if (i != 0) message += div; + if (subserver.isRunning()) { + if (subserver.getStopAction() == SubServer.StopAction.REMOVE_SERVER || subserver.getStopAction() == SubServer.StopAction.RECYCLE_SERVER || subserver.getStopAction() == SubServer.StopAction.DELETE_SERVER) { + message += ChatColor.AQUA; + } else { + message += ChatColor.GREEN; + } + } else if (subserver.isAvailable() && subserver.isEnabled() && subserver.getCurrentIncompatibilities().size() == 0) { + message += ChatColor.YELLOW; + } else { + message += ChatColor.RED; + } + message += subserver.getDisplayName() + " [" + ((subserver.getName().equals(subserver.getDisplayName()))?"":subserver.getName()+ChatColor.stripColor(div)) + subserver.getAddress().getPort() + "]"; + i++; + } + if (i == 0) message += ChatColor.RESET + "(none)"; + sender.sendMessage(ChatColor.convertColor(message)); + i = 0; + sent = true; + } + if (!sent) sender.sendMessage(Component.text("(none)")); + sender.sendMessage(Component.text("Server List:")); + String message = " "; + for (Server server : servers.values()) if (!(server instanceof SubServer)) { + if (i != 0) message += div; + message += ChatColor.WHITE + server.getDisplayName() + " [" + ((server.getName().equals(server.getDisplayName()))?"":server.getName()+ChatColor.stripColor(div)) + server.getAddress().getAddress().getHostAddress()+':'+server.getAddress().getPort() + "]"; + i++; + } + if (i == 0) message += ChatColor.RESET + "(none)"; + sender.sendMessage(ChatColor.convertColor(message)); + if (proxies.keySet().size() > 0) { + sender.sendMessage(Component.text("Proxy List:")); + message = " (master)"; + for (Proxy proxy : proxies.values()) { + message += div; + if (proxy.getSubData()[0] != null) { + message += ChatColor.AQUA; + } else { + message += ChatColor.RED; + } + message += proxy.getDisplayName() + ((proxy.getName().equals(proxy.getDisplayName()))?"":" ["+proxy.getName()+']'); + } + sender.sendMessage(ChatColor.convertColor(message)); + } + }))))); + } else if (args[0].equalsIgnoreCase("info") || args[0].equalsIgnoreCase("status")) { + if (args.length > 1) { + String type = (args.length > 2)?args[1]:null; + String name = args[(type != null)?2:1]; + + Runnable getPlayer = () -> plugin.api.getRemotePlayer(name, player -> { + if (player != null) { + sender.sendMessage(ChatColor.convertColor("SubServers > Info on player: " + ChatColor.WHITE + player.getName())); + if (player.getProxyName() != null) sender.sendMessage(ChatColor.convertColor(" -> Proxy: " + ChatColor.WHITE + player.getProxyName())); + if (player.getServerName() != null) sender.sendMessage(ChatColor.convertColor(" -> Server: " + ChatColor.WHITE + player.getServerName())); + if (player.getAddress() != null) sender.sendMessage(ChatColor.convertColor(" -> Address: " + ChatColor.WHITE + player.getAddress().getAddress().getHostAddress() + ':' + player.getAddress().getPort())); + sender.sendMessage(ChatColor.convertColor(" -> UUID: " + ChatColor.AQUA + player.getUniqueId())); + } else { + if (type == null) { + sender.sendMessage(Component.text("SubServers > There is no object with that name")); + } else { + sender.sendMessage(Component.text("SubServers > There is no player with that name")); + } + } + }); + Runnable getServer = () -> plugin.api.getServer(name, server -> { + if (server != null) { + sender.sendMessage(ChatColor.convertColor("SubServers > Info on " + ((server instanceof SubServer)?"sub":"") + "server: " + ChatColor.WHITE + server.getDisplayName())); + if (!server.getName().equals(server.getDisplayName())) sender.sendMessage(ChatColor.convertColor(" -> System Name: " + ChatColor.WHITE + server.getName())); + if (server instanceof SubServer) { + sender.sendMessage(ChatColor.convertColor(" -> Available: " + ((((SubServer) server).isAvailable())?ChatColor.GREEN+"yes":ChatColor.RED+"no"))); + sender.sendMessage(ChatColor.convertColor(" -> Enabled: " + ((((SubServer) server).isEnabled())?ChatColor.GREEN+"yes":ChatColor.RED+"no"))); + if (!((SubServer) server).isEditable()) sender.sendMessage(ChatColor.convertColor(" -> Editable: " + ChatColor.RED + "no")); + sender.sendMessage(ChatColor.convertColor(" -> Host: " + ChatColor.WHITE + ((SubServer) server).getHost())); + if (((SubServer) server).getTemplate() != null) sender.sendMessage(ChatColor.convertColor(" -> Template: " + ChatColor.WHITE + ((SubServer) server).getHost())); + } + if (server.getGroups().size() > 0) sender.sendMessage(ChatColor.convertColor(" -> Group" + ((server.getGroups().size() > 1)?"s:":": " + ChatColor.WHITE + server.getGroups().get(0)))); + if (server.getGroups().size() > 1) for (String group : server.getGroups()) sender.sendMessage(ChatColor.convertColor(" - " + ChatColor.WHITE + group)); + sender.sendMessage(ChatColor.convertColor(" -> Address: " + ChatColor.WHITE + server.getAddress().getAddress().getHostAddress()+':'+server.getAddress().getPort())); + if (server instanceof SubServer) sender.sendMessage(ChatColor.convertColor(" -> " + ((((SubServer) server).isOnline())?"Online":"Running") + ": " + ((((SubServer) server).isRunning())?ChatColor.GREEN+"yes":ChatColor.RED+"no"))); + if (!(server instanceof SubServer) || ((SubServer) server).isRunning()) { + sender.sendMessage(ChatColor.convertColor(" -> Connected: " + ((server.getSubData()[0] != null)?ChatColor.GREEN+"yes"+((server.getSubData().length > 1)?ChatColor.AQUA+" +"+(server.getSubData().length-1)+" subchannel"+((server.getSubData().length == 2)?"":"s"):""):ChatColor.RED+"no"))); + sender.sendMessage(ChatColor.convertColor(" -> Players: " + ChatColor.AQUA + server.getRemotePlayers().size() + " online")); + } + sender.sendMessage(ChatColor.convertColor(" -> MOTD: " + ChatColor.WHITE + ChatColor.stripColor(server.getMotd()))); + if (server instanceof SubServer && ((SubServer) server).getStopAction() != SubServer.StopAction.NONE) sender.sendMessage(ChatColor.convertColor(" -> Stop Action: " + ChatColor.WHITE + ((SubServer) server).getStopAction().toString())); + sender.sendMessage(ChatColor.convertColor(" -> Signature: " + ChatColor.AQUA + server.getSignature())); + if (server instanceof SubServer) sender.sendMessage(ChatColor.convertColor(" -> Logging: " + ((((SubServer) server).isLogging())?ChatColor.GREEN+"yes":ChatColor.RED+"no"))); + sender.sendMessage(ChatColor.convertColor(" -> Restricted: " + ((server.isRestricted())?ChatColor.GREEN+"yes":ChatColor.RED+"no"))); + if (server instanceof SubServer && ((SubServer) server).getIncompatibilities().size() > 0) { + List current = new ArrayList(); + for (String other : ((SubServer) server).getCurrentIncompatibilities()) current.add(other.toLowerCase()); + sender.sendMessage(Component.text(" -> Incompatibilities:")); + for (String other : ((SubServer) server).getIncompatibilities()) sender.sendMessage(ChatColor.convertColor(" - " + ((current.contains(other.toLowerCase()))?ChatColor.WHITE:ChatColor.GRAY) + other)); + } + sender.sendMessage(ChatColor.convertColor(" -> Hidden: " + ((server.isHidden())?ChatColor.GREEN+"yes":ChatColor.RED+"no"))); + } else { + if (type == null) { + getPlayer.run(); + } else { + sender.sendMessage(Component.text("SubServers > There is no server with that name")); + } + } + }); + Runnable getGroup = () -> plugin.api.getGroup(name, group -> { + if (group != null) { + sender.sendMessage(ChatColor.convertColor("SubServers > Info on group: " + ChatColor.WHITE + group.key())); + sender.sendMessage(ChatColor.convertColor(" -> Servers: " + ((group.value().size() <= 0)?ChatColor.GRAY + "(none)":ChatColor.AQUA.toString() + group.value().size()))); + for (Server server : group.value()) sender.sendMessage(ChatColor.convertColor(" - " + ChatColor.WHITE + server.getDisplayName() + ((server.getName().equals(server.getDisplayName()))?"":" ["+server.getName()+']'))); + } else { + if (type == null) { + getServer.run(); + } else { + sender.sendMessage(Component.text("SubServers > There is no group with that name")); + } + } + }); + Runnable getHost = () -> plugin.api.getHost(name, host -> { + if (host != null) { + sender.sendMessage(ChatColor.convertColor("SubServers > Info on host: " + ChatColor.WHITE + host.getDisplayName())); + if (!host.getName().equals(host.getDisplayName())) sender.sendMessage(ChatColor.convertColor(" -> System Name: " + ChatColor.WHITE + host.getName())); + sender.sendMessage(ChatColor.convertColor(" -> Available: " + ((host.isAvailable())?ChatColor.GREEN+"yes":ChatColor.RED+"no"))); + sender.sendMessage(ChatColor.convertColor(" -> Enabled: " + ((host.isEnabled())?ChatColor.GREEN+"yes":ChatColor.RED+"no"))); + sender.sendMessage(ChatColor.convertColor(" -> Address: " + ChatColor.WHITE + host.getAddress().getHostAddress())); + if (host.getSubData().length > 0) sender.sendMessage(ChatColor.convertColor(" -> Connected: " + ((host.getSubData()[0] != null)?ChatColor.GREEN+"yes"+((host.getSubData().length > 1)?ChatColor.AQUA+" +"+(host.getSubData().length-1)+" subchannel"+((host.getSubData().length == 2)?"":"s"):""):ChatColor.RED+"no"))); + sender.sendMessage(ChatColor.convertColor(" -> SubServers: " + ((host.getSubServers().keySet().size() <= 0)?ChatColor.GRAY + "(none)":ChatColor.AQUA.toString() + host.getSubServers().keySet().size()))); + for (SubServer subserver : host.getSubServers().values()) sender.sendMessage(ChatColor.convertColor(" - " + ((subserver.isEnabled())?ChatColor.WHITE:ChatColor.GRAY) + subserver.getDisplayName() + ((subserver.getName().equals(subserver.getDisplayName()))?"":" ["+subserver.getName()+']'))); + sender.sendMessage(ChatColor.convertColor(" -> Templates: " + ((host.getCreator().getTemplates().keySet().size() <= 0)?ChatColor.GRAY + "(none)":ChatColor.AQUA.toString() + host.getCreator().getTemplates().keySet().size()))); + for (SubCreator.ServerTemplate template : host.getCreator().getTemplates().values()) sender.sendMessage(ChatColor.convertColor(" - " + ((template.isEnabled())?ChatColor.WHITE:ChatColor.GRAY) + template.getDisplayName() + ((template.getName().equals(template.getDisplayName()))?"":" ["+template.getName()+']'))); + sender.sendMessage(ChatColor.convertColor(" -> Signature: " + ChatColor.AQUA + host.getSignature())); + } else { + if (type == null) { + getGroup.run(); + } else { + sender.sendMessage(Component.text("SubServers > There is no host with that name")); + } + } + }); + Runnable getProxy = () -> plugin.api.getProxy(name, proxy -> { + if (proxy != null) { + sender.sendMessage(ChatColor.convertColor("SubServers > Info on proxy: " + ChatColor.WHITE + proxy.getDisplayName())); + if (!proxy.getName().equals(proxy.getDisplayName())) sender.sendMessage(ChatColor.convertColor(" -> System Name: " + ChatColor.WHITE + proxy.getName())); + if (!proxy.isMaster()) sender.sendMessage(ChatColor.convertColor(" -> Connected: " + ((proxy.getSubData()[0] != null)?ChatColor.GREEN+"yes"+((proxy.getSubData().length > 1)?ChatColor.AQUA+" +"+(proxy.getSubData().length-1)+" subchannel"+((proxy.getSubData().length == 2)?"":"s"):""):ChatColor.RED+"no"))); + else if (!proxy.getDisplayName().toLowerCase().contains("master")) sender.sendMessage(ChatColor.convertColor(" -> Type: " + ChatColor.WHITE + "Master")); + sender.sendMessage(ChatColor.convertColor(" -> Players: " + ChatColor.AQUA + proxy.getPlayers().size() + " online")); + sender.sendMessage(ChatColor.convertColor(" -> Signature: " + ChatColor.AQUA + proxy.getSignature())); + } else { + if (type == null) { + getHost.run(); + } else { + sender.sendMessage(Component.text("SubServers > There is no proxy with that name")); + } + } + }); + + if (type == null) { + getProxy.run(); + } else { + switch (type.toLowerCase()) { + case "p": + case "proxy": + getProxy.run(); + break; + case "h": + case "host": + getHost.run(); + break; + case "g": + case "group": + getGroup.run(); + break; + case "s": + case "server": + case "subserver": + getServer.run(); + break; + case "player": + getPlayer.run(); + break; + default: + sender.sendMessage(Component.text("SubServers > There is no object type with that name")); + } + } + } else { + sender.sendMessage(Component.text("SubServers > Usage: " + label + " " + args[0].toLowerCase() + " [proxy|host|group|server|player] ")); + } + } else if (args[0].equalsIgnoreCase("start")) { + if (args.length > 1) { + selectServers(sender, args, 1, true, select -> { + if (select.subservers.length > 0) { + Container success = new Container(0); + Container running = new Container(0); + AsyncConsolidator merge = new AsyncConsolidator(() -> { + if (running.value > 0) sender.sendMessage(Component.text("SubServers > " + running.value + " subserver"+((running.value == 1)?" was":"s were") + " already running")); + if (success.value > 0) sender.sendMessage(Component.text("SubServers > Started " + success.value + " subserver"+((success.value == 1)?"":"s"))); + }); + for (SubServer server : select.subservers) { + merge.reserve(); + server.start(null, response -> { + switch (response) { + case 3: + case 4: + sender.sendMessage(Component.text("SubServers > Subserver " + server.getName() + " has disappeared")); + break; + case 5: + sender.sendMessage(Component.text("SubServers > The host for " + server.getName() + " is not available")); + break; + case 6: + sender.sendMessage(Component.text("SubServers > The host for " + server.getName() + " is not enabled")); + break; + case 7: + sender.sendMessage(Component.text("SubServers > Subserver " + server.getName() + " is not available")); + break; + case 8: + sender.sendMessage(Component.text("SubServers > SubServer " + server.getName() + " is not enabled")); + break; + case 9: + running.value++; + break; + case 10: + sender.sendMessage(Component.text("SubServers > Subserver " + server.getName() + " cannot start while incompatible server(s) are running")); + break; + case 0: + success.value++; + break; + } + merge.release(); + }); + } + } + }); + } else { + sender.sendMessage(Component.text("Usage: " + label + " " + args[0].toLowerCase() + " ")); + } + } else if (args[0].equalsIgnoreCase("restart")) { + if (args.length > 1) { + selectServers(sender, args, 1, true, select -> { + if (select.subservers.length > 0) { + // Step 5: Start the stopped Servers once more + Callback starter = server -> server.start(response -> { + switch (response) { + case 3: + case 4: + sender.sendMessage(Component.text("SubServers > Could not restart server: Subserver " + server.getName() + " has disappeared")); + break; + case 5: + sender.sendMessage(Component.text("SubServers > Could not restart server: The host for " + server.getName() + " is no longer available")); + break; + case 6: + sender.sendMessage(Component.text("SubServers > Could not restart server: The host for " + server.getName() + " is no longer enabled")); + break; + case 7: + sender.sendMessage(Component.text("SubServers > Could not restart server: Subserver " + server.getName() + " is no longer available")); + break; + case 8: + sender.sendMessage(Component.text("SubServers > Could not restart server: Subserver " + server.getName() + " is no longer enabled")); + break; + case 10: + sender.sendMessage(Component.text("SubServers > Could not restart server: Subserver " + server.getName() + " cannot start while incompatible server(s) are running")); + break; + case 9: + case 0: + // success! + break; + } + }); + + // Step 4: Listen for stopped Servers + final HashMap listening = new HashMap(); + PacketInExRunEvent.callback("SubStoppedEvent", new Callback>() { + @Override + public void run(ObjectMap json) { + try { + if (listening.size() > 0) { + PacketInExRunEvent.callback("SubStoppedEvent", this); + String name = json.getString("server").toLowerCase(); + if (listening.keySet().contains(name)) { + Timer timer = new Timer("SubServers.Sync::Server_Restart_Command_Handler(" + name + ")"); + timer.schedule(new TimerTask() { + @Override + public void run() { + starter.run(listening.get(name)); + listening.remove(name); + timer.cancel(); + } + }, 100); + } + } + } catch (Exception e) {} + } + }); + + + // Step 1-3: Restart Servers / Receive command Responses + Container success = new Container(0); + AsyncConsolidator merge = new AsyncConsolidator(() -> { + if (success.value > 0) sender.sendMessage(Component.text("SubServers > Restarting " + success.value + " subserver"+((success.value == 1)?"":"s"))); + }); + for (SubServer server : select.subservers) { + merge.reserve(); + listening.put(server.getName().toLowerCase(), server); + server.stop(response -> { + if (response != 0) listening.remove(server.getName().toLowerCase()); + switch (response) { + case 3: + case 4: + sender.sendMessage(Component.text("Could not restart server: Subserver " + server.getName() + " has disappeared")); + break; + case 5: + starter.run(server); + case 0: + success.value++; + break; + } + merge.release(); + }); + } + } + }); + } else { + sender.sendMessage(Component.text("Usage: " + label + " " + args[0].toLowerCase() + " ")); + } + } else if (args[0].equalsIgnoreCase("stop")) { + if (args.length > 1) { + selectServers(sender, args, 1, true, select -> { + if (select.subservers.length > 0) { + Container success = new Container(0); + Container running = new Container(0); + AsyncConsolidator merge = new AsyncConsolidator(() -> { + if (running.value > 0) sender.sendMessage(Component.text("SubServers > " + running.value + " subserver"+((running.value == 1)?" was":"s were") + " already offline")); + if (success.value > 0) sender.sendMessage(Component.text("SubServers > Stopping " + success.value + " subserver"+((success.value == 1)?"":"s"))); + }); + for (SubServer server : select.subservers) { + merge.reserve(); + server.stop(response -> { + switch (response) { + case 3: + case 4: + sender.sendMessage(Component.text("SubServers > Subserver " + server.getName() + " has disappeared")); + break; + case 5: + running.value++; + break; + case 0: + success.value++; + break; + } + merge.release(); + }); + } + } + }); + } else { + sender.sendMessage(Component.text("Usage: " + label + " " + args[0].toLowerCase() + " ")); + } + } else if (args[0].equalsIgnoreCase("kill") || args[0].equalsIgnoreCase("terminate")) { + if (args.length > 1) { + selectServers(sender, args, 1, true, select -> { + if (select.subservers.length > 0) { + Container success = new Container(0); + Container running = new Container(0); + AsyncConsolidator merge = new AsyncConsolidator(() -> { + if (running.value > 0) sender.sendMessage(Component.text("SubServers > " + running.value + " subserver"+((running.value == 1)?" was":"s were") + " already offline")); + if (success.value > 0) sender.sendMessage(Component.text("SubServers > Terminated " + success.value + " subserver"+((success.value == 1)?"":"s"))); + }); + for (SubServer server : select.subservers) { + merge.reserve(); + server.terminate(response -> { + switch (response) { + case 3: + case 4: + sender.sendMessage(Component.text("SubServers > Subserver " + server.getName() + " has disappeared")); + break; + case 5: + running.value++; + break; + case 0: + success.value++; + break; + } + merge.release(); + }); + } + } + }); + } else { + sender.sendMessage(Component.text("Usage: " + label + " " + args[0].toLowerCase() + " ")); + } + } else if (args[0].equalsIgnoreCase("cmd") || args[0].equalsIgnoreCase("command")) { + if (args.length > 1) { + selectServers(sender, args, 1, true, select -> { + if (select.subservers.length > 0) { + if (select.args.length > 2) { + StringBuilder builder = new StringBuilder(select.args[2]); + for (int i = 3; i < select.args.length; i++) { + builder.append(' '); + builder.append(select.args[i]); + } + + Container success = new Container(0); + Container running = new Container(0); + AsyncConsolidator merge = new AsyncConsolidator(() -> { + if (running.value > 0) sender.sendMessage(Component.text("SubServers > " + running.value + " subserver"+((running.value == 1)?" was":"s were") + " offline")); + if (success.value > 0) sender.sendMessage(Component.text("SubServers > Sent command to " + success.value + " subserver"+((success.value == 1)?"":"s"))); + }); + for (SubServer server : select.subservers) { + merge.reserve(); + server.command(builder.toString(), response -> { + switch (response) { + case 3: + case 4: + sender.sendMessage(Component.text("SubServers > Subserver " + server.getName() + " has disappeared")); + break; + case 5: + running.value++; + break; + case 0: + success.value++; + break; + } + merge.release(); + }); + } + } else { + sender.sendMessage(Component.text("SubServers > No command was entered")); + } + } + }); + } else { + sender.sendMessage(Component.text("Usage: " + label + " " + args[0].toLowerCase() + " [Args...]")); + } + } else if (args[0].equalsIgnoreCase("create")) { + if (args.length > 3) { + if (args.length > 5 && Util.isException(() -> Integer.parseInt(args[5]))) { + sender.sendMessage(Component.text("SubServers > Invalid port number")); + } else { + ((SubDataClient) SubAPI.getInstance().getSubDataNetwork()[0]).sendPacket(new PacketCreateServer(null, args[1], args[2],args[3], (args.length > 4)?new Version(args[4]):null, (args.length > 5)?Integer.parseInt(args[5]):null, data -> { + switch (data.getInt(0x0001)) { + case 3: + case 4: + sender.sendMessage(Component.text("SubServers > There is already a subserver with that name")); + break; + case 5: + sender.sendMessage(Component.text("SubServers > There is no host with that name")); + break; + case 6: + sender.sendMessage(Component.text("SubServers > That host is not available")); + break; + case 7: + sender.sendMessage(Component.text("SubServers > That host is not enabled")); + break; + case 8: + sender.sendMessage(Component.text("SubServers > There is no template with that name")); + break; + case 9: + sender.sendMessage(Component.text("SubServers > That template is not enabled")); + break; + case 10: + sender.sendMessage(Component.text("SubServers > That template requires a Minecraft version to be specified")); + break; + case 11: + sender.sendMessage(Component.text("SubServers > Invalid port number")); + break; + case 0: + sender.sendMessage(Component.text("SubServers > Creating subserver " + args[1])); + break; + } + })); + } + } else { + sender.sendMessage(Component.text("SubServers > Usage: " + label + " " + args[0].toLowerCase() + "