diff --git a/api/pom.xml b/api/pom.xml index 43a4dd9432..9ef2b0da29 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-parent 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-api 1.11-SNAPSHOT jar @@ -20,25 +19,30 @@ - net.md-5 + tc.oc + minecraft-api + 1.11-SNAPSHOT + + + tc.oc bungeecord-chat ${project.version} compile - net.md-5 + tc.oc bungeecord-config ${project.version} compile - net.md-5 + tc.oc bungeecord-event ${project.version} compile - net.md-5 + tc.oc bungeecord-protocol ${project.version} compile diff --git a/api/src/main/java/net/md_5/bungee/api/CommandSender.java b/api/src/main/java/net/md_5/bungee/api/CommandSender.java index a35b3fd0c9..798ca60dda 100644 --- a/api/src/main/java/net/md_5/bungee/api/CommandSender.java +++ b/api/src/main/java/net/md_5/bungee/api/CommandSender.java @@ -1,27 +1,10 @@ package net.md_5.bungee.api; -import net.md_5.bungee.api.chat.BaseComponent; - import java.util.Collection; -public interface CommandSender +public interface CommandSender extends tc.oc.minecraft.api.command.CommandSender { - /** - * Get the unique name of this command sender. - * - * @return the senders username - */ - public String getName(); - - /** - * Send a message to this sender. - * - * @param message the message to send - */ - @Deprecated - public void sendMessage(String message); - /** * Send several messages to this sender. Each message will be sent * separately. @@ -31,20 +14,6 @@ public interface CommandSender @Deprecated public void sendMessages(String... messages); - /** - * Send a message to this sender. - * - * @param message the message to send - */ - public void sendMessage(BaseComponent... message); - - /** - * Send a message to this sender. - * - * @param message the message to send - */ - public void sendMessage(BaseComponent message); - /** * Get all groups this user is part of. This returns an unmodifiable * collection. @@ -67,14 +36,6 @@ public interface CommandSender */ public void removeGroups(String... groups); - /** - * Checks if this user has the specified permission node. - * - * @param permission the node to check - * @return whether they have this node - */ - public boolean hasPermission(String permission); - /** * Set a permission node for this user. * diff --git a/api/src/main/java/net/md_5/bungee/api/ProxyConfig.java b/api/src/main/java/net/md_5/bungee/api/ProxyConfig.java index edd82c1e0e..9147e7388d 100644 --- a/api/src/main/java/net/md_5/bungee/api/ProxyConfig.java +++ b/api/src/main/java/net/md_5/bungee/api/ProxyConfig.java @@ -32,9 +32,75 @@ public interface ProxyConfig /** * Set of all servers. + * + * @deprecated The returned map may be modified concurrently by the proxy. + * The safe alternative is {@link #getServersCopy()}. */ + @Deprecated Map getServers(); + /** + * Return all servers registered to this proxy, keyed by name. The returned map + * is an immutable snapshot of the actual server collection. It cannot be modified, + * and it will not change. + * + * @return all registered remote server destinations + */ + Map getServersCopy(); + + /** + * Gets the server info of a server. + * + * @param name the name of the configured server + * @return the server info belonging to the specified server + */ + ServerInfo getServerInfo(String name); + + /** + * Register the given server to the proxy. + * Any currently registered server with the same name will be replaced. + * + * @return the previously registered server with the same name, or null if there was no such server. + */ + ServerInfo addServer(ServerInfo server); + + /** + * Register all of the given servers to the proxy. + * + * @return true if any servers were added or replaced. + */ + boolean addServers(Collection servers); + + /** + * Un-register the server with the given name from the proxy. + * + * @return the server that was removed, or null if there is no server with the given name. + */ + ServerInfo removeServerNamed(String name); + + /** + * Un-register the given server from the proxy. + * The server is matched by name only, other fields in the given {@link ServerInfo} are ignored. + * + * @return the server that was removed, or null if there is no server with a matching name. + */ + ServerInfo removeServer(ServerInfo server); + + /** + * Un-register servers with any of the given names from the proxy. + * + * @return true if any servers were removed. + */ + boolean removeServersNamed(Collection names); + + /** + * Un-register all of the given servers from the proxy. + * The servers are matched by name only, other fields in the given {@link ServerInfo} are ignored. + * + * @return true if any servers were removed. + */ + boolean removeServers(Collection servers); + /** * Does the server authenticate with mojang */ @@ -79,4 +145,9 @@ public interface ProxyConfig * The favicon used for the server ping list. */ Favicon getFaviconObject(); + + /** + * Shutdown immediately if any plugins fail to load + */ + boolean isRequireAllPlugins(); } diff --git a/api/src/main/java/net/md_5/bungee/api/ProxyServer.java b/api/src/main/java/net/md_5/bungee/api/ProxyServer.java index 11c5b68576..8b02ebab75 100644 --- a/api/src/main/java/net/md_5/bungee/api/ProxyServer.java +++ b/api/src/main/java/net/md_5/bungee/api/ProxyServer.java @@ -16,7 +16,7 @@ import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.scheduler.TaskScheduler; -public abstract class ProxyServer +public abstract class ProxyServer implements tc.oc.minecraft.api.server.LocalServer { @Getter @@ -87,15 +87,41 @@ public static void setInstance(ProxyServer instance) */ public abstract ProxiedPlayer getPlayer(UUID uuid); + @Override + public ProxiedPlayer getPlayerExact(String name) + { + return getPlayer( name ); + } + + @Override + public Collection getOnlinePlayers() + { + return getPlayers(); + } + /** * Return all servers registered to this proxy, keyed by name. Unlike the * methods in {@link ConfigurationAdapter#getServers()}, this will not * return a fresh map each time. * * @return all registered remote server destinations + * + * @deprecated The returned map is part of the proxy's internal state, + * and may be modified concurrently by the proxy. + * The safe alternative is {@link #getServersCopy()}. */ + @Deprecated public abstract Map getServers(); + /** + * Return all servers registered to this proxy, keyed by name. The returned map + * is an immutable snapshot of the actual server collection. It cannot be modified, + * and it will not change. + * + * @return all registered remote server destinations + */ + public abstract Map getServersCopy(); + /** * Gets the server info of a server. * diff --git a/api/src/main/java/net/md_5/bungee/api/ServerPing.java b/api/src/main/java/net/md_5/bungee/api/ServerPing.java index 27b51849ba..3adfbd1363 100644 --- a/api/src/main/java/net/md_5/bungee/api/ServerPing.java +++ b/api/src/main/java/net/md_5/bungee/api/ServerPing.java @@ -140,7 +140,7 @@ public void setFavicon(Favicon favicon) @Deprecated public void setDescription(String description) { - this.description = new TextComponent( TextComponent.fromLegacyText( description ) ); + this.description = new TextComponent( TextComponent.fromLegacyText( description, false ) ); } @Deprecated diff --git a/api/src/main/java/net/md_5/bungee/api/config/ServerInfo.java b/api/src/main/java/net/md_5/bungee/api/config/ServerInfo.java index e9f1ff6696..74b2791d78 100644 --- a/api/src/main/java/net/md_5/bungee/api/config/ServerInfo.java +++ b/api/src/main/java/net/md_5/bungee/api/config/ServerInfo.java @@ -10,7 +10,7 @@ /** * Class used to represent a server to connect to. */ -public interface ServerInfo +public interface ServerInfo extends tc.oc.minecraft.api.server.Server { /** diff --git a/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java b/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java index 375815c446..891a24b018 100644 --- a/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java +++ b/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java @@ -2,7 +2,7 @@ import java.util.Locale; import java.util.Map; -import java.util.UUID; + import net.md_5.bungee.api.Callback; import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.CommandSender; @@ -14,24 +14,9 @@ * Represents a player who's connection is being connected to somewhere else, * whether it be a remote or embedded server. */ -public interface ProxiedPlayer extends Connection, CommandSender +public interface ProxiedPlayer extends Connection, CommandSender, tc.oc.minecraft.api.entity.Player { - /** - * Gets this player's display name. - * - * @return the players current display name - */ - String getDisplayName(); - - /** - * Sets this players display name to be used as their nametag and tab list - * name. - * - * @param name the name to set - */ - void setDisplayName(String name); - /** * Send a message to the specified screen position of this player. * @@ -128,13 +113,6 @@ public interface ProxiedPlayer extends Connection, CommandSender @Deprecated String getUUID(); - /** - * Get this connection's UUID, if set. - * - * @return the UUID - */ - UUID getUniqueId(); - /** * Gets this player's locale. * @@ -211,4 +189,6 @@ public interface ProxiedPlayer extends Connection, CommandSender * not occurred for this {@link ProxiedPlayer} yet. */ Map getModList(); + + Throwable getDisconnectException(); } diff --git a/api/src/main/java/net/md_5/bungee/api/event/ServerConnectEvent.java b/api/src/main/java/net/md_5/bungee/api/event/ServerConnectEvent.java index 17cfccd0a0..51824022bd 100644 --- a/api/src/main/java/net/md_5/bungee/api/event/ServerConnectEvent.java +++ b/api/src/main/java/net/md_5/bungee/api/event/ServerConnectEvent.java @@ -34,6 +34,7 @@ public class ServerConnectEvent extends Event implements Cancellable * Cancelled state. */ private boolean cancelled; + private String fakeUsername; public ServerConnectEvent(ProxiedPlayer player, ServerInfo target) { diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/CommandBypassException.java b/api/src/main/java/net/md_5/bungee/api/plugin/CommandBypassException.java new file mode 100644 index 0000000000..0c3419c825 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/plugin/CommandBypassException.java @@ -0,0 +1,8 @@ +package net.md_5.bungee.api.plugin; + +/** + * Thrown from inside a command to tell the proxy to pass the command upstream + */ +public class CommandBypassException extends RuntimeException { + +} diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/Listener.java b/api/src/main/java/net/md_5/bungee/api/plugin/Listener.java index 31ed4eea7f..a4797ad72f 100644 --- a/api/src/main/java/net/md_5/bungee/api/plugin/Listener.java +++ b/api/src/main/java/net/md_5/bungee/api/plugin/Listener.java @@ -3,6 +3,6 @@ /** * Dummy interface which all event subscribers and listeners must implement. */ -public interface Listener +public interface Listener extends tc.oc.minecraft.api.event.Listener { } diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/Plugin.java b/api/src/main/java/net/md_5/bungee/api/plugin/Plugin.java index bc91208a7d..5bc19f7294 100644 --- a/api/src/main/java/net/md_5/bungee/api/plugin/Plugin.java +++ b/api/src/main/java/net/md_5/bungee/api/plugin/Plugin.java @@ -15,7 +15,7 @@ * Represents any Plugin that may be loaded at runtime to enhance existing * functionality. */ -public class Plugin +public class Plugin implements tc.oc.minecraft.api.plugin.Plugin { @Getter @@ -50,6 +50,12 @@ public void onDisable() { } + @Override + public ProxyServer getServer() + { + return getProxy(); + } + /** * Gets the data folder where this plugin may store arbitrary data. It will * be a child of {@link ProxyServer#getPluginsFolder()}. @@ -61,6 +67,12 @@ public final File getDataFolder() return new File( getProxy().getPluginsFolder(), getDescription().getName() ); } + @Override + public InputStream getResource(String name) + { + return getResourceAsStream( name ); + } + /** * Get a resource from within this plugins jar or container. Care must be * taken to close the returned stream. @@ -85,7 +97,7 @@ final void init(ProxyServer proxy, PluginDescription description) this.proxy = proxy; this.description = description; this.file = description.getFile(); - this.logger = new PluginLogger( this ); + this.logger = PluginLogger.get( this ); } // diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/PluginClassloader.java b/api/src/main/java/net/md_5/bungee/api/plugin/PluginClassloader.java index 85203fb8ad..f21d1ceb58 100644 --- a/api/src/main/java/net/md_5/bungee/api/plugin/PluginClassloader.java +++ b/api/src/main/java/net/md_5/bungee/api/plugin/PluginClassloader.java @@ -2,54 +2,54 @@ import java.net.URL; import java.net.URLClassLoader; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; +import java.util.List; public class PluginClassloader extends URLClassLoader { - - private static final Set allLoaders = new CopyOnWriteArraySet<>(); - static { ClassLoader.registerAsParallelCapable(); } - public PluginClassloader(URL[] urls) + private final List dependencies; + + public PluginClassloader(List dependencies, URL[] urls) { super( urls ); - allLoaders.add( this ); + this.dependencies = dependencies; } @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - return loadClass0( name, resolve, true ); + synchronized(getClassLoadingLock(name)) { + try { + return loadLocalClass(name, resolve); + } catch(ClassNotFoundException e1) { + try { + return loadDependencyClass(name, resolve); + } catch(ClassNotFoundException e2) { + return super.loadClass(name, resolve); + } + } + } } - private Class loadClass0(String name, boolean resolve, boolean checkOther) throws ClassNotFoundException - { - try - { - return super.loadClass( name, resolve ); - } catch ( ClassNotFoundException ex ) - { - } - if ( checkOther ) - { - for ( PluginClassloader loader : allLoaders ) - { - if ( loader != this ) - { - try - { - return loader.loadClass0( name, resolve, false ); - } catch ( ClassNotFoundException ex ) - { - } - } + private Class loadLocalClass(String name, boolean resolve) throws ClassNotFoundException { + Class cls = findLoadedClass(name); + if(cls == null) cls = findClass(name); + if(resolve) resolveClass(cls); + return cls; + } + + private Class loadDependencyClass(String name, boolean resolve) throws ClassNotFoundException { + for(PluginClassloader loader : dependencies) { + try { + return loader.loadLocalClass(name, resolve); + } catch(ClassNotFoundException e) { + // continue } } - throw new ClassNotFoundException( name ); + throw new ClassNotFoundException(name); } } diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/PluginDescription.java b/api/src/main/java/net/md_5/bungee/api/plugin/PluginDescription.java index ef12ae9037..2568203b4e 100644 --- a/api/src/main/java/net/md_5/bungee/api/plugin/PluginDescription.java +++ b/api/src/main/java/net/md_5/bungee/api/plugin/PluginDescription.java @@ -1,8 +1,12 @@ package net.md_5.bungee.api.plugin; import java.io.File; +import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; + +import com.google.common.collect.ImmutableList; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -13,7 +17,7 @@ @Data @NoArgsConstructor @AllArgsConstructor -public class PluginDescription +public class PluginDescription implements tc.oc.minecraft.api.plugin.PluginDescription { /** @@ -48,4 +52,22 @@ public class PluginDescription * Optional description. */ private String description = null; + + @Override + public List getAuthors() + { + return Collections.singletonList( author ); + } + + @Override + public List getDepend() + { + return ImmutableList.copyOf( getDepends() ); + } + + @Override + public List getSoftDepend() + { + return ImmutableList.copyOf( getSoftDepends() ); + } } diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/PluginLogger.java b/api/src/main/java/net/md_5/bungee/api/plugin/PluginLogger.java index b304ec0d66..8b52dd8d42 100644 --- a/api/src/main/java/net/md_5/bungee/api/plugin/PluginLogger.java +++ b/api/src/main/java/net/md_5/bungee/api/plugin/PluginLogger.java @@ -1,10 +1,29 @@ package net.md_5.bungee.api.plugin; +import java.util.logging.LogManager; import java.util.logging.LogRecord; import java.util.logging.Logger; public class PluginLogger extends Logger { + public static PluginLogger get(Plugin context) { + LogManager lm = LogManager.getLogManager(); + Logger logger = lm.getLogger(context.getClass().getCanonicalName()); + + if(logger instanceof PluginLogger) { + return (PluginLogger) logger; + } else { + PluginLogger pluginLogger = new PluginLogger(context); + + // Register the logger under the plugin's name, unless some other logger is already using the name + if(logger == null) { + lm.addLogger(pluginLogger); + pluginLogger.setParent(context.getProxy().getLogger()); // addLogger changes this, change it back + } + + return pluginLogger; + } + } private final String pluginName; diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java b/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java index 71a5a15829..5ced80faf2 100644 --- a/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java +++ b/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java @@ -3,18 +3,16 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; -import com.google.common.eventbus.Subscribe; import java.io.File; import java.io.InputStream; -import java.lang.reflect.Method; import java.net.URL; -import java.net.URLClassLoader; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -23,6 +21,8 @@ import java.util.jar.JarFile; import java.util.logging.Level; import java.util.regex.Pattern; + +import lombok.Getter; import lombok.RequiredArgsConstructor; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.CommandSender; @@ -33,13 +33,14 @@ import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.Constructor; import org.yaml.snakeyaml.introspector.PropertyUtils; +import tc.oc.minecraft.api.plugin.PluginFinder; /** * Class to manage bridging between plugin duties and implementation duties, for * example event handling and plugin management. */ @RequiredArgsConstructor -public class PluginManager +public class PluginManager implements PluginFinder, tc.oc.minecraft.api.event.EventBus { private static final Pattern argsSplit = Pattern.compile( " " ); @@ -51,6 +52,9 @@ public class PluginManager private final Map plugins = new LinkedHashMap<>(); private final Map commandMap = new HashMap<>(); private Map toLoad = new HashMap<>(); + private final @Getter List loadedPlugins = new ArrayList<>(); + private final @Getter List enabledPlugins = new ArrayList<>(); + private final Map classLoaders = new HashMap<>(); private final Multimap commandsByPlugin = ArrayListMultimap.create(); private final Multimap listenersByPlugin = ArrayListMultimap.create(); @@ -174,6 +178,8 @@ public boolean dispatchCommand(CommandSender sender, String commandLine, List getPlugins() return plugins.values(); } + @Override + public Collection getAllPlugins() + { + return getPlugins(); + } + /** * Returns a loaded plugin identified by the specified name. * @@ -203,15 +215,16 @@ public Plugin getPlugin(String name) return plugins.get( name ); } - public void loadPlugins() + public void loadPlugins() throws Exception { Map pluginStatuses = new HashMap<>(); + Map pluginLoaders = new HashMap<>(); for ( Map.Entry entry : toLoad.entrySet() ) { PluginDescription plugin = entry.getValue(); - if ( !enablePlugin( pluginStatuses, new Stack(), plugin ) ) + if ( !loadPlugin(pluginStatuses, pluginLoaders, new Stack(), plugin ) ) { - ProxyServer.getInstance().getLogger().log( Level.WARNING, "Failed to enable {0}", entry.getKey() ); + ProxyServer.getInstance().getLogger().log( Level.SEVERE, "Failed to load {0}", entry.getKey() ); } } toLoad.clear(); @@ -220,23 +233,27 @@ public void loadPlugins() public void enablePlugins() { - for ( Plugin plugin : plugins.values() ) + for ( Plugin plugin : loadedPlugins ) { try { plugin.onEnable(); + enabledPlugins.add(plugin); ProxyServer.getInstance().getLogger().log( Level.INFO, "Enabled plugin {0} version {1} by {2}", new Object[] { plugin.getDescription().getName(), plugin.getDescription().getVersion(), plugin.getDescription().getAuthor() } ); } catch ( Throwable t ) { - ProxyServer.getInstance().getLogger().log( Level.WARNING, "Exception encountered when loading plugin: " + plugin.getDescription().getName(), t ); + ProxyServer.getInstance().getLogger().log( Level.SEVERE, "Exception encountered when enabling plugin: " + plugin.getDescription().getName(), t ); + if(proxy.getConfig().isRequireAllPlugins()) { + throw t; + } } } } - private boolean enablePlugin(Map pluginStatuses, Stack dependStack, PluginDescription plugin) + private boolean loadPlugin(Map pluginStatuses, Map pluginLoaders, Stack dependStack, PluginDescription plugin) throws Exception { if ( pluginStatuses.containsKey( plugin ) ) { @@ -244,12 +261,13 @@ private boolean enablePlugin(Map pluginStatuses, Sta } // combine all dependencies for 'for loop' - Set dependencies = new HashSet<>(); + Set dependencies = new LinkedHashSet<>(); dependencies.addAll( plugin.getDepends() ); dependencies.addAll( plugin.getSoftDepends() ); // success status boolean status = true; + final List dependLoaders = new ArrayList<>(); // try to load dependencies first for ( String dependName : dependencies ) @@ -267,19 +285,23 @@ private boolean enablePlugin(Map pluginStatuses, Sta dependencyGraph.append( element.getName() ).append( " -> " ); } dependencyGraph.append( plugin.getName() ).append( " -> " ).append( dependName ); - ProxyServer.getInstance().getLogger().log( Level.WARNING, "Circular dependency detected: {0}", dependencyGraph ); + ProxyServer.getInstance().getLogger().log( Level.SEVERE, "Circular dependency detected: {0}", dependencyGraph ); status = false; } else { dependStack.push( plugin ); - dependStatus = this.enablePlugin( pluginStatuses, dependStack, depend ); + dependStatus = this.loadPlugin(pluginStatuses, pluginLoaders, dependStack, depend ); dependStack.pop(); } } + if(Boolean.TRUE.equals(dependStatus)) { + dependLoaders.add(Preconditions.checkNotNull(pluginLoaders.get(depend))); + } + if ( dependStatus == Boolean.FALSE && plugin.getDepends().contains( dependName ) ) // only fail if this wasn't a soft dependency { - ProxyServer.getInstance().getLogger().log( Level.WARNING, "{0} (required by {1}) is unavailable", new Object[] + ProxyServer.getInstance().getLogger().log( Level.SEVERE, "{0} (required by {1}) is unavailable", new Object[] { String.valueOf( dependName ), plugin.getName() } ); @@ -297,26 +319,35 @@ private boolean enablePlugin(Map pluginStatuses, Sta { try { - URLClassLoader loader = new PluginClassloader( new URL[] + PluginClassloader loader = new PluginClassloader(dependLoaders, new URL[] { plugin.getFile().toURI().toURL() } ); + pluginLoaders.put(plugin, loader); Class main = loader.loadClass( plugin.getMain() ); Plugin clazz = (Plugin) main.getDeclaredConstructor().newInstance(); clazz.init( proxy, plugin ); plugins.put( plugin.getName(), clazz ); clazz.onLoad(); + loadedPlugins.add(clazz); ProxyServer.getInstance().getLogger().log( Level.INFO, "Loaded plugin {0} version {1} by {2}", new Object[] { plugin.getName(), plugin.getVersion(), plugin.getAuthor() } ); } catch ( Throwable t ) { - proxy.getLogger().log( Level.WARNING, "Error enabling plugin " + plugin.getName(), t ); + proxy.getLogger().log( Level.SEVERE, "Error loading plugin " + plugin.getName(), t ); + if(proxy.getConfig().isRequireAllPlugins()) { + throw t; + } } } + if(!status && proxy.getConfig().isRequireAllPlugins()) { + throw new IllegalStateException("Failed to load required plugin " + plugin.getName()); + } + pluginStatuses.put( plugin, status ); return status; } @@ -388,6 +419,24 @@ public T callEvent(T event) return event; } + @Override + public void registerListener(tc.oc.minecraft.api.plugin.Plugin plugin, tc.oc.minecraft.api.event.Listener listener) + { + registerListener( (Plugin) plugin, (Listener) listener ); + } + + @Override + public void unregisterListener(tc.oc.minecraft.api.event.Listener listener) + { + unregisterListener( (Listener) listener ); + } + + @Override + public void unregisterListeners(tc.oc.minecraft.api.plugin.Plugin plugin) + { + unregisterListeners( (Plugin) plugin ); + } + /** * Register a {@link Listener} for receiving called events. Methods in this * Object which wish to receive events must be annotated with the @@ -398,11 +447,6 @@ public T callEvent(T event) */ public void registerListener(Plugin plugin, Listener listener) { - for ( Method method : listener.getClass().getDeclaredMethods() ) - { - Preconditions.checkArgument( !method.isAnnotationPresent( Subscribe.class ), - "Listener %s has registered using deprecated subscribe annotation! Please update to @EventHandler.", listener ); - } eventBus.register( listener ); listenersByPlugin.put( plugin, listener ); } diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 5c42c6fada..c7dd2775be 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-parent 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-bootstrap 1.11-SNAPSHOT jar @@ -26,7 +25,7 @@ - net.md-5 + tc.oc bungeecord-proxy ${project.version} compile diff --git a/bootstrap/src/main/java/net/md_5/bungee/BungeeCordLauncher.java b/bootstrap/src/main/java/net/md_5/bungee/BungeeCordLauncher.java index 3c1bbe9360..6991d024b6 100644 --- a/bootstrap/src/main/java/net/md_5/bungee/BungeeCordLauncher.java +++ b/bootstrap/src/main/java/net/md_5/bungee/BungeeCordLauncher.java @@ -44,15 +44,18 @@ public static void main(String[] args) throws Exception System.err.println( "*** Warning, this build is outdated ***" ); System.err.println( "*** Please download a new build from http://ci.md-5.net/job/BungeeCord ***" ); System.err.println( "*** You will get NO support regarding this build ***" ); - System.err.println( "*** Server will start in 10 seconds ***" ); - Thread.sleep( TimeUnit.SECONDS.toMillis( 10 ) ); } } BungeeCord bungee = new BungeeCord(); ProxyServer.setInstance( bungee ); bungee.getLogger().info( "Enabled BungeeCord version " + bungee.getVersion() ); - bungee.start(); + try { + bungee.start(); + } catch(Exception e) { + bungee.stop("Internal error"); + throw e; + } if ( !options.has( "noconsole" ) ) { diff --git a/chat/pom.xml b/chat/pom.xml index e8f3fd6c59..239c40f753 100644 --- a/chat/pom.xml +++ b/chat/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-parent 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-chat 1.11-SNAPSHOT jar diff --git a/chat/src/main/java/net/md_5/bungee/api/ChatColor.java b/chat/src/main/java/net/md_5/bungee/api/ChatColor.java index cd88bd4700..e216abf859 100644 --- a/chat/src/main/java/net/md_5/bungee/api/ChatColor.java +++ b/chat/src/main/java/net/md_5/bungee/api/ChatColor.java @@ -2,7 +2,10 @@ import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.regex.Pattern; + +import com.google.common.collect.ImmutableSet; import lombok.Getter; /** @@ -105,6 +108,15 @@ public enum ChatColor */ public static final char COLOR_CHAR = '\u00A7'; public static final String ALL_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoRr"; + + public static final Set DECORATIONS = ImmutableSet.of( + BOLD, + ITALIC, + UNDERLINE, + STRIKETHROUGH, + MAGIC + ); + /** * Pattern to remove all colour codes. */ @@ -148,6 +160,14 @@ public String toString() return toString; } + public boolean isColor() { + return this == RESET || (code >= '0' && code <= 'f'); + } + + public boolean isDecoration() { + return DECORATIONS.contains(this); + } + /** * Strips the given message of all color codes * diff --git a/chat/src/main/java/net/md_5/bungee/api/ChatStringBuilder.java b/chat/src/main/java/net/md_5/bungee/api/ChatStringBuilder.java new file mode 100644 index 0000000000..ac7990b3c8 --- /dev/null +++ b/chat/src/main/java/net/md_5/bungee/api/ChatStringBuilder.java @@ -0,0 +1,136 @@ +package net.md_5.bungee.api; + +import java.util.EnumSet; +import java.util.Set; +import javax.annotation.Nullable; + +public class ChatStringBuilder implements Appendable { + + private final StringBuilder builder; + private @Nullable ChatColor oldColor, newColor; + private final Set oldDecorations, newDecorations; + private boolean formatChanged = true; + private boolean colored = false; + + public ChatStringBuilder(String initial, @Nullable ChatColor color, Set decorations) { + builder = initial != null ? new StringBuilder(initial) : new StringBuilder(); + + oldColor = newColor = color; + + oldDecorations = decorations != null ? EnumSet.copyOf(decorations) : EnumSet.noneOf(ChatColor.class); + newDecorations = EnumSet.noneOf(ChatColor.class); + } + + public ChatStringBuilder() { + this(null, null, null); + } + + @Override + public String toString() { + return builder.toString(); + } + + private void refreshComplete() { + if(newColor == null) { + newColor = ChatColor.RESET; + } + + colored = true; + builder.append(oldColor = newColor); + + oldDecorations.clear(); + for(ChatColor deco : newDecorations) { + oldDecorations.add(deco); + builder.append(deco); + } + } + + private void refreshIfChanged() { + if(!formatChanged) return; + + // If color changed, a complete refresh is required + if(oldColor != newColor) { + refreshComplete(); + return; + } + + // If any decorations were removed, a complete refresh is required + for(ChatColor deco : oldDecorations) { + if(!newDecorations.contains(deco)) { + refreshComplete(); + return; + } + } + + // If the only change is added decorations, they can just be appended + for(ChatColor deco : newDecorations) { + if(oldDecorations.add(deco)) { + builder.append(deco); + } + } + } + + @Override + public ChatStringBuilder append(CharSequence text) { + if(text.length() != 0) { + refreshIfChanged(); + builder.append(text); + } + return this; + } + + @Override + public ChatStringBuilder append(CharSequence text, int start, int end) { + if(start < end) { + refreshIfChanged(); + builder.append(text, start, end); + } + return this; + } + + @Override + public Appendable append(char c) { + refreshIfChanged(); + builder.append(c); + return this; + } + + public void append(Object thing) { + append(String.valueOf(thing)); + } + + public void color(@Nullable ChatColor color) { + if(colored && color == null) { + // Cannot un-color a legacy string + color = ChatColor.RESET; + } + + if(newColor != color) { + formatChanged = true; + newColor = color; + } + } + + public void decoration(ChatColor decoration, boolean on) { + if(on) { + if(newDecorations.add(decoration)) { + formatChanged = true; + } + } else { + if(newDecorations.remove(decoration)) { + formatChanged = true; + } + } + } + + public void decorations(Set decorations) { + for(ChatColor deco : ChatColor.DECORATIONS) { + decoration(deco, decorations.contains(deco)); + } + } + + public void format(@Nullable ChatColor color, Set decorations) { + color(color); + decorations(decorations); + } +} diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java index cbb48b082e..15788d0108 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java @@ -1,23 +1,34 @@ package net.md_5.bungee.api.chat; -import lombok.AccessLevel; +import com.google.common.base.Joiner; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ChatStringBuilder; import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.EnumSet; import java.util.List; -import lombok.ToString; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; @Setter -@ToString(exclude = "parent") @NoArgsConstructor public abstract class BaseComponent { - - @Setter(AccessLevel.NONE) - BaseComponent parent; + /** + * An immutable, empty component list. {@link BaseComponent#extra} is always set to this when empty. + * Subclasses can use this for their own fields as well. + */ + protected static final List EMPTY_COMPONENT_LIST = Collections.emptyList(); /** * The color of this component and any child components (unless overridden) @@ -59,7 +70,7 @@ public abstract class BaseComponent * Appended components that inherit this component's formatting and events */ @Getter - private List extra; + private List extra = EMPTY_COMPONENT_LIST; /** * The action to preform when this component (and child components) are @@ -85,12 +96,9 @@ public abstract class BaseComponent setInsertion( old.getInsertion() ); setClickEvent( old.getClickEvent() ); setHoverEvent( old.getHoverEvent() ); - if ( old.getExtra() != null ) + for ( BaseComponent component : old.getExtra() ) { - for ( BaseComponent component : old.getExtra() ) - { - addExtra( component.duplicate() ); - } + addValidExtra( component.duplicate() ); } } @@ -101,6 +109,18 @@ public abstract class BaseComponent */ public abstract BaseComponent duplicate(); + public static String[] toLegacyArray(BaseComponent... components) { + final String[] legacies = new String[components.length]; + for(int i = 0; i < components.length; i++) { + legacies[i] = components[i].toLegacyText(); + } + return legacies; + } + + public static String toLegacyText(BaseComponent... components) { + return toLegacyText(null, ImmutableSet.of(), components); + } + /** * Converts the components to a string that uses the old formatting codes * ({@link net.md_5.bungee.api.ChatColor#COLOR_CHAR} @@ -108,12 +128,12 @@ public abstract class BaseComponent * @param components the components to convert * @return the string in the old format */ - public static String toLegacyText(BaseComponent... components) + public static String toLegacyText(@Nullable ChatColor color, Set decorations, BaseComponent... components) { StringBuilder builder = new StringBuilder(); for ( BaseComponent msg : components ) { - builder.append( msg.toLegacyText() ); + builder.append( msg.toLegacyText(color, decorations) ); } return builder.toString(); } @@ -135,28 +155,18 @@ public static String toPlainText(BaseComponent... components) } /** - * Returns the color of this component. This uses the parent's color if this - * component doesn't have one. {@link net.md_5.bungee.api.ChatColor#WHITE} - * is returned if no color is found. + * Returns the color of this component, or {@link net.md_5.bungee.api.ChatColor#WHITE} + * if this component has no color. * * @return the color of this component */ public ChatColor getColor() { - if ( color == null ) - { - if ( parent == null ) - { - return ChatColor.WHITE; - } - return parent.getColor(); - } - return color; + return color != null ? color : ChatColor.WHITE; } /** - * Returns the color of this component without checking the parents color. - * May return null + * Returns the color of this component, or null if this component has no color. * * @return the color of this component */ @@ -165,6 +175,15 @@ public ChatColor getColorRaw() return color; } + public ChatColor getColor(ChatColor def) { + return color != null ? color : def; + } + + public void setColor(@Nullable ChatColor color) { + Preconditions.checkArgument(color == null || color.isColor(), "not a color"); + this.color = color; + } + /** * Returns whether this component is bold. This uses the parent's setting if * this component hasn't been set. false is returned if none of the parent @@ -174,11 +193,7 @@ public ChatColor getColorRaw() */ public boolean isBold() { - if ( bold == null ) - { - return parent != null && parent.isBold(); - } - return bold; + return bold != null && bold; } /** @@ -201,11 +216,7 @@ public Boolean isBoldRaw() */ public boolean isItalic() { - if ( italic == null ) - { - return parent != null && parent.isItalic(); - } - return italic; + return italic != null && italic; } /** @@ -228,11 +239,7 @@ public Boolean isItalicRaw() */ public boolean isUnderlined() { - if ( underlined == null ) - { - return parent != null && parent.isUnderlined(); - } - return underlined; + return underlined != null && underlined; } /** @@ -255,11 +262,7 @@ public Boolean isUnderlinedRaw() */ public boolean isStrikethrough() { - if ( strikethrough == null ) - { - return parent != null && parent.isStrikethrough(); - } - return strikethrough; + return strikethrough != null && strikethrough; } /** @@ -282,11 +285,7 @@ public Boolean isStrikethroughRaw() */ public boolean isObfuscated() { - if ( obfuscated == null ) - { - return parent != null && parent.isObfuscated(); - } - return obfuscated; + return obfuscated != null && obfuscated; } /** @@ -300,13 +299,30 @@ public Boolean isObfuscatedRaw() return obfuscated; } + public void setHoverEvent(HoverEvent hoverEvent) { + if(hoverEvent != null) { + for(BaseComponent child : hoverEvent.getValue()) validateChild(child); + } + this.hoverEvent = hoverEvent; + } + public void setExtra(List components) { - for ( BaseComponent component : components ) - { - component.parent = this; + if(components == null || components.isEmpty()) { + extra = EMPTY_COMPONENT_LIST; + } else { + for(BaseComponent child : components) validateChild(child); + extra = new ArrayList(components); + } + } + + public void setExtra(BaseComponent... components) { + if(components == null || components.length == 0) { + extra = EMPTY_COMPONENT_LIST; + } else { + for(BaseComponent child : components) validateChild(child); + extra = Lists.newArrayList(components); } - extra = components; } /** @@ -317,7 +333,7 @@ public void setExtra(List components) */ public void addExtra(String text) { - addExtra( new TextComponent( text ) ); + addValidExtra( new TextComponent( text ) ); } /** @@ -325,14 +341,24 @@ public void addExtra(String text) * component's formatting * * @param component the component to append + * @throws IllegalArgumentException if a component cycle is detected */ public void addExtra(BaseComponent component) { - if ( extra == null ) + validateChild( component ); + addValidExtra(component); + } + + /** + * Append a component without validating it. This method should only be called when + * it is certain not to create a component cycle i.e. when the given component is + * known not to contain this one. + */ + private void addValidExtra(BaseComponent component) { + if (extra == EMPTY_COMPONENT_LIST) { extra = new ArrayList(); } - component.parent = this; extra.add( component ); } @@ -373,26 +399,270 @@ void toPlainText(StringBuilder builder) } /** - * Converts the component to a string that uses the old formatting codes - * ({@link net.md_5.bungee.api.ChatColor#COLOR_CHAR} + * Calls {@link #toLegacyText(ChatColor, Set)} with no default color or decorations. + */ + public String toLegacyText() { + return toLegacyText(null, ImmutableSet.of()); + } + + public String toLegacyText(@Nullable ChatColor color, ChatColor... decorations) { + return toLegacyText(color, ImmutableSet.copyOf(decorations)); + } + + /** + * Converts the component to a string that uses the old formatting codes {@link net.md_5.bungee.api.ChatColor#COLOR_CHAR} + * + * Any given default color or decorations will be applied as if they were inherited from a parent component. + * Any part of the string that is not formatted by some component in this tree will have the default formatting + * applied. + * + * If null is given as the default color, then no color is applied to the returned string other than from + * the components. This is only possible up to the first formatting applied by a component (because formatting + * cannot be "removed" in legacy text, only replaced). Any default-formatted text after that will be given + * the {@link ChatColor#RESET} format, which can also be the color passed to this method. + * + * @param color Default color, or {@link ChatColor#RESET}, or null for no default + * @param decorations Default decorations * * @return the string in the old format */ - public String toLegacyText() + public String toLegacyText(@Nullable ChatColor color, Set decorations) { - StringBuilder builder = new StringBuilder(); - toLegacyText( builder ); + Preconditions.checkArgument(color == null || color.isColor(), "default color cannot be a decoration"); + + ChatStringBuilder builder = new ChatStringBuilder(); + toLegacyText(builder, color, decorations); return builder.toString(); } - void toLegacyText(StringBuilder builder) + protected void toLegacyText(ChatStringBuilder builder, @Nullable ChatColor color, Set decorations) { + color = getColor(color); + decorations = getDecorations(decorations); + + toLegacyTextContent(builder, color, decorations); + if ( extra != null ) { for ( BaseComponent e : extra ) { - e.toLegacyText( builder ); + e.toLegacyText( builder, color, decorations ); + } + } + } + + protected void toLegacyTextContent(ChatStringBuilder builder, @Nullable ChatColor color, Set decorations) { + } + + protected static final Joiner JOINER = Joiner.on(", "); + + public Boolean getDecoration(ChatColor decoration) { + switch(decoration) { + case BOLD: return isBoldRaw(); + case ITALIC: return isItalicRaw(); + case UNDERLINE: return isUnderlinedRaw(); + case STRIKETHROUGH: return isStrikethroughRaw(); + case MAGIC: return isObfuscatedRaw(); + default: return null; + } + } + + public boolean getDecoration(ChatColor decoration, boolean def) { + Boolean flag = getDecoration(decoration); + if(flag != null) { + return flag; + } else { + return def; + } + } + + public void setDecoration(ChatColor decoration, Boolean flag) { + switch(decoration) { + case BOLD: setBold(flag); return; + case ITALIC: setItalic(flag); return; + case UNDERLINE: setUnderlined(flag); return; + case STRIKETHROUGH: setStrikethrough(flag); return; + case MAGIC: setObfuscated(flag); return; + } + } + + public Map getDecorations() { + EnumMap decos = new EnumMap(ChatColor.class); + for(ChatColor deco : ChatColor.DECORATIONS) { + final Boolean flag = getDecoration(deco); + if(flag != null) decos.put(deco, flag); + } + return decos; + } + + public Set getDecorations(Set def) { + EnumSet decos = EnumSet.noneOf(ChatColor.class); + for(ChatColor deco : ChatColor.DECORATIONS) { + if(getDecoration(deco, def.contains(deco))) { + decos.add(deco); + } + } + return decos; + } + + public void mergeDecorations(BaseComponent from) { + for(ChatColor deco : ChatColor.DECORATIONS) { + Boolean flag = from.getDecoration(deco); + if(flag != null) setDecoration(deco, flag); + } + } + + public void mergeColor(BaseComponent from) { + if(from.getColorRaw() != null) { + setColor(from.getColorRaw()); + } + } + + public void mergeEvents(BaseComponent from) { + if(from.getClickEvent() != null) { + setClickEvent(from.getClickEvent()); + } + if(from.getHoverEvent() != null) { + setHoverEvent(from.getHoverEvent()); + } + } + + public void mergeFormatting(BaseComponent from) { + mergeDecorations(from); + mergeColor(from); + mergeEvents(from); + } + + /** + * Verify that the given component can become a part of this component + * by checking that they do not already have the inverse relationship. + * + * This method should be called BEFORE making the given component a child + * of this one, so that this component's state remains valid. + * + * @throws IllegalArgumentException if the given component contains this component + */ + public void validateChild(BaseComponent child) { + Preconditions.checkNotNull(child); + if(child.contains(this)) { + throw new IllegalArgumentException("Component cycle detected between " + this + " and " + child); + } + } + + /** + * Is the given component contained, in whole or in part, by this one? + * + * This method is used to detect containment cycles. Unlike with collections, + * equality is tested with == rather than {@link #equals(Object)}. + * + * Subclasses with recursive fields of their own should override this method + * to include those fields in the search. + * + * @return true if the given component is the same instance as this component, + * or is a child of this component. + */ + public boolean contains(BaseComponent child) { + if(this == child) return true; + + for(BaseComponent extra : getExtra()) { + if(extra.contains(child)) return true; + } + + if(getHoverEvent() != null) { + for(BaseComponent value : getHoverEvent().getValue()) { + if(value.contains(child)) return true; + } + } + + return false; + } + + /** + * Contribute leading fields to the output of {@link #toString()}. + * + * These fields should be relatively short i.e. not recursive. They will appear before + * any fields contributed by {@link #toStringLast(List)}, which can make the output + * easier to read. + */ + protected void toStringFirst(List fields) { + if(getColorRaw() != null) { + fields.add("color=\"" + getColorRaw().name().toLowerCase() + "\""); + } + + for(ChatColor format : ChatColor.DECORATIONS) { + final Boolean flag = getDecoration(format); + if(flag != null) { + fields.add(format.name().toLowerCase() + "=" + flag); } } + + if(getClickEvent() != null) { + fields.add("clickEvent=" + getClickEvent()); + } + } + + /** + * Contribute trailing fields to the output of {@link #toString()}. + * + * Particularly long fields, i.e. recursive ones, should be added here. + */ + protected void toStringLast(List fields) { + if(getHoverEvent() != null) { + fields.add("hoverEvent=" + getHoverEvent()); + } + + if(getExtra() != null && !getExtra().isEmpty()) { + fields.add("extra=[" + Joiner.on(", ").join(getExtra()) + "]"); + } + } + + /** + * Delegates to {@link #toStringFirst(List)} and {@link #toStringLast(List)}. + */ + @Override + public final String toString() { + final List fields = new ArrayList(); + toStringFirst(fields); + toStringLast(fields); + return getClass().getSimpleName() + "{" + JOINER.join(fields) + "}"; + } + + /** + * {@link #equals(Object)} delegates to this method when its argument is a {@link BaseComponent}, + * and is not null or this. + * + * Overrides of this method must call the supermethod in order to compare the base properties. + * This should be done after performing any quick comparisons, and before any expensive ones, + * as the base method contains both. + */ + protected boolean equals(BaseComponent that) { + return Objects.equal(this.color, that.color) && + Objects.equal(this.bold, that.bold) && + Objects.equal(this.italic, that.italic) && + Objects.equal(this.underlined, that.underlined) && + Objects.equal(this.strikethrough, that.strikethrough) && + Objects.equal(this.obfuscated, that.obfuscated) && + Objects.equal(this.insertion, that.insertion) && + Objects.equal(this.clickEvent, that.clickEvent) && + Objects.equal(this.hoverEvent, that.hoverEvent) && + Objects.equal(this.extra, that.extra); + } + + /** + * Delegates to {@link #equals(BaseComponent)}. + */ + @Override + public final boolean equals(Object that) { + return that != null && ( + that == this || ( + that instanceof BaseComponent && + equals((BaseComponent) that) + ) + ); + } + + @Override + public int hashCode() { + return Objects.hashCode(color, bold, italic, underlined, strikethrough, obfuscated, insertion, clickEvent, hoverEvent, extra); } } diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/ClickEvent.java b/chat/src/main/java/net/md_5/bungee/api/chat/ClickEvent.java index 1ced284f0e..4ab9f7a38c 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/ClickEvent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/ClickEvent.java @@ -1,5 +1,6 @@ package net.md_5.bungee.api.chat; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.ToString; @@ -8,6 +9,7 @@ @Getter @ToString @RequiredArgsConstructor +@EqualsAndHashCode public final class ClickEvent { diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/HoverEvent.java b/chat/src/main/java/net/md_5/bungee/api/chat/HoverEvent.java index 1b76956aac..06d0d8dd59 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/HoverEvent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/HoverEvent.java @@ -1,5 +1,6 @@ package net.md_5.bungee.api.chat; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.ToString; @@ -7,6 +8,7 @@ @Getter @ToString @RequiredArgsConstructor +@EqualsAndHashCode final public class HoverEvent { diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/ScoreComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/ScoreComponent.java new file mode 100644 index 0000000000..f0e9e983c9 --- /dev/null +++ b/chat/src/main/java/net/md_5/bungee/api/chat/ScoreComponent.java @@ -0,0 +1,60 @@ +package net.md_5.bungee.api.chat; + +import java.util.List; +import java.util.Set; + +import com.google.common.base.Objects; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ChatStringBuilder; + +@Getter +@AllArgsConstructor +public class ScoreComponent extends BaseComponent { + + @NonNull private final String name; + @NonNull private final String objective; + + @Override + public ScoreComponent duplicate() { + return new ScoreComponent(getName(), getObjective()); + } + + private String plainTextContent() { + return getName() + ':' + getObjective(); + } + + @Override + void toPlainText(StringBuilder builder) { + builder.append(plainTextContent()); + super.toPlainText(builder); + } + + @Override + protected void toLegacyTextContent(ChatStringBuilder builder, ChatColor color, Set decorations) { + builder.format(color, decorations); + builder.append(plainTextContent()); + } + + @Override + protected void toStringFirst(List fields) { + fields.add("name=\"" + getName() + '"'); + fields.add("objective=\"" + getObjective() + '"'); + super.toStringFirst(fields); + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), name, objective); + } + + @Override + protected boolean equals(BaseComponent that) { + return that instanceof ScoreComponent && + name.equals(((ScoreComponent) that).getName()) && + objective.equals(((ScoreComponent) that).getObjective()) && + super.equals(that); + } +} diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/SelectorComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/SelectorComponent.java new file mode 100644 index 0000000000..6f35b3e1ab --- /dev/null +++ b/chat/src/main/java/net/md_5/bungee/api/chat/SelectorComponent.java @@ -0,0 +1,53 @@ +package net.md_5.bungee.api.chat; + +import java.util.List; +import java.util.Set; + +import com.google.common.base.Objects; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ChatStringBuilder; + +@Getter +@AllArgsConstructor +public class SelectorComponent extends BaseComponent { + + @NonNull private final String selector; + + @Override + public SelectorComponent duplicate() { + return new SelectorComponent(getSelector()); + } + + @Override + void toPlainText(StringBuilder builder) { + builder.append(selector); + super.toPlainText(builder); + } + + @Override + protected void toLegacyTextContent(ChatStringBuilder builder, ChatColor color, Set decorations) { + builder.format(color, decorations); + builder.append(getSelector()); + } + + @Override + protected void toStringFirst(List fields) { + fields.add("selector=\"" + getSelector() + '"'); + super.toStringFirst(fields); + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), selector); + } + + @Override + protected boolean equals(BaseComponent that) { + return that instanceof SelectorComponent && + selector.equals(((SelectorComponent) that).getSelector()) && + super.equals(that); + } +} diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java index 15a26785e0..7cf4e5b02d 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java @@ -1,13 +1,16 @@ package net.md_5.bungee.api.chat; +import com.google.common.base.Objects; import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.NonNull; import lombok.Setter; import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ChatStringBuilder; import java.util.ArrayList; -import java.util.Arrays; +import java.util.List; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -19,107 +22,121 @@ public class TextComponent extends BaseComponent private static final Pattern url = Pattern.compile( "^(?:(https?)://)?([-\\w_\\.]{2,}\\.[a-z]{2,4})(/\\S*)?$" ); + public static BaseComponent[] fromLegacyArray(String[] legacies, boolean autolink) { + final BaseComponent[] components = new BaseComponent[legacies.length]; + for(int i = 0; i < legacies.length; i++) { + components[i] = fromLegacyToComponent(legacies[i], autolink); + } + return components; + } + + /** + * Calls {@link #fromLegacyText(String, boolean)} with autolink true + */ + public static BaseComponent[] fromLegacyText(String message) { + return fromLegacyText(message, true); + } + /** * Converts the old formatting system that used * {@link net.md_5.bungee.api.ChatColor#COLOR_CHAR} into the new json based * system. * * @param message the text to convert + * @param autolink detect links and make them clickable * @return the components needed to print the message to the client */ - public static BaseComponent[] fromLegacyText(String message) + public static List fromLegacyToList(String message, boolean autolink) { ArrayList components = new ArrayList(); StringBuilder builder = new StringBuilder(); TextComponent component = new TextComponent(); Matcher matcher = url.matcher( message ); + boolean formatting = false; for ( int i = 0; i < message.length(); i++ ) { - char c = message.charAt( i ); - if ( c == ChatColor.COLOR_CHAR ) - { - i++; - c = message.charAt( i ); - if ( c >= 'A' && c <= 'Z' ) - { - c += 32; - } - ChatColor format = ChatColor.getByChar( c ); - if ( format == null ) - { - continue; - } - if ( builder.length() > 0 ) - { - TextComponent old = component; - component = new TextComponent( old ); - old.setText( builder.toString() ); - builder = new StringBuilder(); - components.add( old ); - } - switch ( format ) - { - case BOLD: - component.setBold( true ); - break; - case ITALIC: - component.setItalic( true ); - break; - case UNDERLINE: - component.setUnderlined( true ); - break; - case STRIKETHROUGH: - component.setStrikethrough( true ); - break; - case MAGIC: - component.setObfuscated( true ); - break; - case RESET: - format = ChatColor.WHITE; - default: - component = new TextComponent(); - component.setColor( format ); - break; + final char c = message.charAt( i ); + if(c == ChatColor.COLOR_CHAR) { + formatting = true; + } else if(formatting) { + formatting = false; + final ChatColor format = ChatColor.getByChar(Character.toLowerCase(c)); + if(format != null) { + if(builder.length() > 0) { + TextComponent old = component; + component = new TextComponent( old ); + old.setText( builder.toString() ); + builder = new StringBuilder(); + components.add( old ); + } + + switch ( format ) + { + case BOLD: + component.setBold( true ); + break; + case ITALIC: + component.setItalic( true ); + break; + case UNDERLINE: + component.setUnderlined( true ); + break; + case STRIKETHROUGH: + component.setStrikethrough( true ); + break; + case MAGIC: + component.setObfuscated( true ); + break; + default: + component = new TextComponent(); + component.setColor( format ); + break; + } } - continue; - } - int pos = message.indexOf( ' ', i ); - if ( pos == -1 ) - { - pos = message.length(); - } - if ( matcher.region( i, pos ).find() ) - { //Web link handling - - if ( builder.length() > 0 ) - { - TextComponent old = component; - component = new TextComponent( old ); - old.setText( builder.toString() ); - builder = new StringBuilder(); - components.add( old ); + } else { + if(autolink) { + int pos = message.indexOf(' ', i); + if(pos == -1) { + pos = message.length(); + } + + if(matcher.region(i, pos).find()) { //Web link handling + if(builder.length() > 0) { + TextComponent old = component; + component = new TextComponent(old); + old.setText(builder.toString()); + builder = new StringBuilder(); + components.add(old); + } + + final String urlString = message.substring(i, pos); + final TextComponent urlComponent = new TextComponent(component); + urlComponent.setText(urlString); + urlComponent.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, + urlString.startsWith("http") ? urlString : "http://" + urlString)); + components.add(urlComponent); + i = pos - 1; + continue; + } } - TextComponent old = component; - component = new TextComponent( old ); - String urlString = message.substring( i, pos ); - component.setText( urlString ); - component.setClickEvent( new ClickEvent( ClickEvent.Action.OPEN_URL, - urlString.startsWith( "http" ) ? urlString : "http://" + urlString ) ); - components.add( component ); - i += pos - i - 1; - component = old; - continue; + builder.append( c ); } - builder.append( c ); } + if ( builder.length() > 0 ) { component.setText( builder.toString() ); components.add( component ); } + return components; + } + + public static BaseComponent[] fromLegacyText(String message, boolean autolink) { + final List components = fromLegacyToList(message, autolink); + // The client will crash if the array is empty if ( components.isEmpty() ) { @@ -129,17 +146,25 @@ public static BaseComponent[] fromLegacyText(String message) return components.toArray( new BaseComponent[ components.size() ] ); } + public static BaseComponent fromLegacyToComponent(String message, boolean autolink) { + final List components = fromLegacyToList(message, autolink); + switch(components.size()) { + case 0: return new TextComponent(); + case 1: return components.get(0); + default: return new TextComponent(components); + } + } + /** * The text of the component that will be displayed to the client */ - private String text; + @NonNull private String text; /** - * Creates a TextComponent with blank text. + * Creates a blank component */ - public TextComponent() - { - this.text = ""; + public TextComponent() { + this(""); } /** @@ -163,7 +188,16 @@ public TextComponent(TextComponent textComponent) public TextComponent(BaseComponent... extras) { setText( "" ); - setExtra( new ArrayList( Arrays.asList( extras ) ) ); + setExtra( extras ); + } + + public TextComponent(List extra) { + this("", extra); + } + + public TextComponent(String text, List extra) { + setText(text); + setExtra(extra); } /** @@ -185,36 +219,26 @@ protected void toPlainText(StringBuilder builder) } @Override - protected void toLegacyText(StringBuilder builder) + protected void toLegacyTextContent(ChatStringBuilder builder, ChatColor color, Set decorations) { - builder.append( getColor() ); - if ( isBold() ) - { - builder.append( ChatColor.BOLD ); - } - if ( isItalic() ) - { - builder.append( ChatColor.ITALIC ); - } - if ( isUnderlined() ) - { - builder.append( ChatColor.UNDERLINE ); - } - if ( isStrikethrough() ) - { - builder.append( ChatColor.STRIKETHROUGH ); - } - if ( isObfuscated() ) - { - builder.append( ChatColor.MAGIC ); - } + builder.format(color, decorations); builder.append( text ); - super.toLegacyText( builder ); + } + + @Override protected void toStringFirst(List fields) { + fields.add("text=\"" + getText() + "\""); + super.toStringFirst(fields); } @Override - public String toString() - { - return String.format( "TextComponent{text=%s, %s}", text, super.toString() ); + public int hashCode() { + return Objects.hashCode(super.hashCode(), text); + } + + @Override + protected boolean equals(BaseComponent that) { + return that instanceof TextComponent && + text.equals(((TextComponent) that).getText()) && + super.equals(that); } } diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java index 79c2e32e65..34d2ed167d 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java @@ -1,36 +1,41 @@ package net.md_5.bungee.api.chat; +import com.google.common.base.Objects; +import com.google.common.collect.Lists; import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.NonNull; import lombok.Setter; -import net.md_5.bungee.api.ChatColor; + import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.MissingResourceException; import java.util.ResourceBundle; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import lombok.ToString; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ChatStringBuilder; @Getter @Setter -@ToString -@NoArgsConstructor public class TranslatableComponent extends BaseComponent { - private final ResourceBundle locales = ResourceBundle.getBundle( "mojang-translations/en_US" ); - private final Pattern format = Pattern.compile( "%(?:(\\d+)\\$)?([A-Za-z%]|$)" ); + private static final ResourceBundle locales = ResourceBundle.getBundle( "mojang-translations/en_US" ); + private static final Pattern format = Pattern.compile( "%(?:(\\d+)\\$)?([A-Za-z%]|$)" ); /** * The key into the Minecraft locale files to use for the translation. The * text depends on the client's locale setting. The console is always en_US */ - private String translate; + @NonNull private String translate; + /** * The components to substitute into the translation */ - private List with; + private List with = EMPTY_COMPONENT_LIST; /** * Creates a translatable component from the original to clone it. @@ -49,7 +54,7 @@ public TranslatableComponent(TranslatableComponent original) { temp.add( baseComponent.duplicate() ); } - setWith( temp ); + setWithInternal( temp ); } } @@ -77,7 +82,7 @@ public TranslatableComponent(String translate, Object... with) temp.add( (BaseComponent) w ); } } - setWith( temp ); + setWithInternal( temp ); } /** @@ -99,11 +104,27 @@ public BaseComponent duplicate() */ public void setWith(List components) { - for ( BaseComponent component : components ) - { - component.parent = this; + if(components == null) { + setWithInternal(null); + } else { + for(BaseComponent child : components) validateChild(child); + setWithInternal(new ArrayList(components)); } - with = components; + } + + /** + * Sets the translation substitutions to be used in this component. Removes + * any previously set substitutions + * + * @param components the components to substitute + */ + public void setWith(BaseComponent... components) + { + setWith(components == null ? EMPTY_COMPONENT_LIST : Arrays.asList(components)); + } + + private void setWithInternal(List components) { + with = components == null || components.isEmpty() ? EMPTY_COMPONENT_LIST : components; } /** @@ -125,11 +146,11 @@ public void addWith(String text) */ public void addWith(BaseComponent component) { - if ( with == null ) + validateChild(component); + if ( with == EMPTY_COMPONENT_LIST ) { with = new ArrayList(); } - component.parent = this; with.add( component ); } @@ -179,7 +200,7 @@ protected void toPlainText(StringBuilder builder) } @Override - protected void toLegacyText(StringBuilder builder) + protected void toLegacyTextContent(ChatStringBuilder builder, ChatColor color, Set decorations) { String trans; try @@ -198,7 +219,7 @@ protected void toLegacyText(StringBuilder builder) int pos = matcher.start(); if ( pos != position ) { - addFormat( builder ); + builder.format( color, decorations ); builder.append( trans.substring( position, pos ) ); } position = matcher.end(); @@ -209,44 +230,54 @@ protected void toLegacyText(StringBuilder builder) case 's': case 'd': String withIndex = matcher.group( 1 ); - with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ ).toLegacyText( builder ); + with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ ).toLegacyText( builder, color, decorations ); break; case '%': - addFormat( builder ); + builder.format( color, decorations ); builder.append( '%' ); break; } } if ( trans.length() != position ) { - addFormat( builder ); + builder.format( color, decorations ); builder.append( trans.substring( position, trans.length() ) ); } - super.toLegacyText( builder ); } - private void addFormat(StringBuilder builder) - { - builder.append( getColor() ); - if ( isBold() ) - { - builder.append( ChatColor.BOLD ); - } - if ( isItalic() ) - { - builder.append( ChatColor.ITALIC ); - } - if ( isUnderlined() ) - { - builder.append( ChatColor.UNDERLINE ); - } - if ( isStrikethrough() ) - { - builder.append( ChatColor.STRIKETHROUGH ); + @Override + public boolean contains(BaseComponent child) { + if(super.contains(child)) return true; + for(BaseComponent with : getWith()) { + if(with.contains(child)) return true; } - if ( isObfuscated() ) - { - builder.append( ChatColor.MAGIC ); + return false; + } + + @Override + protected void toStringFirst(List fields) { + fields.add("translate=\"" + getTranslate() + "\""); + super.toStringFirst(fields); + } + + @Override + protected void toStringLast(List fields) { + if(getWith() != null && !getWith().isEmpty()) { + fields.add("with=[" + JOINER.join(getWith()) + "]"); } + super.toStringLast(fields); + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), translate, with); + } + + @Override + protected boolean equals(BaseComponent that) { + return that instanceof TranslatableComponent && + translate.equals(((TranslatableComponent) that).getTranslate()) && + super.equals(that) && + with.equals(((TranslatableComponent) that).getWith()); } } diff --git a/chat/src/main/java/net/md_5/bungee/chat/BaseComponentSerializer.java b/chat/src/main/java/net/md_5/bungee/chat/BaseComponentSerializer.java index 9aed9c564b..284490155e 100644 --- a/chat/src/main/java/net/md_5/bungee/chat/BaseComponentSerializer.java +++ b/chat/src/main/java/net/md_5/bungee/chat/BaseComponentSerializer.java @@ -1,6 +1,5 @@ package net.md_5.bungee.chat; -import com.google.common.base.Preconditions; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonObject; import com.google.gson.JsonSerializationContext; @@ -10,7 +9,6 @@ import net.md_5.bungee.api.chat.HoverEvent; import java.util.Arrays; -import java.util.HashSet; public class BaseComponentSerializer { @@ -78,72 +76,54 @@ protected void deserialize(JsonObject object, BaseComponent component, JsonDeser protected void serialize(JsonObject object, BaseComponent component, JsonSerializationContext context) { - boolean first = false; - if ( ComponentSerializer.serializedComponents.get() == null ) + if ( component.getColorRaw() != null ) { - first = true; - ComponentSerializer.serializedComponents.set( new HashSet() ); + object.addProperty( "color", component.getColorRaw().getName() ); } - try + if ( component.isBoldRaw() != null ) { - Preconditions.checkArgument( !ComponentSerializer.serializedComponents.get().contains( component ), "Component loop" ); - ComponentSerializer.serializedComponents.get().add( component ); - if ( component.getColorRaw() != null ) - { - object.addProperty( "color", component.getColorRaw().getName() ); - } - if ( component.isBoldRaw() != null ) - { - object.addProperty( "bold", component.isBoldRaw() ); - } - if ( component.isItalicRaw() != null ) - { - object.addProperty( "italic", component.isItalicRaw() ); - } - if ( component.isUnderlinedRaw() != null ) - { - object.addProperty( "underlined", component.isUnderlinedRaw() ); - } - if ( component.isStrikethroughRaw() != null ) - { - object.addProperty( "strikethrough", component.isStrikethroughRaw() ); - } - if ( component.isObfuscatedRaw() != null ) - { - object.addProperty( "obfuscated", component.isObfuscatedRaw() ); - } - if ( component.getInsertion() != null ) - { - object.addProperty( "insertion", component.getInsertion() ); - } + object.addProperty( "bold", component.isBoldRaw() ); + } + if ( component.isItalicRaw() != null ) + { + object.addProperty( "italic", component.isItalicRaw() ); + } + if ( component.isUnderlinedRaw() != null ) + { + object.addProperty( "underlined", component.isUnderlinedRaw() ); + } + if ( component.isStrikethroughRaw() != null ) + { + object.addProperty( "strikethrough", component.isStrikethroughRaw() ); + } + if ( component.isObfuscatedRaw() != null ) + { + object.addProperty( "obfuscated", component.isObfuscatedRaw() ); + } + if ( component.getInsertion() != null ) + { + object.addProperty( "insertion", component.getInsertion() ); + } - if ( component.getExtra() != null ) - { - object.add( "extra", context.serialize( component.getExtra() ) ); - } + if ( component.getExtra() != null && !component.getExtra().isEmpty() ) + { + object.add( "extra", context.serialize( component.getExtra() ) ); + } - //Events - if ( component.getClickEvent() != null ) - { - JsonObject clickEvent = new JsonObject(); - clickEvent.addProperty( "action", component.getClickEvent().getAction().toString().toLowerCase() ); - clickEvent.addProperty( "value", component.getClickEvent().getValue() ); - object.add( "clickEvent", clickEvent ); - } - if ( component.getHoverEvent() != null ) - { - JsonObject hoverEvent = new JsonObject(); - hoverEvent.addProperty( "action", component.getHoverEvent().getAction().toString().toLowerCase() ); - hoverEvent.add( "value", context.serialize( component.getHoverEvent().getValue() ) ); - object.add( "hoverEvent", hoverEvent ); - } - } finally + //Events + if ( component.getClickEvent() != null ) { - ComponentSerializer.serializedComponents.get().remove( component ); - if ( first ) - { - ComponentSerializer.serializedComponents.set( null ); - } + JsonObject clickEvent = new JsonObject(); + clickEvent.addProperty( "action", component.getClickEvent().getAction().toString().toLowerCase() ); + clickEvent.addProperty( "value", component.getClickEvent().getValue() ); + object.add( "clickEvent", clickEvent ); + } + if ( component.getHoverEvent() != null ) + { + JsonObject hoverEvent = new JsonObject(); + hoverEvent.addProperty( "action", component.getHoverEvent().getAction().toString().toLowerCase() ); + hoverEvent.add( "value", context.serialize( component.getHoverEvent().getValue() ) ); + object.add( "hoverEvent", hoverEvent ); } } } diff --git a/chat/src/main/java/net/md_5/bungee/chat/ComponentSerializer.java b/chat/src/main/java/net/md_5/bungee/chat/ComponentSerializer.java index 5756fc4afd..b9d795d2a5 100644 --- a/chat/src/main/java/net/md_5/bungee/chat/ComponentSerializer.java +++ b/chat/src/main/java/net/md_5/bungee/chat/ComponentSerializer.java @@ -8,23 +8,24 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ScoreComponent; +import net.md_5.bungee.api.chat.SelectorComponent; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TranslatableComponent; import java.lang.reflect.Type; -import java.util.HashSet; public class ComponentSerializer implements JsonDeserializer { private final static Gson gson = new GsonBuilder(). - registerTypeAdapter( BaseComponent.class, new ComponentSerializer() ). - registerTypeAdapter( TextComponent.class, new TextComponentSerializer() ). - registerTypeAdapter( TranslatableComponent.class, new TranslatableComponentSerializer() ). + registerTypeHierarchyAdapter( BaseComponent.class, new ComponentSerializer() ). + registerTypeHierarchyAdapter( TextComponent.class, new TextComponentSerializer() ). + registerTypeHierarchyAdapter( TranslatableComponent.class, new TranslatableComponentSerializer() ). + registerTypeHierarchyAdapter( SelectorComponent.class, new SelectorComponentSerializer() ). + registerTypeHierarchyAdapter( ScoreComponent.class, new ScoreComponentSerializer() ). create(); - public final static ThreadLocal> serializedComponents = new ThreadLocal>(); - public static BaseComponent[] parse(String json) { if ( json.startsWith( "[" ) ) @@ -59,6 +60,14 @@ public BaseComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializa { return context.deserialize( json, TranslatableComponent.class ); } + if ( object.has( "selector" ) ) + { + return context.deserialize( json, SelectorComponent.class ); + } + if ( object.has( "score" ) ) + { + return context.deserialize( json, ScoreComponent.class ); + } return context.deserialize( json, TextComponent.class ); } } diff --git a/chat/src/main/java/net/md_5/bungee/chat/ScoreComponentSerializer.java b/chat/src/main/java/net/md_5/bungee/chat/ScoreComponentSerializer.java new file mode 100644 index 0000000000..07f3576d11 --- /dev/null +++ b/chat/src/main/java/net/md_5/bungee/chat/ScoreComponentSerializer.java @@ -0,0 +1,38 @@ +package net.md_5.bungee.chat; + +import java.lang.reflect.Type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import net.md_5.bungee.api.chat.ScoreComponent; + +public class ScoreComponentSerializer extends BaseComponentSerializer implements JsonSerializer, JsonDeserializer { + + @Override + public ScoreComponent deserialize(JsonElement jsonElement, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + final JsonObject json = jsonElement.getAsJsonObject(); + final JsonObject score = json.getAsJsonObject("score"); + final ScoreComponent component = new ScoreComponent(score.get("name").getAsString(), + score.get("objective").getAsString()); + deserialize(json, component, context); + return component; + } + + @Override + public JsonElement serialize(ScoreComponent src, Type typeOfSrc, JsonSerializationContext context) { + final JsonObject score = new JsonObject(); + score.addProperty("name", src.getName()); + score.addProperty("objective", src.getObjective()); + + final JsonObject json = new JsonObject(); + serialize(json, src, context); + json.add("score", score); + + return json; + } +} diff --git a/chat/src/main/java/net/md_5/bungee/chat/SelectorComponentSerializer.java b/chat/src/main/java/net/md_5/bungee/chat/SelectorComponentSerializer.java new file mode 100644 index 0000000000..c5fbf641a5 --- /dev/null +++ b/chat/src/main/java/net/md_5/bungee/chat/SelectorComponentSerializer.java @@ -0,0 +1,31 @@ +package net.md_5.bungee.chat; + +import java.lang.reflect.Type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import net.md_5.bungee.api.chat.SelectorComponent; + +public class SelectorComponentSerializer extends BaseComponentSerializer implements JsonSerializer, JsonDeserializer { + + @Override + public SelectorComponent deserialize(JsonElement jsonElement, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + final JsonObject json = jsonElement.getAsJsonObject(); + final SelectorComponent component = new SelectorComponent(json.get("selector").getAsString()); + deserialize(json, component, context); + return component; + } + + @Override + public JsonElement serialize(SelectorComponent src, Type typeOfSrc, JsonSerializationContext context) { + final JsonObject json = new JsonObject(); + serialize(json, src, context); + json.addProperty("selector", src.getSelector()); + return json; + } +} diff --git a/chat/src/main/java/net/md_5/bungee/chat/TranslatableComponentSerializer.java b/chat/src/main/java/net/md_5/bungee/chat/TranslatableComponentSerializer.java index c01af26864..a6019d6211 100644 --- a/chat/src/main/java/net/md_5/bungee/chat/TranslatableComponentSerializer.java +++ b/chat/src/main/java/net/md_5/bungee/chat/TranslatableComponentSerializer.java @@ -19,10 +19,9 @@ public class TranslatableComponentSerializer extends BaseComponentSerializer imp @Override public TranslatableComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - TranslatableComponent component = new TranslatableComponent(); JsonObject object = json.getAsJsonObject(); + TranslatableComponent component = new TranslatableComponent(object.get( "translate" ).getAsString()); deserialize( object, component, context ); - component.setTranslate( object.get( "translate" ).getAsString() ); if ( object.has( "with" ) ) { component.setWith( Arrays.asList( (BaseComponent[]) context.deserialize( object.get( "with" ), BaseComponent[].class ) ) ); diff --git a/chat/src/test/java/net/md_5/bungee/api/chat/TextComponentTest.java b/chat/src/test/java/net/md_5/bungee/api/chat/TextComponentTest.java new file mode 100644 index 0000000000..93f93a69ca --- /dev/null +++ b/chat/src/test/java/net/md_5/bungee/api/chat/TextComponentTest.java @@ -0,0 +1,152 @@ +package net.md_5.bungee.api.chat; + +import net.md_5.bungee.api.ChatColor; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class TextComponentTest { + + @Test + public void fromLegacyNoFormatting() throws Exception { + assertEquals(new TextComponent("woot"), + TextComponent.fromLegacyToComponent("woot", false)); + } + + @Test + public void fromLegacyWithFormatting() throws Exception { + TextComponent c = new TextComponent("woot"); + c.setColor(ChatColor.GREEN); + c.setBold(true); + + assertEquals(c, TextComponent.fromLegacyToComponent(ChatColor.GREEN.toString() + ChatColor.BOLD + "woot", false)); + } + + @Test + public void fromLegacyPartialColor() throws Exception { + TextComponent one = new TextComponent("One"); + TextComponent two = new TextComponent("Two"); two.setColor(ChatColor.GREEN); + TextComponent c = new TextComponent(one, two); + + assertEquals(c, TextComponent.fromLegacyToComponent("One" + ChatColor.GREEN + "Two", false)); + } + + @Test + public void fromLegacyAutoLinkStandalone() throws Exception { + TextComponent c = new TextComponent("https://oc.tc"); + c.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://oc.tc")); + + assertEquals(c, TextComponent.fromLegacyToComponent("https://oc.tc", true)); + } + + @Test + public void fromLegacyAutoLinkInline() throws Exception { + TextComponent one = new TextComponent("Go to "); + TextComponent two = new TextComponent("https://oc.tc"); two.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://oc.tc")); + TextComponent three = new TextComponent(" for a good time"); + TextComponent c = new TextComponent(one, two, three); + + assertEquals(c, TextComponent.fromLegacyToComponent("Go to https://oc.tc for a good time", true)); + } + + @Test + public void toLegacyNoFormatting() throws Exception { + assertEquals("woot", new TextComponent("woot").toLegacyText()); + } + + @Test + public void toLegacyWithFormatting() throws Exception { + TextComponent c = new TextComponent("woot"); + c.setColor(ChatColor.GREEN); + c.setBold(true); + + assertEquals(ChatColor.GREEN.toString() + ChatColor.BOLD + "woot", + c.toLegacyText()); + } + + @Test + public void toLegacyPartialColorNoDefault() throws Exception { + TextComponent one = new TextComponent("One"); + TextComponent two = new TextComponent("Two"); two.setColor(ChatColor.GREEN); + TextComponent three = new TextComponent("Three"); + one.addExtra(two); + one.addExtra(three); + + assertEquals("One" + ChatColor.GREEN + "Two" + ChatColor.RESET + "Three", + one.toLegacyText()); + } + + @Test + public void toLegacyPartialColorWithDefault() throws Exception { + TextComponent one = new TextComponent("One"); + TextComponent two = new TextComponent("Two"); two.setColor(ChatColor.GREEN); + TextComponent three = new TextComponent("Three"); + one.addExtra(two); + one.addExtra(three); + + assertEquals(ChatColor.RED + "One" + ChatColor.GREEN + "Two" + ChatColor.RED + "Three", + one.toLegacyText(ChatColor.RED)); + } + + @Test + public void toLegacyAddDecoration() throws Exception { + TextComponent one = new TextComponent("One"); one.setColor(ChatColor.RED); + TextComponent two = new TextComponent("Two"); two.setBold(true); + one.addExtra(two); + + assertEquals(ChatColor.RED + "One" + ChatColor.BOLD + "Two", + one.toLegacyText()); + } + + @Test + public void toLegacyRemoveDecoration() throws Exception { + TextComponent one = new TextComponent("One"); one.setColor(ChatColor.RED); + TextComponent two = new TextComponent("Two"); two.setBold(true); + TextComponent three = new TextComponent("Three"); + one.addExtra(two); + one.addExtra(three); + + assertEquals(ChatColor.RED + "One" + ChatColor.BOLD + "Two" + ChatColor.RED + "Three", + one.toLegacyText()); + } + + @Test + public void toLegacyNestedDecoration() throws Exception { + TextComponent one = new TextComponent("One"); one.setColor(ChatColor.RED); + TextComponent two = new TextComponent("Two"); two.setBold(true); + TextComponent three = new TextComponent("Three"); three.setItalic(true); + one.addExtra(two); + two.addExtra(three); + two.addExtra(new TextComponent("Two")); + one.addExtra(new TextComponent("One")); + + assertEquals(ChatColor.RED + "One" + ChatColor.BOLD + "Two" + ChatColor.ITALIC + "Three" + ChatColor.RED + ChatColor.BOLD + "Two" + ChatColor.RED + "One", + one.toLegacyText()); + } + + @Test + public void toLegacyCollapseFormatting() throws Exception { + TextComponent one = new TextComponent(""); one.setColor(ChatColor.RED); + TextComponent two = new TextComponent(""); two.setColor(ChatColor.GREEN); + TextComponent three = new TextComponent("Hi"); three.setColor(ChatColor.BLUE); + TextComponent four = new TextComponent(""); four.setColor(ChatColor.YELLOW); + TextComponent five = new TextComponent("There"); five.setColor(ChatColor.BLUE); + one.addExtra(two); + one.addExtra(three); + one.addExtra(four); + one.addExtra(five); + + assertEquals(ChatColor.BLUE + "HiThere", + one.toLegacyText()); + } + + @Test + public void toLegacyWithClickEvent() throws Exception { + TextComponent one = new TextComponent("Go to "); + TextComponent two = new TextComponent("https://oc.tc"); two.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://oc.tc")); + TextComponent three = new TextComponent(" for a good time"); + TextComponent c = new TextComponent(one, two, three); + + assertEquals("Go to https://oc.tc for a good time", c.toLegacyText()); + } +} diff --git a/chat/src/test/java/net/md_5/bungee/api/chat/TranslatableComponentTest.java b/chat/src/test/java/net/md_5/bungee/api/chat/TranslatableComponentTest.java index 453d375d15..70be2889c6 100644 --- a/chat/src/test/java/net/md_5/bungee/api/chat/TranslatableComponentTest.java +++ b/chat/src/test/java/net/md_5/bungee/api/chat/TranslatableComponentTest.java @@ -13,6 +13,6 @@ public void testMissingPlaceholdersAdded() { TranslatableComponent testComponent = new TranslatableComponent( "Test string with %s placeholders: %s", "2", "aoeu" ); assertEquals( "Test string with 2 placeholders: aoeu", testComponent.toPlainText() ); - assertEquals( "§fTest string with §f2§f placeholders: §faoeu", testComponent.toLegacyText() ); + assertEquals( "Test string with 2 placeholders: aoeu", testComponent.toLegacyText() ); } } diff --git a/config/pom.xml b/config/pom.xml index 5d32d0e842..e731d711e5 100644 --- a/config/pom.xml +++ b/config/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-parent 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-config 1.11-SNAPSHOT jar @@ -19,6 +18,12 @@ Generic java configuration API intended for use with BungeeCord + + tc.oc + minecraft-api + 1.11-SNAPSHOT + compile + org.yaml snakeyaml diff --git a/config/src/main/java/net/md_5/bungee/config/Configuration.java b/config/src/main/java/net/md_5/bungee/config/Configuration.java index 967a1b2acd..38e2d6a17a 100644 --- a/config/src/main/java/net/md_5/bungee/config/Configuration.java +++ b/config/src/main/java/net/md_5/bungee/config/Configuration.java @@ -1,5 +1,6 @@ package net.md_5.bungee.config; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import java.util.ArrayList; import java.util.Collection; @@ -7,12 +8,17 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import lombok.Getter; +import tc.oc.minecraft.api.configuration.AbstractConfigurationSection; +import tc.oc.minecraft.api.configuration.InvalidConfigurationException; -public final class Configuration +public final class Configuration extends AbstractConfigurationSection implements tc.oc.minecraft.api.configuration.Configuration { private static final char SEPARATOR = '.'; + final String absolutePath; final Map self; + @Getter private final Configuration defaults; public Configuration() @@ -25,8 +31,19 @@ public Configuration(Configuration defaults) this( new LinkedHashMap(), defaults ); } + public Configuration(Configuration values, Configuration defaults) + { + this( values.self, defaults ); + } + Configuration(Map map, Configuration defaults) { + this("", map, defaults); + } + + Configuration(String absolutePath, Map map, Configuration defaults) + { + this.absolutePath = absolutePath; this.self = new LinkedHashMap<>(); this.defaults = defaults; @@ -56,7 +73,7 @@ private Configuration getSectionFor(String path) Object section = self.get( root ); if ( section == null ) { - section = new Configuration( ( defaults == null ) ? null : defaults.getSection( path ) ); + section = new Configuration( resolvePath(path), ImmutableMap.of(), (defaults == null ) ? null : defaults.getSection(path ) ); self.put( root, section ); } @@ -136,6 +153,11 @@ public Configuration getSection(String path) return (Configuration) get( path, ( def instanceof Configuration ) ? def : new Configuration( ( defaults == null ) ? null : defaults.getSection( path ) ) ); } + @Override + public Configuration needSection(String path) throws InvalidConfigurationException { + return needType(path, Configuration.class); + } + /** * Gets keys, not deep by default. * @@ -411,4 +433,9 @@ public List getList(String path, List def) Object val = get( path, def ); return ( val instanceof List ) ? (List) val : def; } + + @Override + public String getCurrentPath() { + return absolutePath; + } } diff --git a/event/pom.xml b/event/pom.xml index 65a642244e..bf8b306550 100644 --- a/event/pom.xml +++ b/event/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-parent 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-event 1.11-SNAPSHOT jar diff --git a/event/src/main/java/net/md_5/bungee/event/EventBus.java b/event/src/main/java/net/md_5/bungee/event/EventBus.java index 5b5d42010b..080349d2b9 100644 --- a/event/src/main/java/net/md_5/bungee/event/EventBus.java +++ b/event/src/main/java/net/md_5/bungee/event/EventBus.java @@ -52,7 +52,7 @@ public void post(Object event) throw new Error( "Method rejected target/argument: " + event, ex ); } catch ( InvocationTargetException ex ) { - logger.log( Level.WARNING, MessageFormat.format( "Error dispatching event {0} to listener {1}", event, method.getListener() ), ex.getCause() ); + logger.log( Level.SEVERE, MessageFormat.format( "Error dispatching event {0} to listener {1}", event, method.getListener() ), ex.getCause() ); } } } diff --git a/log/pom.xml b/log/pom.xml index 22a933f273..56528a4bc9 100644 --- a/log/pom.xml +++ b/log/pom.xml @@ -4,13 +4,13 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-parent 1.11-SNAPSHOT ../pom.xml - net.md-5 + tc.oc bungeecord-log 1.11-SNAPSHOT jar @@ -26,7 +26,7 @@ compile - net.md-5 + tc.oc bungeecord-chat ${project.version} compile diff --git a/log/src/main/java/net/md_5/bungee/log/BungeeLogger.java b/log/src/main/java/net/md_5/bungee/log/BungeeLogger.java index 9598fff324..ea042038c5 100644 --- a/log/src/main/java/net/md_5/bungee/log/BungeeLogger.java +++ b/log/src/main/java/net/md_5/bungee/log/BungeeLogger.java @@ -4,13 +4,22 @@ import java.io.IOException; import java.util.logging.FileHandler; import java.util.logging.Formatter; -import java.util.logging.Level; +import java.util.logging.LogManager; import java.util.logging.LogRecord; import java.util.logging.Logger; import jline.console.ConsoleReader; public class BungeeLogger extends Logger { + public static BungeeLogger get(String loggerName, String filePattern, ConsoleReader reader) { + final LogManager lm = LogManager.getLogManager(); + Logger logger = lm.getLogger(loggerName); + if(!(logger instanceof BungeeLogger)) { + logger = new BungeeLogger(loggerName, filePattern, reader); + lm.addLogger(logger); + } + return (BungeeLogger) logger; + } private final Formatter formatter = new ConciseFormatter(); private final LogDispatcher dispatcher = new LogDispatcher( this ); @@ -20,10 +29,11 @@ public class BungeeLogger extends Logger "CallToPrintStackTrace", "CallToThreadStartDuringObjectConstruction" }) @SuppressFBWarnings("SC_START_IN_CTOR") - public BungeeLogger(String loggerName, String filePattern, ConsoleReader reader) + private BungeeLogger(String loggerName, String filePattern, ConsoleReader reader) { super( loggerName, null ); - setLevel( Level.ALL ); + setParent(Logger.getLogger("")); + setUseParentHandlers(false); try { @@ -32,7 +42,6 @@ public BungeeLogger(String loggerName, String filePattern, ConsoleReader reader) addHandler( fileHandler ); ColouredWriter consoleHandler = new ColouredWriter( reader ); - consoleHandler.setLevel( Level.INFO ); consoleHandler.setFormatter( formatter ); addHandler( consoleHandler ); } catch ( IOException ex ) diff --git a/module/cmd-alert/pom.xml b/module/cmd-alert/pom.xml index 223d0f6ff1..4339668bb3 100644 --- a/module/cmd-alert/pom.xml +++ b/module/cmd-alert/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-module 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-module-cmd-alert 1.11-SNAPSHOT jar diff --git a/module/cmd-find/pom.xml b/module/cmd-find/pom.xml index 7635889da4..7a420ba846 100644 --- a/module/cmd-find/pom.xml +++ b/module/cmd-find/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-module 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-module-cmd-find 1.11-SNAPSHOT jar diff --git a/module/cmd-list/pom.xml b/module/cmd-list/pom.xml index 76158bcb54..90ebd4f5dd 100644 --- a/module/cmd-list/pom.xml +++ b/module/cmd-list/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-module 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-module-cmd-list 1.11-SNAPSHOT jar diff --git a/module/cmd-send/pom.xml b/module/cmd-send/pom.xml index 8737541900..37d2df285f 100644 --- a/module/cmd-send/pom.xml +++ b/module/cmd-send/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-module 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-module-cmd-send 1.11-SNAPSHOT jar diff --git a/module/cmd-server/pom.xml b/module/cmd-server/pom.xml index 8a0ed9de39..f197d3a0ce 100644 --- a/module/cmd-server/pom.xml +++ b/module/cmd-server/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-module 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-module-cmd-server 1.11-SNAPSHOT jar diff --git a/module/pom.xml b/module/pom.xml index 2d517465b9..a70727e38b 100644 --- a/module/pom.xml +++ b/module/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-parent 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-module 1.11-SNAPSHOT pom @@ -33,7 +32,7 @@ - net.md-5 + tc.oc bungeecord-api ${project.version} compile diff --git a/module/reconnect-yaml/pom.xml b/module/reconnect-yaml/pom.xml index c08ad70d47..11cf9d51ff 100644 --- a/module/reconnect-yaml/pom.xml +++ b/module/reconnect-yaml/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-module 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-module-reconnect-yaml 1.11-SNAPSHOT jar diff --git a/native/pom.xml b/native/pom.xml index 940d257e1e..7a2dadce14 100644 --- a/native/pom.xml +++ b/native/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-parent 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-native 1.11-SNAPSHOT jar diff --git a/pom.xml b/pom.xml index 7f9564c6d7..4a17e53301 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ 9 - net.md-5 + tc.oc bungeecord-parent 1.11-SNAPSHOT pom @@ -29,6 +29,16 @@ repo + + + overcast-deployment + https://repo.oc.tc/content/repositories/releases + + + overcast-deployment + https://repo.oc.tc/content/repositories/snapshots + + @@ -117,31 +127,6 @@ - - org.codehaus.mojo - animal-sniffer-maven-plugin - 1.13 - - - process-classes - - check - - - - - - java.lang.ClassLoader - java.lang.Throwable - java.util.Locale - - - org.codehaus.mojo.signature - java16 - 1.1 - - - org.apache.maven.plugins diff --git a/protocol/pom.xml b/protocol/pom.xml index 63d04980a9..d73892d9a4 100644 --- a/protocol/pom.xml +++ b/protocol/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-parent 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-protocol 1.11-SNAPSHOT jar @@ -20,7 +19,7 @@ - net.md-5 + tc.oc bungeecord-chat ${project.version} compile diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/BadPacketException.java b/protocol/src/main/java/net/md_5/bungee/protocol/BadPacketException.java index 6c0ef4dfa0..d3ee377d8b 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/BadPacketException.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/BadPacketException.java @@ -1,6 +1,8 @@ package net.md_5.bungee.protocol; -public class BadPacketException extends RuntimeException +import io.netty.handler.codec.DecoderException; + +public class BadPacketException extends DecoderException { public BadPacketException(String message) diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/MinecraftDecoder.java b/protocol/src/main/java/net/md_5/bungee/protocol/MinecraftDecoder.java index e7cb380342..f7566f625c 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/MinecraftDecoder.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/MinecraftDecoder.java @@ -30,8 +30,14 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t DefinedPacket packet = prot.createPacket( packetId, protocolVersion ); if ( packet != null ) { - packet.read( in, prot.getDirection(), protocolVersion ); - + try + { + packet.read( in, prot.getDirection(), protocolVersion ); + } + catch( IndexOutOfBoundsException e ) + { + throw new BadPacketException( "Unexpected end of packet " + packet.getClass(), e ); + } if ( in.isReadable() ) { throw new BadPacketException( "Did not read all bytes from packet " + packet.getClass() + " " + packetId + " Protocol " + protocol + " Direction " + prot ); diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java b/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java index 01f23441d6..685b7db179 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java @@ -285,9 +285,9 @@ public final DefinedPacket createPacket(int id, int version) ProtocolData protocolData = getProtocolData( version ); if (protocolData == null) { - throw new BadPacketException( "Unsupported protocol version" ); + return null; // Unsupported protocol version } - if ( id > MAX_PACKET_ID ) + if ( id < 0 || id > MAX_PACKET_ID ) { throw new BadPacketException( "Packet with id " + id + " outside of range " ); } diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/Varint21FrameDecoder.java b/protocol/src/main/java/net/md_5/bungee/protocol/Varint21FrameDecoder.java index 29e54dba06..f5f8d863a3 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/Varint21FrameDecoder.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/Varint21FrameDecoder.java @@ -4,7 +4,6 @@ import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; -import io.netty.handler.codec.CorruptedFrameException; import java.util.List; @@ -33,7 +32,7 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t int length = DefinedPacket.readVarInt( Unpooled.wrappedBuffer( buf ) ); if ( length == 0 ) { - throw new CorruptedFrameException( "Empty Packet!" ); + throw new BadPacketException( "Empty Packet!" ); } if ( in.readableBytes() < length ) @@ -64,6 +63,6 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t } } - throw new CorruptedFrameException( "length wider than 21-bit" ); + throw new BadPacketException( "length wider than 21-bit" ); } } diff --git a/proxy/pom.xml b/proxy/pom.xml index 703fbda3fe..68776775a1 100644 --- a/proxy/pom.xml +++ b/proxy/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-parent 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-proxy 1.11-SNAPSHOT jar @@ -39,31 +38,31 @@ compile - net.md-5 + tc.oc bungeecord-api ${project.version} compile - net.md-5 + tc.oc bungeecord-log ${project.version} compile - net.md-5 + tc.oc bungeecord-native ${project.version} compile - net.md-5 + tc.oc bungeecord-protocol ${project.version} compile - net.md-5 + tc.oc bungeecord-query ${project.version} compile diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index 159dab671a..b1a71344c0 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -4,6 +4,7 @@ import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -35,6 +36,7 @@ import java.util.MissingResourceException; import java.util.PropertyResourceBundle; import java.util.ResourceBundle; +import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.UUID; @@ -55,6 +57,8 @@ import net.md_5.bungee.api.ServerPing; import net.md_5.bungee.api.Title; import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ScoreComponent; +import net.md_5.bungee.api.chat.SelectorComponent; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TranslatableComponent; import net.md_5.bungee.api.config.ConfigurationAdapter; @@ -64,6 +68,8 @@ import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.PluginManager; import net.md_5.bungee.chat.ComponentSerializer; +import net.md_5.bungee.chat.ScoreComponentSerializer; +import net.md_5.bungee.chat.SelectorComponentSerializer; import net.md_5.bungee.chat.TextComponentSerializer; import net.md_5.bungee.chat.TranslatableComponentSerializer; import net.md_5.bungee.command.CommandBungee; @@ -88,6 +94,7 @@ import net.md_5.bungee.scheduler.BungeeScheduler; import net.md_5.bungee.util.CaseInsensitiveMap; import org.fusesource.jansi.AnsiConsole; +import tc.oc.minecraft.api.plugin.PluginFinder; /** * Main BungeeCord proxy class. @@ -130,7 +137,7 @@ public class BungeeCord extends ProxyServer * Plugin manager. */ @Getter - public final PluginManager pluginManager = new PluginManager( this ); + public final PluginManager pluginManager; @Getter @Setter private ReconnectHandler reconnectHandler; @@ -150,24 +157,14 @@ public class BungeeCord extends ProxyServer .registerTypeAdapter( BaseComponent.class, new ComponentSerializer() ) .registerTypeAdapter( TextComponent.class, new TextComponentSerializer() ) .registerTypeAdapter( TranslatableComponent.class, new TranslatableComponentSerializer() ) + .registerTypeAdapter( SelectorComponent.class, new SelectorComponentSerializer() ) + .registerTypeAdapter( ScoreComponent.class, new ScoreComponentSerializer() ) .registerTypeAdapter( ServerPing.PlayerInfo.class, new PlayerInfoSerializer() ) .registerTypeAdapter( Favicon.class, Favicon.getFaviconTypeAdapter() ).create(); @Getter private ConnectionThrottle connectionThrottle; private final ModuleManager moduleManager = new ModuleManager(); - - { - // TODO: Proper fallback when we interface the manager - getPluginManager().registerCommand( null, new CommandReload() ); - getPluginManager().registerCommand( null, new CommandEnd() ); - getPluginManager().registerCommand( null, new CommandIP() ); - getPluginManager().registerCommand( null, new CommandBungee() ); - getPluginManager().registerCommand( null, new CommandPerms() ); - - registerChannel( "BungeeCord" ); - } - public static BungeeCord getInstance() { return (BungeeCord) ProxyServer.getInstance(); @@ -179,7 +176,8 @@ public BungeeCord() throws IOException // Java uses ! to indicate a resource inside of a jar/zip/other container. Running Bungee from within a directory that has a ! will cause this to muck up. Preconditions.checkState( new File( "." ).getAbsolutePath().indexOf( '!' ) == -1, "Cannot use BungeeCord in directory with ! in path." ); - System.setSecurityManager( new BungeeSecurityManager() ); + // Overcast - disable security manager + // System.setSecurityManager( new BungeeSecurityManager() ); try { @@ -211,8 +209,10 @@ public BungeeCord() throws IOException consoleReader = new ConsoleReader(); consoleReader.setExpandEvents( false ); - logger = new BungeeLogger( "BungeeCord", "proxy.log", consoleReader ); - System.setErr( new PrintStream( new LoggingOutputStream( logger, Level.SEVERE ), true ) ); + logger = BungeeLogger.get( "BungeeCord", "proxy.log", consoleReader ); + + // Overcast - stderr gets a lot of non-error output, so log it at WARNING level instead of SEVERE + System.setErr( new PrintStream( new LoggingOutputStream( logger, Level.WARNING ), true ) ); System.setOut( new PrintStream( new LoggingOutputStream( logger, Level.INFO ), true ) ); if ( !Boolean.getBoolean( "net.md_5.bungee.native.disable" ) ) @@ -232,6 +232,17 @@ public BungeeCord() throws IOException logger.info( "Using standard Java compressor. To enable zero copy compression, run on 64 bit Linux" ); } } + + pluginManager = new PluginManager( this ); + + // TODO: Proper fallback when we interface the manager + getPluginManager().registerCommand( null, new CommandReload() ); + getPluginManager().registerCommand( null, new CommandEnd() ); + getPluginManager().registerCommand( null, new CommandIP() ); + getPluginManager().registerCommand( null, new CommandBungee() ); + getPluginManager().registerCommand( null, new CommandPerms() ); + + registerChannel( "BungeeCord" ); } /** @@ -411,7 +422,7 @@ public void run() // TODO: Fix this shit getLogger().info( "Disabling plugins" ); - for ( Plugin plugin : Lists.reverse( new ArrayList<>( pluginManager.getPlugins() ) ) ) + for ( Plugin plugin : Lists.reverse( new ArrayList<>( pluginManager.getEnabledPlugins() ) ) ) { try { @@ -556,10 +567,16 @@ public Map getServers() return config.getServers(); } + @Override + public Map getServersCopy() + { + return config.getServersCopy(); + } + @Override public ServerInfo getServerInfo(String name) { - return getServers().get( name ); + return config.getServerInfo( name ); } @Override @@ -606,6 +623,11 @@ public ServerInfo constructServerInfo(String name, InetSocketAddress address, St return new BungeeServerInfo( name, address, motd, restricted ); } + @Override + public tc.oc.minecraft.api.command.ConsoleCommandSender getConsoleSender() { + return ConsoleCommandSender.getInstance(); + } + @Override public CommandSender getConsole() { @@ -695,4 +717,14 @@ public Title createTitle() { return new BungeeTitle(); } + + @Override + public Set getProtocolVersions() { + return ImmutableSet.copyOf(ProtocolConstants.SUPPORTED_VERSION_IDS); + } + + @Override + public PluginFinder getPluginFinder() { + return pluginManager; + } } diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java b/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java index 1dd0aeba8a..f8699d26ad 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java @@ -13,6 +13,8 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.Queue; +import java.util.UUID; + import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Synchronized; @@ -69,6 +71,32 @@ public Collection getPlayers() return Collections.unmodifiableCollection( new HashSet( players ) ); } + @Override + public Collection getOnlinePlayers() + { + return getPlayers(); + } + + @Override + public ProxiedPlayer getPlayerExact(String name) + { + for(ProxiedPlayer player : getPlayers()) + { + if(name.equalsIgnoreCase(player.getName())) return player; + } + return null; + } + + @Override + public ProxiedPlayer getPlayer(UUID id) + { + for(ProxiedPlayer player : getPlayers()) + { + if(id.equals(player.getUniqueId())) return player; + } + return null; + } + @Override public boolean canAccess(CommandSender player) { diff --git a/proxy/src/main/java/net/md_5/bungee/ServerConnector.java b/proxy/src/main/java/net/md_5/bungee/ServerConnector.java index 2014b0c492..90d91d7d2d 100644 --- a/proxy/src/main/java/net/md_5/bungee/ServerConnector.java +++ b/proxy/src/main/java/net/md_5/bungee/ServerConnector.java @@ -33,6 +33,7 @@ import net.md_5.bungee.protocol.packet.Handshake; import net.md_5.bungee.protocol.packet.Kick; import net.md_5.bungee.protocol.packet.Login; +import net.md_5.bungee.protocol.packet.LoginRequest; import net.md_5.bungee.protocol.packet.LoginSuccess; import net.md_5.bungee.protocol.packet.PluginMessage; import net.md_5.bungee.protocol.packet.Respawn; @@ -48,6 +49,7 @@ public class ServerConnector extends PacketHandler private final UserConnection user; private final BungeeServerInfo target; private State thisState = State.LOGIN_SUCCESS; + private final String fakeUsername; @Getter private ForgeServerHandler handshakeHandler; private boolean obsolete; @@ -105,7 +107,7 @@ public void connected(ChannelWrapper channel) throws Exception channel.write( copiedHandshake ); channel.setProtocol( Protocol.LOGIN ); - channel.write( user.getPendingConnection().getLoginRequest() ); + channel.write(new LoginRequest(fakeUsername != null ? fakeUsername : user.getPendingConnection().getName())); } @Override diff --git a/proxy/src/main/java/net/md_5/bungee/UserConnection.java b/proxy/src/main/java/net/md_5/bungee/UserConnection.java index d620fbf598..652224bc71 100644 --- a/proxy/src/main/java/net/md_5/bungee/UserConnection.java +++ b/proxy/src/main/java/net/md_5/bungee/UserConnection.java @@ -149,6 +149,9 @@ public void sendPacket(DefinedPacket packet) } }; + @Getter + private Throwable disconnectException; + public void init() { this.entityRewrite = EntityMap.getEntityMap( getPendingConnection().getVersion() ); @@ -241,7 +244,11 @@ public ServerInfo updateAndGetNextServer(ServerInfo currentTarget) return next; } - public void connect(ServerInfo info, final Callback callback, final boolean retry) + public void connect(ServerInfo info, final Callback callback, final boolean retry) { + connect(info, callback, retry, false); + } + + public void connect(ServerInfo info, final Callback callback, final boolean retry, final boolean quiet) { Preconditions.checkNotNull( info, "info" ); @@ -256,6 +263,7 @@ public void connect(ServerInfo info, final Callback callback, final boo } final BungeeServerInfo target = (BungeeServerInfo) event.getTarget(); // Update in case the event changed target + final String fakeUsername = event.getFakeUsername(); if ( getServer() != null && Objects.equal( getServer().getInfo(), target ) ) { @@ -264,7 +272,9 @@ public void connect(ServerInfo info, final Callback callback, final boo callback.done( false, null ); } - sendMessage( bungee.getTranslation( "already_connected" ) ); + if(!quiet) { + sendMessage( bungee.getTranslation( "already_connected" ) ); + } return; } if ( pendingConnects.contains( target ) ) @@ -274,7 +284,9 @@ public void connect(ServerInfo info, final Callback callback, final boo callback.done( false, null ); } - sendMessage( bungee.getTranslation( "already_connecting" ) ); + if(!quiet) { + sendMessage( bungee.getTranslation( "already_connecting" ) ); + } return; } @@ -288,7 +300,7 @@ protected void initChannel(Channel ch) throws Exception PipelineUtils.BASE.initChannel( ch ); ch.pipeline().addAfter( PipelineUtils.FRAME_DECODER, PipelineUtils.PACKET_DECODER, new MinecraftDecoder( Protocol.HANDSHAKE, false, getPendingConnection().getVersion() ) ); ch.pipeline().addAfter( PipelineUtils.FRAME_PREPENDER, PipelineUtils.PACKET_ENCODER, new MinecraftEncoder( Protocol.HANDSHAKE, false, getPendingConnection().getVersion() ) ); - ch.pipeline().get( HandlerBoss.class ).setHandler( new ServerConnector( bungee, UserConnection.this, target ) ); + ch.pipeline().get( HandlerBoss.class ).setHandler( new ServerConnector( bungee, UserConnection.this, target, fakeUsername ) ); } }; ChannelFutureListener listener = new ChannelFutureListener() @@ -310,13 +322,14 @@ public void operationComplete(ChannelFuture future) throws Exception ServerInfo def = updateAndGetNextServer( target ); if ( retry && def != null && ( getServer() == null || def != getServer().getInfo() ) ) { - sendMessage( bungee.getTranslation( "fallback_lobby" ) ); + if(!quiet) { + sendMessage( bungee.getTranslation( "fallback_lobby" ) ); + } connect( def, null, false ); } else if ( dimensionChange ) { disconnect( bungee.getTranslation( "fallback_kick", future.cause().getClass().getName() ) ); - } else - { + } else if(!quiet) { sendMessage( bungee.getTranslation( "fallback_kick", future.cause().getClass().getName() ) ); } } @@ -336,6 +349,13 @@ public void operationComplete(ChannelFuture future) throws Exception b.connect().addListener( listener ); } + public void disconnect(Throwable exception) { + if(disconnectException == null) { + disconnectException = exception; + } + disconnect(Util.exception(exception)); + } + @Override public void disconnect(String reason) { @@ -550,6 +570,12 @@ public Locale getLocale() return ( locale == null && settings != null ) ? locale = Locale.forLanguageTag( settings.getLocale().replaceAll( "_", "-" ) ) : locale; } + @Override + public Locale getCurrentLocale() + { + return getLocale(); + } + @Override public boolean isForgeUser() { @@ -622,4 +648,9 @@ public boolean isConnected() { return !ch.isClosed(); } + + @Override + public int getProtocolVersion() { + return getPendingConnection().getVersion(); + } } diff --git a/proxy/src/main/java/net/md_5/bungee/command/ConsoleCommandSender.java b/proxy/src/main/java/net/md_5/bungee/command/ConsoleCommandSender.java index 49dde16ffe..326e89abf9 100644 --- a/proxy/src/main/java/net/md_5/bungee/command/ConsoleCommandSender.java +++ b/proxy/src/main/java/net/md_5/bungee/command/ConsoleCommandSender.java @@ -10,7 +10,7 @@ /** * Command sender representing the proxy console. */ -public class ConsoleCommandSender implements CommandSender +public class ConsoleCommandSender implements CommandSender, tc.oc.minecraft.api.command.ConsoleCommandSender { @Getter diff --git a/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java b/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java index 25d87d97d6..cacb30af22 100644 --- a/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java +++ b/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java @@ -1,6 +1,7 @@ package net.md_5.bungee.conf; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; import gnu.trove.map.TMap; import java.io.File; import java.io.IOException; @@ -11,6 +12,7 @@ import java.util.logging.Level; import javax.imageio.ImageIO; import lombok.Getter; +import lombok.Synchronized; import net.md_5.bungee.api.Favicon; import net.md_5.bungee.api.ProxyConfig; import net.md_5.bungee.api.ProxyServer; @@ -57,6 +59,7 @@ public class Configuration implements ProxyConfig private boolean ipForward; private Favicon favicon; private int compressionThreshold = 256; + private boolean requireAllPlugins; public void load() { @@ -84,6 +87,7 @@ public void load() throttle = adapter.getInt( "connection_throttle", throttle ); ipForward = adapter.getBoolean( "ip_forward", ipForward ); compressionThreshold = adapter.getInt( "network_compression_threshold", compressionThreshold ); + requireAllPlugins = adapter.getBoolean( "require_all_plugins", requireAllPlugins ); disabledCommands = new CaseInsensitiveSet( (Collection) adapter.getList( "disabled_commands", Arrays.asList( "disabledcommandhere" ) ) ); @@ -142,4 +146,69 @@ public Favicon getFaviconObject() { return favicon; } + + @Override + @Synchronized("servers") + public Map getServersCopy() { + return ImmutableMap.copyOf( servers ); + } + + @Override + @Synchronized("servers") + public ServerInfo getServerInfo(String name) + { + return this.servers.get( name ); + } + + @Override + @Synchronized("servers") + public ServerInfo addServer(ServerInfo server) + { + return this.servers.put( server.getName(), server ); + } + + @Override + @Synchronized("servers") + public boolean addServers(Collection servers) + { + boolean changed = false; + for ( ServerInfo server : servers ) + { + if ( server != this.servers.put( server.getName(), server ) ) changed = true; + } + return changed; + } + + @Override + @Synchronized("servers") + public ServerInfo removeServerNamed(String name) + { + return this.servers.remove( name ); + } + + @Override + @Synchronized("servers") + public ServerInfo removeServer(ServerInfo server) + { + return this.servers.remove( server.getName() ); + } + + @Override + @Synchronized("servers") + public boolean removeServersNamed(Collection names) + { + return this.servers.keySet().removeAll( names ); + } + + @Override + @Synchronized("servers") + public boolean removeServers(Collection servers) + { + boolean changed = false; + for ( ServerInfo server : servers ) + { + if ( null != this.servers.remove( server.getName() ) ) changed = true; + } + return changed; + } } diff --git a/proxy/src/main/java/net/md_5/bungee/connection/DownstreamBridge.java b/proxy/src/main/java/net/md_5/bungee/connection/DownstreamBridge.java index c2dd942ff3..28f50bc6a5 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/DownstreamBridge.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/DownstreamBridge.java @@ -68,7 +68,7 @@ public void exception(Throwable t) throws Exception con.sendMessage( bungee.getTranslation( "server_went_down" ) ); } else { - con.disconnect( Util.exception( t ) ); + con.disconnect( t ); } } @@ -328,6 +328,14 @@ public void handle(PluginMessage pluginMessage) throws Exception con.connect( server ); } } + if ( subChannel.equals( "ConnectQuiet" ) ) + { + ServerInfo server = bungee.getServerInfo( in.readUTF() ); + if ( server != null ) + { + con.connect( server, null, false, true ); + } + } if ( subChannel.equals( "ConnectOther" ) ) { ProxiedPlayer player = bungee.getPlayer( in.readUTF() ); diff --git a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java index bdcbdb7ad1..adab8fa9ba 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java @@ -111,7 +111,7 @@ public boolean shouldHandle(PacketWrapper packet) throws Exception private enum State { - HANDSHAKE, STATUS, PING, USERNAME, ENCRYPT, FINISHED; + HANDSHAKE, STATUS, USERNAME, ENCRYPT, FINISHED; } @Override @@ -243,14 +243,12 @@ public void done(ProxyPingEvent pingResult, Throwable error) motd, BungeeCord.getInstance().config.getFaviconObject() ), null ); } - - thisState = State.PING; } @Override public void handle(PingPacket ping) throws Exception { - Preconditions.checkState( thisState == State.PING, "Not expecting PING" ); + Preconditions.checkState( thisState == State.STATUS, "Not expecting PING" ); unsafe.sendPacket( ping ); disconnect( "" ); } @@ -544,7 +542,7 @@ public void disconnect(final BaseComponent... reason) @Override public void run() { - if ( thisState != State.STATUS && thisState != State.PING ) + if ( thisState != State.STATUS ) { unsafe().sendPacket( new Kick( ComponentSerializer.toString( reason ) ) ); } diff --git a/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java b/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java index 92d1da05b8..bf74eb1d11 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java @@ -44,7 +44,7 @@ public UpstreamBridge(ProxyServer bungee, UserConnection con) @Override public void exception(Throwable t) throws Exception { - con.disconnect( Util.exception( t ) ); + con.disconnect( t ); } @Override diff --git a/proxy/src/main/java/net/md_5/bungee/entitymap/EntityMap.java b/proxy/src/main/java/net/md_5/bungee/entitymap/EntityMap.java index 34ddc19f5e..6ce1078385 100644 --- a/proxy/src/main/java/net/md_5/bungee/entitymap/EntityMap.java +++ b/proxy/src/main/java/net/md_5/bungee/entitymap/EntityMap.java @@ -5,6 +5,7 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; import net.md_5.bungee.protocol.DefinedPacket; +import net.md_5.bungee.protocol.Protocol; import net.md_5.bungee.protocol.ProtocolConstants; /** @@ -107,12 +108,15 @@ private static void rewrite(ByteBuf packet, int oldId, int newId, boolean[] ints int packetId = DefinedPacket.readVarInt( packet ); int packetIdLength = packet.readerIndex() - readerIndex; - if ( ints[packetId] ) + if ( 0 <= packetId && packetId < Protocol.MAX_PACKET_ID) { - rewriteInt( packet, oldId, newId, readerIndex + packetIdLength ); - } else if ( varints[packetId] ) - { - rewriteVarInt( packet, oldId, newId, readerIndex + packetIdLength ); + if ( ints[packetId] ) + { + rewriteInt( packet, oldId, newId, readerIndex + packetIdLength ); + } else if ( varints[packetId] ) + { + rewriteVarInt(packet, oldId, newId, readerIndex + packetIdLength); + } } packet.readerIndex( readerIndex ); } diff --git a/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java b/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java index 0b0dd736e5..a2bc0831dc 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java @@ -13,6 +13,8 @@ import net.md_5.bungee.protocol.PacketWrapper; import net.md_5.bungee.protocol.Protocol; +import java.util.NoSuchElementException; + public class ChannelWrapper { @@ -110,7 +112,12 @@ public void setCompressionThreshold(int compressionThreshold) { if ( ch.pipeline().get( PacketCompressor.class ) == null && compressionThreshold != -1 ) { - addBefore( PipelineUtils.PACKET_ENCODER, "compress", new PacketCompressor() ); + try { + addBefore(PipelineUtils.PACKET_ENCODER, "compress", new PacketCompressor()); + } catch(NoSuchElementException ex) { + // Sometimes packet-encoder is not in the pipeline, probably when the client disconnects soon after connecting + return; + } } if ( compressionThreshold != -1 ) { diff --git a/proxy/src/main/java/net/md_5/bungee/netty/HandlerBoss.java b/proxy/src/main/java/net/md_5/bungee/netty/HandlerBoss.java index 3efeff8178..bf504a110c 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/HandlerBoss.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/HandlerBoss.java @@ -7,6 +7,8 @@ import io.netty.handler.timeout.ReadTimeoutException; import java.io.IOException; import java.util.logging.Level; +import java.util.logging.LogRecord; + import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.connection.CancelSendSignal; import net.md_5.bungee.connection.InitialHandler; @@ -119,7 +121,10 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E } ); } else { - ProxyServer.getInstance().getLogger().log( Level.SEVERE, handler + " - encountered exception", cause ); + final LogRecord record = new LogRecord( Level.SEVERE, handler + " - encountered exception" ); + record.setThrown( cause ); + record.setParameters( new Object[]{ ctx.channel().remoteAddress() } ); + ProxyServer.getInstance().getLogger().log( record ); } if ( handler != null ) diff --git a/proxy/src/test/java/net/md_5/bungee/chat/ComponentsTest.java b/proxy/src/test/java/net/md_5/bungee/chat/ComponentsTest.java index 004a2b7a88..e9985eac40 100644 --- a/proxy/src/test/java/net/md_5/bungee/chat/ComponentsTest.java +++ b/proxy/src/test/java/net/md_5/bungee/chat/ComponentsTest.java @@ -1,5 +1,7 @@ package net.md_5.bungee.chat; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.ClickEvent; @@ -35,8 +37,8 @@ public void testLegacyConverter() Assert.assertEquals( "Text http://spigotmc.org google.com/test", BaseComponent.toPlainText( test2 ) ); //The extra ChatColor.WHITEs are sometimes inserted when not needed but it doesn't change the result - Assert.assertEquals( ChatColor.WHITE + "Text " + ChatColor.WHITE + "http://spigotmc.org" + ChatColor.WHITE - + " " + ChatColor.GREEN + "google.com/test", BaseComponent.toLegacyText( test2 ) ); + Assert.assertEquals( ChatColor.WHITE + "Text " + ChatColor.WHITE + "http://spigotmc.org" + ChatColor.WHITE + " " + ChatColor.GREEN + "google.com/test", + BaseComponent.toLegacyText( ChatColor.WHITE, ImmutableSet.of(), test2 ) ); ClickEvent url1 = test2[1].getClickEvent(); Assert.assertNotNull( url1 ); @@ -60,13 +62,13 @@ public void testTranslateComponent() Assert.assertEquals( "Given Golden Sword * 5 to thinkofdeath", translatableComponent.toPlainText() ); Assert.assertEquals( ChatColor.WHITE + "Given " + ChatColor.AQUA + "Golden Sword" + ChatColor.WHITE - + " * " + ChatColor.WHITE + "5" + ChatColor.WHITE + " to " + ChatColor.WHITE + "thinkofdeath", - translatableComponent.toLegacyText() ); + + " * 5 to thinkofdeath", + translatableComponent.toLegacyText(ChatColor.WHITE) ); TranslatableComponent positional = new TranslatableComponent( "book.pageIndicator", "5", "50" ); Assert.assertEquals( "Page 5 of 50", positional.toPlainText() ); - Assert.assertEquals( ChatColor.WHITE + "Page " + ChatColor.WHITE + "5" + ChatColor.WHITE + " of " + ChatColor.WHITE + "50", positional.toLegacyText() ); + Assert.assertEquals( ChatColor.WHITE + "Page 5 of 50", positional.toLegacyText(ChatColor.WHITE) ); } @Test @@ -123,50 +125,58 @@ public void testBuilderFormatRetention() Assert.assertEquals( eventRetention[1].getClickEvent(), testClickEvent ); } - @Test(expected = IllegalArgumentException.class) + @Test public void testLoopSimple() { TextComponent component = new TextComponent( "Testing" ); - component.addExtra( component ); - ComponentSerializer.toString( component ); + + try { + component.addExtra( component ); + Assert.fail(); + } catch(IllegalArgumentException ignored) {} + + try { + component.setExtra( component ); + Assert.fail(); + } catch(IllegalArgumentException ignored) {} + + try { + component.setExtra(ImmutableList.of(component)); + Assert.fail(); + } catch(IllegalArgumentException ignored) {} } - @Test(expected = IllegalArgumentException.class) + @Test public void testLoopComplex() { TextComponent a = new TextComponent( "A" ); TextComponent b = new TextComponent( "B" ); - b.setColor( ChatColor.AQUA ); TextComponent c = new TextComponent( "C" ); - c.setColor( ChatColor.RED ); a.addExtra( b ); b.addExtra( c ); - c.addExtra( a ); - ComponentSerializer.toString( a ); + + try { + c.addExtra( a ); + Assert.fail(); + } catch(IllegalArgumentException ignored) {} } @Test - public void testRepeated() - { - TextComponent a = new TextComponent( "A" ); - TextComponent b = new TextComponent( "B" ); - b.setColor( ChatColor.AQUA ); - a.addExtra( b ); - a.addExtra( b ); - ComponentSerializer.toString( a ); + public void testLoopInTranslatable() { + TranslatableComponent c = new TranslatableComponent("hi"); + try { + c.addWith(c); + Assert.fail(); + } catch(IllegalArgumentException ignored) {} } - @Test(expected = IllegalArgumentException.class) - public void testRepeatedError() + @Test + public void testRepeated() { TextComponent a = new TextComponent( "A" ); TextComponent b = new TextComponent( "B" ); b.setColor( ChatColor.AQUA ); - TextComponent c = new TextComponent( "C" ); - c.setColor( ChatColor.RED ); a.addExtra( b ); - a.addExtra( c ); - c.addExtra( a ); a.addExtra( b ); ComponentSerializer.toString( a ); } diff --git a/query/pom.xml b/query/pom.xml index fbadb18910..65f7684dd2 100644 --- a/query/pom.xml +++ b/query/pom.xml @@ -4,13 +4,12 @@ 4.0.0 - net.md-5 + tc.oc bungeecord-parent 1.11-SNAPSHOT ../pom.xml - net.md-5 bungeecord-query 1.11-SNAPSHOT jar @@ -26,7 +25,7 @@ compile - net.md-5 + tc.oc bungeecord-api ${project.version} compile