Merge branch 'master' of https://github.com/SpigotMC/BungeeCord into patch/bossbar

This commit is contained in:
MrIvanPlays 2020-06-24 11:28:24 +03:00
commit ad1ef316a3
No known key found for this signature in database
GPG Key ID: 95CC87CFF7202863
116 changed files with 2956 additions and 916 deletions

21
.github/workflows/maven.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: Maven Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java: [8, 11]
name: Java ${{ matrix.java }}
steps:
- uses: actions/checkout@v1
- uses: actions/setup-java@v1
with:
java-version: ${{ matrix.java }}
- run: java -version && mvn --version
- run: mvn --activate-profiles dist --no-transfer-progress package

2
.gitignore vendored
View File

@ -24,7 +24,7 @@ dist/
manifest.mf
# Mac filesystem dust
.DS_Store/
.DS_Store
# intellij
*.iml

View File

@ -1,8 +0,0 @@
sudo: false
language: java
jdk:
- openjdk7
- oraclejdk7
- oraclejdk8
notifications:
email: false

View File

@ -23,4 +23,4 @@ Binaries
--------
Precompiled binaries are available for end users on [Jenkins](https://www.spigotmc.org/go/bungeecord-dl).
(c) 2012-2018 SpigotMC Pty. Ltd.
(c) 2012-2020 SpigotMC Pty. Ltd.

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-API</name>
@ -43,5 +43,17 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-unix-common</artifactId>
<version>${netty.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.25</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -2,7 +2,9 @@ package net.md_5.bungee;
import com.google.common.base.Joiner;
import com.google.common.primitives.UnsignedLongs;
import io.netty.channel.unix.DomainSocketAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.UUID;
@ -21,15 +23,30 @@ public class Util
* @param hostline in the format of 'host:port'
* @return the constructed hostname + port.
*/
public static InetSocketAddress getAddr(String hostline)
public static SocketAddress getAddr(String hostline)
{
URI uri;
URI uri = null;
try
{
uri = new URI( "tcp://" + hostline );
uri = new URI( hostline );
} catch ( URISyntaxException ex )
{
throw new IllegalArgumentException( "Bad hostline: " + hostline, ex );
}
if ( uri != null && "unix".equals( uri.getScheme() ) )
{
return new DomainSocketAddress( uri.getPath() );
}
if ( uri == null || uri.getHost() == null )
{
try
{
uri = new URI( "tcp://" + hostline );
} catch ( URISyntaxException ex )
{
throw new IllegalArgumentException( "Bad hostline: " + hostline, ex );
}
}
if ( uri.getHost() == null )

View File

@ -16,52 +16,96 @@ public interface ProxyConfig
/**
* Time before users are disconnected due to no network activity.
*
* @return timeout
*/
int getTimeout();
/**
* UUID used for metrics.
*
* @return uuid
*/
String getUuid();
/**
* Set of all listeners.
*
* @return listeners
*/
Collection<ListenerInfo> getListeners();
/**
* Set of all servers.
*
* @return servers
*/
Map<String, ServerInfo> getServers();
/**
* Does the server authenticate with mojang
* Does the server authenticate with Mojang.
*
* @return online mode
*/
boolean isOnlineMode();
/**
* Whether proxy commands are logged to the proxy log
* Whether proxy commands are logged to the proxy log.
*
* @return log commands
*/
boolean isLogCommands();
/**
* Time in milliseconds to cache server list info from a ping request from
* the proxy to a server.
*
* @return cache time
*/
int getRemotePingCache();
/**
* Returns the player max.
*
* @return player limit
*/
int getPlayerLimit();
/**
* A collection of disabled commands.
*
* @return disabled commands
*/
Collection<String> getDisabledCommands();
/**
* Time in milliseconds before timing out a clients request to connect to a
* server.
*
* @return connect timeout
*/
int getServerConnectTimeout();
/**
* Time in milliseconds before timing out a ping request from the proxy to a
* server when attempting to request server list info.
*
* @return ping timeout
*/
int getRemotePingTimeout();
/**
* The connection throttle delay.
*
* @return throttle
*/
@Deprecated
int getThrottle();
/**
* Whether the proxy will parse IPs with spigot or not
* Whether the proxy will parse IPs with spigot or not.
*
* @return ip forward
*/
@Deprecated
boolean isIpForward();
@ -69,6 +113,7 @@ public interface ProxyConfig
/**
* The encoded favicon.
*
* @return favicon
* @deprecated Use #getFaviconObject instead.
*/
@Deprecated
@ -76,6 +121,8 @@ public interface ProxyConfig
/**
* The favicon used for the server ping list.
*
* @return favicon
*/
Favicon getFaviconObject();
}

View File

@ -3,6 +3,7 @@ package net.md_5.bungee.api;
import com.google.common.base.Preconditions;
import java.io.File;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
@ -55,6 +56,8 @@ public abstract class ProxyServer
/**
* Gets a localized string from the .properties file.
*
* @param name translation name
* @param args translation arguments
* @return the localized string
*/
public abstract String getTranslation(String name, Object... args);
@ -207,6 +210,18 @@ public abstract class ProxyServer
*/
public abstract ServerInfo constructServerInfo(String name, InetSocketAddress address, String motd, boolean restricted);
/**
* Factory method to construct an implementation specific server info
* instance.
*
* @param name name of the server
* @param address connectable Minecraft address + port of the server
* @param motd the motd when used as a forced server
* @param restricted whether the server info restricted property will be set
* @return the constructed instance
*/
public abstract ServerInfo constructServerInfo(String name, SocketAddress address, String motd, boolean restricted);
/**
* Returns the console overlord for this proxy. Being the console, this
* command server cannot have permissions or groups, and will be able to

View File

@ -3,6 +3,7 @@ package net.md_5.bungee.api;
import lombok.Builder;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.event.ServerConnectEvent;
@ -59,12 +60,14 @@ public class ServerConnectRequest
/**
* Timeout in milliseconds for request.
*/
private final int connectTimeout;
@Setter
private int connectTimeout;
/**
* Should the player be attempted to connect to the next server in their
* queue if the initial request fails.
*/
private final boolean retry;
@Setter
private boolean retry;
/**
* Class that sets default properties/adds methods to the lombok builder
@ -73,6 +76,6 @@ public class ServerConnectRequest
public static class Builder
{
private int connectTimeout = 5000; // TODO: Configurable
private int connectTimeout = ProxyServer.getInstance().getConfig().getServerConnectTimeout();
}
}

View File

@ -1,6 +1,7 @@
package net.md_5.bungee.api.config;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
@ -18,7 +19,7 @@ public class ListenerInfo
/**
* Host to bind to.
*/
private final InetSocketAddress host;
private final SocketAddress socketAddress;
/**
* Displayed MOTD.
*/
@ -102,4 +103,16 @@ public class ListenerInfo
{
return ( serverPriority.size() > 1 ) ? serverPriority.get( 1 ) : getDefaultServer();
}
/**
* Gets the bind address as an InetSocketAddress if possible.
*
* @return bind host
* @deprecated BungeeCord can listen via Unix domain sockets
*/
@Deprecated
public InetSocketAddress getHost()
{
return (InetSocketAddress) socketAddress;
}
}

View File

@ -1,6 +1,7 @@
package net.md_5.bungee.api.config;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.CommandSender;
@ -26,9 +27,19 @@ public interface ServerInfo
* class.
*
* @return the IP and port pair for this server
* @deprecated BungeeCord can connect via Unix domain sockets
*/
@Deprecated
InetSocketAddress getAddress();
/**
* Gets the connectable address for this server. Implementations expect this
* to be used as the unique identifier per each instance of this class.
*
* @return the address for this server
*/
SocketAddress getSocketAddress();
/**
* Get the set of all players on this server.
*

View File

@ -1,6 +1,7 @@
package net.md_5.bungee.api.connection;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.protocol.DefinedPacket;
@ -16,9 +17,18 @@ public interface Connection
* Gets the remote address of this connection.
*
* @return the remote address
* @deprecated BungeeCord can accept connections via Unix domain sockets
*/
@Deprecated
InetSocketAddress getAddress();
/**
* Gets the remote address of this connection.
*
* @return the remote address
*/
SocketAddress getSocketAddress();
/**
* Disconnects this end of the connection for the specified reason. If this
* is an {@link ProxiedPlayer} the respective server connection will be

View File

@ -0,0 +1,35 @@
package net.md_5.bungee.api.event;
import java.net.SocketAddress;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.plugin.Cancellable;
import net.md_5.bungee.api.plugin.Event;
/**
* Event called to represent an initial client connection.
* <br>
* Note: This event is called at an early stage of every connection, handling
* should be <b>fast</b>.
*/
@Data
@ToString(callSuper = false)
@EqualsAndHashCode(callSuper = false)
public class ClientConnectEvent extends Event implements Cancellable
{
/**
* Cancelled state.
*/
private boolean cancelled;
/**
* Remote address of connection.
*/
private final SocketAddress socketAddress;
/**
* Listener that accepted the connection.
*/
private final ListenerInfo listener;
}

View File

@ -4,6 +4,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import net.md_5.bungee.api.ServerConnectRequest;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Cancellable;
@ -30,11 +31,18 @@ public class ServerConnectEvent extends Event implements Cancellable
*/
@NonNull
private ServerInfo target;
/**
* Reason for connecting to a new server.
*/
private final Reason reason;
/**
* Request used to connect to given server.
*/
private final ServerConnectRequest request;
/**
* Cancelled state.
*/
private boolean cancelled;
private final Reason reason;
@Deprecated
public ServerConnectEvent(ProxiedPlayer player, ServerInfo target)
@ -42,11 +50,18 @@ public class ServerConnectEvent extends Event implements Cancellable
this( player, target, Reason.UNKNOWN );
}
@Deprecated
public ServerConnectEvent(ProxiedPlayer player, ServerInfo target, Reason reason)
{
this( player, target, reason, null );
}
public ServerConnectEvent(ProxiedPlayer player, ServerInfo target, Reason reason, ServerConnectRequest request)
{
this.player = player;
this.target = target;
this.reason = reason;
this.request = request;
}
public enum Reason

View File

@ -1,5 +1,6 @@
package net.md_5.bungee.api.plugin;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.io.File;
import java.io.InputStream;
@ -27,6 +28,22 @@ public class Plugin
@Getter
private Logger logger;
public Plugin()
{
ClassLoader classLoader = getClass().getClassLoader();
Preconditions.checkState( classLoader instanceof PluginClassloader, "Plugin requires " + PluginClassloader.class.getName() );
( (PluginClassloader) classLoader ).init( this );
}
protected Plugin(ProxyServer proxy, PluginDescription description)
{
ClassLoader classLoader = getClass().getClassLoader();
Preconditions.checkState( !( classLoader instanceof PluginClassloader ), "Cannot use initialization constructor at runtime" );
// init( proxy, description );
}
/**
* Called when the plugin has just been loaded. Most of the proxy will not
* be initialized, so only use it for registering

View File

@ -1,23 +1,33 @@
package net.md_5.bungee.api.plugin;
import com.google.common.base.Preconditions;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import net.md_5.bungee.api.ProxyServer;
public class PluginClassloader extends URLClassLoader
final class PluginClassloader extends URLClassLoader
{
private static final Set<PluginClassloader> allLoaders = new CopyOnWriteArraySet<>();
//
private final ProxyServer proxy;
private final PluginDescription desc;
//
private Plugin plugin;
static
{
ClassLoader.registerAsParallelCapable();
}
public PluginClassloader(URL[] urls)
public PluginClassloader(ProxyServer proxy, PluginDescription desc, URL[] urls)
{
super( urls );
this.proxy = proxy;
this.desc = desc;
allLoaders.add( this );
}
@ -52,4 +62,17 @@ public class PluginClassloader extends URLClassLoader
}
throw new ClassNotFoundException( name );
}
void init(Plugin plugin)
{
Preconditions.checkArgument( plugin != null, "plugin" );
Preconditions.checkArgument( plugin.getClass().getClassLoader() == this, "Plugin has incorrect ClassLoader" );
if ( this.plugin != null )
{
throw new IllegalArgumentException( "Plugin already initialized!" );
}
this.plugin = plugin;
plugin.init( proxy, desc );
}
}

View File

@ -40,7 +40,7 @@ import org.yaml.snakeyaml.introspector.PropertyUtils;
* example event handling and plugin management.
*/
@RequiredArgsConstructor
public class PluginManager
public final class PluginManager
{
/*========================================================================*/
@ -148,6 +148,9 @@ public class PluginManager
* @param sender the sender executing the command
* @param commandLine the complete command line including command name and
* arguments
* @param tabResults list to place tab results into. If this list is non
* null then the command will not be executed and tab results will be
* returned instead.
* @return whether the command was handled
*/
public boolean dispatchCommand(CommandSender sender, String commandLine, List<String> tabResults)
@ -317,14 +320,13 @@ public class PluginManager
{
try
{
URLClassLoader loader = new PluginClassloader( new URL[]
URLClassLoader loader = new PluginClassloader( proxy, plugin, new URL[]
{
plugin.getFile().toURI().toURL()
} );
Class<?> main = loader.loadClass( plugin.getMain() );
Plugin clazz = (Plugin) main.getDeclaredConstructor().newInstance();
clazz.init( proxy, plugin );
plugins.put( plugin.getName(), clazz );
clazz.onLoad();
ProxyServer.getInstance().getLogger().log( Level.INFO, "Loaded plugin {0} version {1} by {2}", new Object[]
@ -400,9 +402,9 @@ public class PluginManager
long elapsed = System.nanoTime() - start;
if ( elapsed > 250000000 )
{
ProxyServer.getInstance().getLogger().log( Level.WARNING, "Event {0} took {1}ns to process!", new Object[]
ProxyServer.getInstance().getLogger().log( Level.WARNING, "Event {0} took {1}ms to process!", new Object[]
{
event, elapsed
event, elapsed / 1000000
} );
}
return event;
@ -440,6 +442,8 @@ public class PluginManager
/**
* Unregister all of a Plugin's listener.
*
* @param plugin target plugin
*/
public void unregisterListeners(Plugin plugin)
{

View File

@ -86,6 +86,7 @@ public interface TaskScheduler
/**
* An executor service which underlies this scheduler.
*
* @param plugin owning plugin
* @return the underlying executor service or compatible wrapper
*/
ExecutorService getExecutorService(Plugin plugin);

View File

@ -1,11 +1,11 @@
package net.md_5.bungee.api;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ServerConnectEvent;
import org.junit.Assert;
import org.junit.Test;
public class ServerConnectRequestTest
@ -19,6 +19,12 @@ public class ServerConnectRequestTest
return null;
}
@Override
public SocketAddress getSocketAddress()
{
return null;
}
@Override
public InetSocketAddress getAddress()
{
@ -72,13 +78,6 @@ public class ServerConnectRequestTest
}
};
@Test
public void testDefaultConnectTimeout()
{
ServerConnectRequest request = ServerConnectRequest.builder().target( DUMMY_INFO ).reason( ServerConnectEvent.Reason.JOIN_PROXY ).build();
Assert.assertEquals( 5000, request.getConnectTimeout() );
}
@Test(expected = NullPointerException.class)
public void testNullTarget()
{

View File

@ -1,6 +1,8 @@
package net.md_5.bungee.util;
import io.netty.channel.unix.DomainSocketAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Arrays;
import java.util.Collection;
import lombok.RequiredArgsConstructor;
@ -44,6 +46,9 @@ public class AddressParseTest
},
{
"[0:0:0:0:0:0:0:1]:1337", "0:0:0:0:0:0:0:1", 1337
},
{
"unix:///var/run/bungee.sock", "/var/run/bungee.sock", -1
}
} );
}
@ -54,8 +59,23 @@ public class AddressParseTest
@Test
public void test()
{
InetSocketAddress parsed = Util.getAddr( line );
Assert.assertEquals( host, parsed.getHostString() );
Assert.assertEquals( port, parsed.getPort() );
SocketAddress parsed = Util.getAddr( line );
if ( parsed instanceof InetSocketAddress )
{
InetSocketAddress tcp = (InetSocketAddress) parsed;
Assert.assertEquals( host, tcp.getHostString() );
Assert.assertEquals( port, tcp.getPort() );
} else if ( parsed instanceof DomainSocketAddress )
{
DomainSocketAddress unix = (DomainSocketAddress) parsed;
Assert.assertEquals( host, unix.path() );
Assert.assertEquals( -1, port );
} else
{
throw new AssertionError( "Unknown socket " + parsed );
}
}
}

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-bootstrap</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Bootstrap</name>
@ -20,6 +20,7 @@
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
<maven.javadoc.skip>true</maven.javadoc.skip>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
<maven.build.timestamp.format>yyyyMMdd</maven.build.timestamp.format>
@ -40,7 +41,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<version>3.2.0</version>
<configuration>
<archive>
<manifestEntries>
@ -54,7 +55,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-chat</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Chat</name>

View File

@ -1,145 +1,180 @@
package net.md_5.bungee.api;
import com.google.common.base.Preconditions;
import java.awt.Color;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
import lombok.Getter;
/**
* Simplistic enumeration of all supported color values for chat.
*/
public enum ChatColor
public final class ChatColor
{
/**
* Represents black.
*/
BLACK( '0', "black" ),
/**
* Represents dark blue.
*/
DARK_BLUE( '1', "dark_blue" ),
/**
* Represents dark green.
*/
DARK_GREEN( '2', "dark_green" ),
/**
* Represents dark blue (aqua).
*/
DARK_AQUA( '3', "dark_aqua" ),
/**
* Represents dark red.
*/
DARK_RED( '4', "dark_red" ),
/**
* Represents dark purple.
*/
DARK_PURPLE( '5', "dark_purple" ),
/**
* Represents gold.
*/
GOLD( '6', "gold" ),
/**
* Represents gray.
*/
GRAY( '7', "gray" ),
/**
* Represents dark gray.
*/
DARK_GRAY( '8', "dark_gray" ),
/**
* Represents blue.
*/
BLUE( '9', "blue" ),
/**
* Represents green.
*/
GREEN( 'a', "green" ),
/**
* Represents aqua.
*/
AQUA( 'b', "aqua" ),
/**
* Represents red.
*/
RED( 'c', "red" ),
/**
* Represents light purple.
*/
LIGHT_PURPLE( 'd', "light_purple" ),
/**
* Represents yellow.
*/
YELLOW( 'e', "yellow" ),
/**
* Represents white.
*/
WHITE( 'f', "white" ),
/**
* Represents magical characters that change around randomly.
*/
MAGIC( 'k', "obfuscated" ),
/**
* Makes the text bold.
*/
BOLD( 'l', "bold" ),
/**
* Makes a line appear through the text.
*/
STRIKETHROUGH( 'm', "strikethrough" ),
/**
* Makes the text appear underlined.
*/
UNDERLINE( 'n', "underline" ),
/**
* Makes the text italic.
*/
ITALIC( 'o', "italic" ),
/**
* Resets all previous chat colors or formats.
*/
RESET( 'r', "reset" );
/**
* The special character which prefixes all chat colour codes. Use this if
* you need to dynamically convert colour codes from your custom format.
*/
public static final char COLOR_CHAR = '\u00A7';
public static final String ALL_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoRr";
public static final String ALL_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoRrXx";
/**
* Pattern to remove all colour codes.
*/
public static final Pattern STRIP_COLOR_PATTERN = Pattern.compile( "(?i)" + String.valueOf( COLOR_CHAR ) + "[0-9A-FK-OR]" );
public static final Pattern STRIP_COLOR_PATTERN = Pattern.compile( "(?i)" + String.valueOf( COLOR_CHAR ) + "[0-9A-FK-ORX]" );
/**
* Colour instances keyed by their active character.
*/
private static final Map<Character, ChatColor> BY_CHAR = new HashMap<Character, ChatColor>();
/**
* The code appended to {@link #COLOR_CHAR} to make usable colour.
* Colour instances keyed by their name.
*/
private final char code;
private static final Map<String, ChatColor> BY_NAME = new HashMap<String, ChatColor>();
/**
* Represents black.
*/
public static final ChatColor BLACK = new ChatColor( '0', "black" );
/**
* Represents dark blue.
*/
public static final ChatColor DARK_BLUE = new ChatColor( '1', "dark_blue" );
/**
* Represents dark green.
*/
public static final ChatColor DARK_GREEN = new ChatColor( '2', "dark_green" );
/**
* Represents dark blue (aqua).
*/
public static final ChatColor DARK_AQUA = new ChatColor( '3', "dark_aqua" );
/**
* Represents dark red.
*/
public static final ChatColor DARK_RED = new ChatColor( '4', "dark_red" );
/**
* Represents dark purple.
*/
public static final ChatColor DARK_PURPLE = new ChatColor( '5', "dark_purple" );
/**
* Represents gold.
*/
public static final ChatColor GOLD = new ChatColor( '6', "gold" );
/**
* Represents gray.
*/
public static final ChatColor GRAY = new ChatColor( '7', "gray" );
/**
* Represents dark gray.
*/
public static final ChatColor DARK_GRAY = new ChatColor( '8', "dark_gray" );
/**
* Represents blue.
*/
public static final ChatColor BLUE = new ChatColor( '9', "blue" );
/**
* Represents green.
*/
public static final ChatColor GREEN = new ChatColor( 'a', "green" );
/**
* Represents aqua.
*/
public static final ChatColor AQUA = new ChatColor( 'b', "aqua" );
/**
* Represents red.
*/
public static final ChatColor RED = new ChatColor( 'c', "red" );
/**
* Represents light purple.
*/
public static final ChatColor LIGHT_PURPLE = new ChatColor( 'd', "light_purple" );
/**
* Represents yellow.
*/
public static final ChatColor YELLOW = new ChatColor( 'e', "yellow" );
/**
* Represents white.
*/
public static final ChatColor WHITE = new ChatColor( 'f', "white" );
/**
* Represents magical characters that change around randomly.
*/
public static final ChatColor MAGIC = new ChatColor( 'k', "obfuscated" );
/**
* Makes the text bold.
*/
public static final ChatColor BOLD = new ChatColor( 'l', "bold" );
/**
* Makes a line appear through the text.
*/
public static final ChatColor STRIKETHROUGH = new ChatColor( 'm', "strikethrough" );
/**
* Makes the text appear underlined.
*/
public static final ChatColor UNDERLINE = new ChatColor( 'n', "underline" );
/**
* Makes the text italic.
*/
public static final ChatColor ITALIC = new ChatColor( 'o', "italic" );
/**
* Resets all previous chat colors or formats.
*/
public static final ChatColor RESET = new ChatColor( 'r', "reset" );
/**
* Count used for populating legacy ordinal.
*/
private static int count = 0;
/**
* This colour's colour char prefixed by the {@link #COLOR_CHAR}.
*/
private final String toString;
@Getter
private final String name;
static
{
for ( ChatColor colour : values() )
{
BY_CHAR.put( colour.code, colour );
}
}
private final int ordinal;
private ChatColor(char code, String name)
{
this.code = code;
this.name = name;
this.toString = new String( new char[]
{
COLOR_CHAR, code
} );
this.ordinal = count++;
BY_CHAR.put( code, this );
BY_NAME.put( name.toUpperCase( Locale.ROOT ), this );
}
private ChatColor(String name, String toString)
{
this.name = name;
this.toString = toString;
this.ordinal = -1;
}
@Override
public int hashCode()
{
int hash = 7;
hash = 53 * hash + Objects.hashCode( this.toString );
return hash;
}
@Override
public boolean equals(Object obj)
{
if ( this == obj )
{
return true;
}
if ( obj == null || getClass() != obj.getClass() )
{
return false;
}
final ChatColor other = (ChatColor) obj;
return Objects.equals( this.toString, other.toString );
}
@Override
@ -188,4 +223,95 @@ public enum ChatColor
{
return BY_CHAR.get( code );
}
public static ChatColor of(Color color)
{
return of( "#" + Integer.toHexString( color.getRGB() ).substring( 2 ) );
}
public static ChatColor of(String string)
{
Preconditions.checkArgument( string != null, "string cannot be null" );
if ( string.startsWith( "#" ) && string.length() == 7 )
{
try
{
Integer.parseInt( string.substring( 1 ), 16 );
} catch ( NumberFormatException ex )
{
throw new IllegalArgumentException( "Illegal hex string " + string );
}
StringBuilder magic = new StringBuilder( COLOR_CHAR + "x" );
for ( char c : string.substring( 1 ).toCharArray() )
{
magic.append( COLOR_CHAR ).append( c );
}
return new ChatColor( string, magic.toString() );
}
ChatColor defined = BY_NAME.get( string.toUpperCase( Locale.ROOT ) );
if ( defined != null )
{
return defined;
}
throw new IllegalArgumentException( "Could not parse ChatColor " + string );
}
/**
* See {@link Enum#valueOf(java.lang.Class, java.lang.String)}.
*
* @param name color name
* @return ChatColor
* @deprecated holdover from when this class was an enum
*/
@Deprecated
public static ChatColor valueOf(String name)
{
Preconditions.checkNotNull( name, "Name is null" );
ChatColor defined = BY_NAME.get( name );
Preconditions.checkArgument( defined != null, "No enum constant " + ChatColor.class.getName() + "." + name );
return defined;
}
/**
* Get an array of all defined colors and formats.
*
* @return copied array of all colors and formats
* @deprecated holdover from when this class was an enum
*/
@Deprecated
public static ChatColor[] values()
{
return BY_CHAR.values().toArray( new ChatColor[ BY_CHAR.values().size() ] );
}
/**
* See {@link Enum#name()}.
*
* @return constant name
* @deprecated holdover from when this class was an enum
*/
@Deprecated
public String name()
{
return getName().toUpperCase( Locale.ROOT );
}
/**
* See {@link Enum#ordinal()}.
*
* @return ordinal
* @deprecated holdover from when this class was an enum
*/
@Deprecated
public int ordinal()
{
Preconditions.checkArgument( ordinal >= 0, "Cannot get ordinal of hex color" );
return ordinal;
}
}

View File

@ -5,7 +5,6 @@ import java.util.List;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import net.md_5.bungee.api.ChatColor;
@ -13,8 +12,7 @@ import net.md_5.bungee.api.chat.ComponentBuilder.FormatRetention;
@Setter
@ToString(exclude = "parent")
@EqualsAndHashCode
@NoArgsConstructor
@EqualsAndHashCode(exclude = "parent")
public abstract class BaseComponent
{
@ -25,6 +23,10 @@ public abstract class BaseComponent
* The color of this component and any child components (unless overridden)
*/
private ChatColor color;
/**
* The font of this component and any child components (unless overridden)
*/
private String font;
/**
* Whether this component and any child components (unless overridden) is
* bold
@ -76,6 +78,16 @@ public abstract class BaseComponent
@Getter
private HoverEvent hoverEvent;
/**
* Default constructor.
*
* @deprecated for use by internal classes only, will be removed.
*/
@Deprecated
public BaseComponent()
{
}
BaseComponent(BaseComponent old)
{
copyFormatting( old, FormatRetention.ALL, true );
@ -139,6 +151,10 @@ public abstract class BaseComponent
{
setColor( component.getColorRaw() );
}
if ( replace || font == null )
{
setFont( component.getFontRaw() );
}
if ( replace || bold == null )
{
setBold( component.isBoldRaw() );
@ -275,6 +291,36 @@ public abstract class BaseComponent
return color;
}
/**
* Returns the font of this component. This uses the parent's font if this
* component doesn't have one.
*
* @return the font of this component, or null if default font
*/
public String getFont()
{
if ( color == null )
{
if ( parent == null )
{
return null;
}
return parent.getFont();
}
return font;
}
/**
* Returns the font of this component without checking the parents font. May
* return null
*
* @return the font of this component
*/
public String getFontRaw()
{
return font;
}
/**
* 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
@ -453,7 +499,7 @@ public abstract class BaseComponent
*/
public boolean hasFormatting()
{
return color != null || bold != null
return color != null || font != null || bold != null
|| italic != null || underlined != null
|| strikethrough != null || obfuscated != null
|| insertion != null || hoverEvent != null || clickEvent != null;
@ -505,4 +551,29 @@ public abstract class BaseComponent
}
}
}
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 );
}
if ( isObfuscated() )
{
builder.append( ChatColor.MAGIC );
}
}
}

View File

@ -13,11 +13,11 @@ public final class ClickEvent
{
/**
* The type of action to perform on click
* The type of action to perform on click.
*/
private final Action action;
/**
* Depends on action
* Depends on the action.
*
* @see Action
*/
@ -28,29 +28,35 @@ public final class ClickEvent
/**
* Open a url at the path given by
* {@link net.md_5.bungee.api.chat.ClickEvent#value}
* {@link net.md_5.bungee.api.chat.ClickEvent#value}.
*/
OPEN_URL,
/**
* Open a file at the path given by
* {@link net.md_5.bungee.api.chat.ClickEvent#value}
* {@link net.md_5.bungee.api.chat.ClickEvent#value}.
*/
OPEN_FILE,
/**
* Run the command given by
* {@link net.md_5.bungee.api.chat.ClickEvent#value}
* {@link net.md_5.bungee.api.chat.ClickEvent#value}.
*/
RUN_COMMAND,
/**
* Inserts the string given by
* {@link net.md_5.bungee.api.chat.ClickEvent#value} into the players
* text box
* {@link net.md_5.bungee.api.chat.ClickEvent#value} into the player's
* text box.
*/
SUGGEST_COMMAND,
/**
* Change to the page number given by
* {@link net.md_5.bungee.api.chat.ClickEvent#value} in a book
* {@link net.md_5.bungee.api.chat.ClickEvent#value} in a book.
*/
CHANGE_PAGE
CHANGE_PAGE,
/**
* Copy the string given by
* {@link net.md_5.bungee.api.chat.ClickEvent#value} into the player's
* clipboard.
*/
COPY_TO_CLIPBOARD
}
}

View File

@ -3,6 +3,8 @@ package net.md_5.bungee.api.chat;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.NoArgsConstructor;
import net.md_5.bungee.api.ChatColor;
/**
@ -23,11 +25,29 @@ import net.md_5.bungee.api.ChatColor;
* part's formatting
* </p>
*/
@NoArgsConstructor
public final class ComponentBuilder
{
private BaseComponent current;
/**
* The position for the current part to modify. Modified cursors will
* automatically reset to the last part after appending new components.
* Default value at -1 to assert that the builder has no parts.
*/
@Getter
private int cursor = -1;
@Getter
private final List<BaseComponent> parts = new ArrayList<BaseComponent>();
private BaseComponent dummy;
private ComponentBuilder(BaseComponent[] parts)
{
for ( BaseComponent baseComponent : parts )
{
this.parts.add( baseComponent.duplicate() );
}
resetCursor();
}
/**
* Creates a ComponentBuilder from the other given ComponentBuilder to clone
@ -37,11 +57,7 @@ public final class ComponentBuilder
*/
public ComponentBuilder(ComponentBuilder original)
{
current = original.current.duplicate();
for ( BaseComponent baseComponent : original.parts )
{
parts.add( baseComponent.duplicate() );
}
this( original.parts.toArray( new BaseComponent[ original.parts.size() ] ) );
}
/**
@ -51,7 +67,7 @@ public final class ComponentBuilder
*/
public ComponentBuilder(String text)
{
current = new TextComponent( text );
this( new TextComponent( text ) );
}
/**
@ -61,7 +77,58 @@ public final class ComponentBuilder
*/
public ComponentBuilder(BaseComponent component)
{
current = component.duplicate();
this( new BaseComponent[]
{
component
} );
}
private BaseComponent getDummy()
{
if ( dummy == null )
{
dummy = new BaseComponent()
{
@Override
public BaseComponent duplicate()
{
return this;
}
};
}
return dummy;
}
/**
* Resets the cursor to index of the last element.
*
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder resetCursor()
{
cursor = parts.size() - 1;
return this;
}
/**
* Sets the position of the current component to be modified
*
* @param pos the cursor position synonymous to an element position for a
* list
* @return this ComponentBuilder for chaining
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()})
*/
public ComponentBuilder setCursor(int pos) throws IndexOutOfBoundsException
{
if ( ( this.cursor != pos ) && ( pos < 0 || pos >= parts.size() ) )
{
throw new IndexOutOfBoundsException( "Cursor out of bounds (expected between 0 + " + ( parts.size() - 1 ) + ")" );
}
this.cursor = pos;
return this;
}
/**
@ -88,11 +155,18 @@ public final class ComponentBuilder
*/
public ComponentBuilder append(BaseComponent component, FormatRetention retention)
{
parts.add( current );
BaseComponent previous = current;
current = component.duplicate();
current.copyFormatting( previous, retention, false );
BaseComponent previous = ( parts.isEmpty() ) ? null : parts.get( parts.size() - 1 );
if ( previous == null )
{
previous = dummy;
dummy = null;
}
if ( previous != null )
{
component.copyFormatting( previous, retention, false );
}
parts.add( component );
resetCursor();
return this;
}
@ -122,13 +196,9 @@ public final class ComponentBuilder
{
Preconditions.checkArgument( components.length != 0, "No components to append" );
BaseComponent previous = current;
for ( BaseComponent component : components )
{
parts.add( current );
current = component.duplicate();
current.copyFormatting( previous, retention, false );
append( component, retention );
}
return this;
@ -170,13 +240,7 @@ public final class ComponentBuilder
*/
public ComponentBuilder append(String text, FormatRetention retention)
{
parts.add( current );
BaseComponent old = current;
current = new TextComponent( text );
current.copyFormatting( old, retention, false );
return this;
return append( new TextComponent( text ), retention );
}
/**
@ -210,6 +274,44 @@ public final class ComponentBuilder
return joiner.join( this, retention );
}
/**
* Remove the component part at the position of given index.
*
* @param pos the index to remove at
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()})
*/
public void removeComponent(int pos) throws IndexOutOfBoundsException
{
if ( parts.remove( pos ) != null )
{
resetCursor();
}
}
/**
* Gets the component part at the position of given index.
*
* @param pos the index to find
* @return the component
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= size()})
*/
public BaseComponent getComponent(int pos) throws IndexOutOfBoundsException
{
return parts.get( pos );
}
/**
* Gets the component at the position of the cursor.
*
* @return the active component or null if builder is empty
*/
public BaseComponent getCurrentComponent()
{
return ( cursor == -1 ) ? getDummy() : parts.get( cursor );
}
/**
* Sets the color of the current part.
*
@ -218,7 +320,7 @@ public final class ComponentBuilder
*/
public ComponentBuilder color(ChatColor color)
{
current.setColor( color );
getCurrentComponent().setColor( color );
return this;
}
@ -230,7 +332,7 @@ public final class ComponentBuilder
*/
public ComponentBuilder bold(boolean bold)
{
current.setBold( bold );
getCurrentComponent().setBold( bold );
return this;
}
@ -242,7 +344,7 @@ public final class ComponentBuilder
*/
public ComponentBuilder italic(boolean italic)
{
current.setItalic( italic );
getCurrentComponent().setItalic( italic );
return this;
}
@ -254,7 +356,7 @@ public final class ComponentBuilder
*/
public ComponentBuilder underlined(boolean underlined)
{
current.setUnderlined( underlined );
getCurrentComponent().setUnderlined( underlined );
return this;
}
@ -266,7 +368,7 @@ public final class ComponentBuilder
*/
public ComponentBuilder strikethrough(boolean strikethrough)
{
current.setStrikethrough( strikethrough );
getCurrentComponent().setStrikethrough( strikethrough );
return this;
}
@ -278,7 +380,7 @@ public final class ComponentBuilder
*/
public ComponentBuilder obfuscated(boolean obfuscated)
{
current.setObfuscated( obfuscated );
getCurrentComponent().setObfuscated( obfuscated );
return this;
}
@ -290,7 +392,7 @@ public final class ComponentBuilder
*/
public ComponentBuilder insertion(String insertion)
{
current.setInsertion( insertion );
getCurrentComponent().setInsertion( insertion );
return this;
}
@ -302,7 +404,7 @@ public final class ComponentBuilder
*/
public ComponentBuilder event(ClickEvent clickEvent)
{
current.setClickEvent( clickEvent );
getCurrentComponent().setClickEvent( clickEvent );
return this;
}
@ -314,7 +416,7 @@ public final class ComponentBuilder
*/
public ComponentBuilder event(HoverEvent hoverEvent)
{
current.setHoverEvent( hoverEvent );
getCurrentComponent().setHoverEvent( hoverEvent );
return this;
}
@ -336,24 +438,28 @@ public final class ComponentBuilder
*/
public ComponentBuilder retain(FormatRetention retention)
{
current.retain( retention );
getCurrentComponent().retain( retention );
return this;
}
/**
* Returns the components needed to display the message created by this
* builder.
* builder.git
*
* @return the created components
*/
public BaseComponent[] create()
{
BaseComponent[] result = parts.toArray( new BaseComponent[ parts.size() + 1 ] );
result[parts.size()] = current;
return result;
BaseComponent[] cloned = new BaseComponent[ parts.size() ];
int i = 0;
for ( BaseComponent part : parts )
{
cloned[i++] = part.duplicate();
}
return cloned;
}
public static enum FormatRetention
public enum FormatRetention
{
/**

View File

@ -5,7 +5,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import net.md_5.bungee.api.ChatColor;
@Getter
@Setter
@ -45,7 +44,7 @@ public final class KeybindComponent extends BaseComponent
}
@Override
public BaseComponent duplicate()
public KeybindComponent duplicate()
{
return new KeybindComponent( this );
}
@ -60,29 +59,8 @@ public final class KeybindComponent extends BaseComponent
@Override
protected void toLegacyText(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 );
}
if ( isObfuscated() )
{
builder.append( ChatColor.MAGIC );
}
addFormat( builder );
builder.append( getKeybind() );
super.toLegacyText( builder );
}
}

View File

@ -84,9 +84,17 @@ public final class ScoreComponent extends BaseComponent
return new ScoreComponent( this );
}
@Override
protected void toPlainText(StringBuilder builder)
{
builder.append( this.value );
super.toPlainText( builder );
}
@Override
protected void toLegacyText(StringBuilder builder)
{
addFormat( builder );
builder.append( this.value );
super.toLegacyText( builder );
}

View File

@ -50,9 +50,17 @@ public final class SelectorComponent extends BaseComponent
return new SelectorComponent( this );
}
@Override
protected void toPlainText(StringBuilder builder)
{
builder.append( this.selector );
super.toPlainText( builder );
}
@Override
protected void toLegacyText(StringBuilder builder)
{
addFormat( builder );
builder.append( this.selector );
super.toLegacyText( builder );
}

View File

@ -63,7 +63,27 @@ public final class TextComponent extends BaseComponent
{
c += 32;
}
ChatColor format = ChatColor.getByChar( c );
ChatColor format;
if ( c == 'x' && i + 12 < message.length() )
{
StringBuilder hex = new StringBuilder( "#" );
for ( int j = 0; j < 6; j++ )
{
hex.append( message.charAt( i + 2 + ( j * 2 ) ) );
}
try
{
format = ChatColor.of( hex.toString() );
} catch ( IllegalArgumentException ex )
{
format = null;
}
i += 12;
} else
{
format = ChatColor.getByChar( c );
}
if ( format == null )
{
continue;
@ -76,29 +96,30 @@ public final class TextComponent extends BaseComponent
builder = new StringBuilder();
components.add( old );
}
switch ( format )
if ( format == ChatColor.BOLD )
{
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 = defaultColor;
default:
component = new TextComponent();
component.setColor( format );
break;
component.setBold( true );
} else if ( format == ChatColor.ITALIC )
{
component.setItalic( true );
} else if ( format == ChatColor.UNDERLINE )
{
component.setUnderlined( true );
} else if ( format == ChatColor.STRIKETHROUGH )
{
component.setStrikethrough( true );
} else if ( format == ChatColor.MAGIC )
{
component.setObfuscated( true );
} else if ( format == ChatColor.RESET )
{
format = defaultColor;
component = new TextComponent();
component.setColor( format );
} else
{
component = new TextComponent();
component.setColor( format );
}
continue;
}
@ -172,7 +193,11 @@ public final class TextComponent extends BaseComponent
*/
public TextComponent(BaseComponent... extras)
{
setText( "" );
this();
if ( extras.length == 0 )
{
return;
}
setExtra( new ArrayList<BaseComponent>( Arrays.asList( extras ) ) );
}
@ -182,7 +207,7 @@ public final class TextComponent extends BaseComponent
* @return the duplicate of this TextComponent.
*/
@Override
public BaseComponent duplicate()
public TextComponent duplicate()
{
return new TextComponent( this );
}
@ -197,27 +222,7 @@ public final class TextComponent extends BaseComponent
@Override
protected void toLegacyText(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 );
}
if ( isObfuscated() )
{
builder.append( ChatColor.MAGIC );
}
addFormat( builder );
builder.append( text );
super.toLegacyText( builder );
}

View File

@ -9,7 +9,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.chat.TranslationRegistry;
@Getter
@ -56,12 +55,12 @@ public final class TranslatableComponent extends BaseComponent
/**
* Creates a translatable component with the passed substitutions
*
* @see #translate
* @see #setWith(java.util.List)
* @param translate the translation key
* @param with the {@link java.lang.String}s and
* {@link net.md_5.bungee.api.chat.BaseComponent}s to use into the
* translation
* @see #translate
* @see #setWith(java.util.List)
*/
public TranslatableComponent(String translate, Object... with)
{
@ -89,7 +88,7 @@ public final class TranslatableComponent extends BaseComponent
* @return the duplicate of this TranslatableComponent.
*/
@Override
public BaseComponent duplicate()
public TranslatableComponent duplicate()
{
return new TranslatableComponent( this );
}
@ -139,43 +138,18 @@ public final class TranslatableComponent extends BaseComponent
@Override
protected void toPlainText(StringBuilder builder)
{
String trans = TranslationRegistry.INSTANCE.translate( translate );
Matcher matcher = format.matcher( trans );
int position = 0;
int i = 0;
while ( matcher.find( position ) )
{
int pos = matcher.start();
if ( pos != position )
{
builder.append( trans.substring( position, pos ) );
}
position = matcher.end();
String formatCode = matcher.group( 2 );
switch ( formatCode.charAt( 0 ) )
{
case 's':
case 'd':
String withIndex = matcher.group( 1 );
with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ ).toPlainText( builder );
break;
case '%':
builder.append( '%' );
break;
}
}
if ( trans.length() != position )
{
builder.append( trans.substring( position, trans.length() ) );
}
convert( builder, false );
super.toPlainText( builder );
}
@Override
protected void toLegacyText(StringBuilder builder)
{
convert( builder, true );
super.toLegacyText( builder );
}
private void convert(StringBuilder builder, boolean applyFormat)
{
String trans = TranslationRegistry.INSTANCE.translate( translate );
@ -187,7 +161,10 @@ public final class TranslatableComponent extends BaseComponent
int pos = matcher.start();
if ( pos != position )
{
addFormat( builder );
if ( applyFormat )
{
addFormat( builder );
}
builder.append( trans.substring( position, pos ) );
}
position = matcher.end();
@ -198,44 +175,32 @@ public final class TranslatableComponent extends BaseComponent
case 's':
case 'd':
String withIndex = matcher.group( 1 );
with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ ).toLegacyText( builder );
BaseComponent withComponent = with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ );
if ( applyFormat )
{
withComponent.toLegacyText( builder );
} else
{
withComponent.toPlainText( builder );
}
break;
case '%':
addFormat( builder );
if ( applyFormat )
{
addFormat( builder );
}
builder.append( '%' );
break;
}
}
if ( trans.length() != position )
{
addFormat( builder );
if ( applyFormat )
{
addFormat( builder );
}
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 );
}
if ( isObfuscated() )
{
builder.append( ChatColor.MAGIC );
}
}
}

View File

@ -20,7 +20,11 @@ public class BaseComponentSerializer
{
if ( object.has( "color" ) )
{
component.setColor( ChatColor.valueOf( object.get( "color" ).getAsString().toUpperCase( Locale.ROOT ) ) );
component.setColor( ChatColor.of( object.get( "color" ).getAsString() ) );
}
if ( object.has( "font" ) )
{
component.setFont( object.get( "font" ).getAsString() );
}
if ( object.has( "bold" ) )
{
@ -93,6 +97,10 @@ public class BaseComponentSerializer
{
object.addProperty( "color", component.getColorRaw().getName() );
}
if ( component.getFontRaw() != null )
{
object.addProperty( "font", component.getFontRaw() );
}
if ( component.isBoldRaw() != null )
{
object.addProperty( "bold", component.isBoldRaw() );

View File

@ -24,7 +24,7 @@ public class TranslatableComponentSerializer extends BaseComponentSerializer imp
component.setTranslate( object.get( "translate" ).getAsString() );
if ( object.has( "with" ) )
{
component.setWith( Arrays.asList( (BaseComponent[]) context.deserialize( object.get( "with" ), BaseComponent[].class ) ) );
component.setWith( Arrays.asList( context.<BaseComponent[]>deserialize( object.get( "with" ), BaseComponent[].class ) ) );
}
return component;
}

View File

@ -1,5 +1,6 @@
package net.md_5.bungee.api.chat;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -11,16 +12,112 @@ import org.junit.Test;
public class ComponentsTest
{
@Test
public void testEmptyComponentBuilder()
{
ComponentBuilder builder = new ComponentBuilder();
BaseComponent[] parts = builder.create();
Assert.assertEquals( parts.length, 0 );
for ( int i = 0; i < 3; i++ )
{
builder.append( "part:" + i );
parts = builder.create();
Assert.assertEquals( parts.length, i + 1 );
}
}
@Test
public void testDummyRetaining()
{
ComponentBuilder builder = new ComponentBuilder();
Assert.assertNotNull( builder.getCurrentComponent() );
builder.color( ChatColor.GREEN );
builder.append( "test ", ComponentBuilder.FormatRetention.ALL );
Assert.assertEquals( builder.getCurrentComponent().getColor(), ChatColor.GREEN );
}
@Test(expected = IndexOutOfBoundsException.class)
public void testComponentGettingExceptions()
{
ComponentBuilder builder = new ComponentBuilder();
builder.getComponent( -1 );
builder.getComponent( 0 );
builder.getComponent( 1 );
BaseComponent component = new TextComponent( "Hello" );
builder.append( component );
Assert.assertEquals( builder.getComponent( 0 ), component );
builder.getComponent( 1 );
}
@Test
public void testComponentParting()
{
ComponentBuilder builder = new ComponentBuilder();
TextComponent apple = new TextComponent( "apple" );
builder.append( apple );
Assert.assertEquals( builder.getCurrentComponent(), apple );
Assert.assertEquals( builder.getComponent( 0 ), apple );
TextComponent mango = new TextComponent( "mango" );
TextComponent orange = new TextComponent( "orange" );
builder.append( mango );
builder.append( orange );
builder.removeComponent( 1 ); // Removing mango
Assert.assertEquals( builder.getComponent( 0 ), apple );
Assert.assertEquals( builder.getComponent( 1 ), orange );
}
@Test
public void testToLegacyFromLegacy()
{
String text = "§a§lHello §f§kworld§7!";
Assert.assertEquals( text, TextComponent.toLegacyText( TextComponent.fromLegacyText( text ) ) );
}
@Test(expected = IndexOutOfBoundsException.class)
public void testComponentBuilderCursorInvalidPos()
{
ComponentBuilder builder = new ComponentBuilder();
builder.append( new TextComponent( "Apple, " ) );
builder.append( new TextComponent( "Orange, " ) );
builder.setCursor( -1 );
builder.setCursor( 2 );
}
@Test
public void testComponentBuilderCursor()
{
TextComponent t1, t2, t3;
ComponentBuilder builder = new ComponentBuilder();
Assert.assertEquals( builder.getCursor(), -1 );
builder.append( t1 = new TextComponent( "Apple, " ) );
Assert.assertEquals( builder.getCursor(), 0 );
builder.append( t2 = new TextComponent( "Orange, " ) );
builder.append( t3 = new TextComponent( "Mango, " ) );
Assert.assertEquals( builder.getCursor(), 2 );
builder.setCursor( 0 );
Assert.assertEquals( builder.getCurrentComponent(), t1 );
// Test that appending new components updates the position to the new list size
// after having previously set it to 0 (first component)
builder.append( new TextComponent( "and Grapefruit" ) );
Assert.assertEquals( builder.getCursor(), 3 );
builder.setCursor( 0 );
builder.resetCursor();
Assert.assertEquals( builder.getCursor(), 3 );
}
@Test
public void testLegacyComponentBuilderAppend()
{
String text = "§a§lHello §r§kworld§7!";
BaseComponent[] components = TextComponent.fromLegacyText( text );
BaseComponent[] builderComponents = new ComponentBuilder( "" ).append( components ).create();
BaseComponent[] builderComponents = new ComponentBuilder().append( components ).create();
List<BaseComponent> list = new ArrayList<BaseComponent>( Arrays.asList( builderComponents ) );
// Remove the first element (empty text component). This needs to be done because toLegacyText always
// appends &f regardless if the color is non null or not and would otherwise mess with our unit test.
list.remove( 0 );
Assert.assertEquals(
TextComponent.toLegacyText( components ),
TextComponent.toLegacyText( list.toArray( new BaseComponent[ list.size() ] ) )
@ -28,7 +125,7 @@ public class ComponentsTest
}
@Test
public void testComponentFormatRetention()
public void testFormatRetentionCopyFormatting()
{
TextComponent first = new TextComponent( "Hello" );
first.setBold( true );
@ -47,7 +144,7 @@ public class ComponentsTest
@Test
public void testBuilderClone()
{
ComponentBuilder builder = new ComponentBuilder( "Hel" ).color( ChatColor.RED ).append( "lo" ).color( ChatColor.DARK_RED );
ComponentBuilder builder = new ComponentBuilder( "Hello " ).color( ChatColor.RED ).append( "world" ).color( ChatColor.DARK_RED );
ComponentBuilder cloned = new ComponentBuilder( builder );
Assert.assertEquals( TextComponent.toLegacyText( builder.create() ), TextComponent.toLegacyText( cloned.create() ) );
@ -313,6 +410,41 @@ public class ComponentsTest
Assert.assertEquals( text, roundtripLegacyText );
}
@Test
public void testEquals()
{
TextComponent first = new TextComponent( "Hello, " );
first.addExtra( new TextComponent( "World!" ) );
TextComponent second = new TextComponent( "Hello, " );
second.addExtra( new TextComponent( "World!" ) );
Assert.assertEquals( first, second );
}
@Test
public void testNotEquals()
{
TextComponent first = new TextComponent( "Hello, " );
first.addExtra( new TextComponent( "World." ) );
TextComponent second = new TextComponent( "Hello, " );
second.addExtra( new TextComponent( "World!" ) );
Assert.assertNotEquals( first, second );
}
@Test
public void testLegacyHack()
{
BaseComponent[] hexColored = new ComponentBuilder().color( ChatColor.of( Color.GRAY ) ).append( "Test" ).create();
String legacy = TextComponent.toLegacyText( hexColored );
BaseComponent[] reColored = TextComponent.fromLegacyText( legacy );
Assert.assertArrayEquals( hexColored, reColored );
}
private String fromAndToLegacyText(String legacyText)
{
return BaseComponent.toLegacyText( TextComponent.fromLegacyText( legacyText ) );

View File

@ -1,5 +1,6 @@
package net.md_5.bungee.api.chat;
import net.md_5.bungee.chat.ComponentSerializer;
import org.junit.Assert;
import org.junit.Test;
@ -13,4 +14,15 @@ public class TranslatableComponentTest
Assert.assertEquals( "Test string with 2 placeholders: aoeu", testComponent.toPlainText() );
Assert.assertEquals( "§fTest string with §f2§f placeholders: §faoeu", testComponent.toLegacyText() );
}
@Test
public void testJsonSerialisation()
{
TranslatableComponent testComponent = new TranslatableComponent( "Test string with %s placeholder", "a" );
String jsonString = ComponentSerializer.toString( testComponent );
BaseComponent[] baseComponents = ComponentSerializer.parse( jsonString );
Assert.assertEquals( "Test string with a placeholder", TextComponent.toPlainText( baseComponents ) );
Assert.assertEquals( "§fTest string with §fa§f placeholder", TextComponent.toLegacyText( baseComponents ) );
}
}

View File

@ -14,13 +14,23 @@
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
<module name="RegexpSingleline">
<property name="format" value="\s+$"/>
<property name="minimum" value="0"/>
<property name="maximum" value="0"/>
<property name="message" value="Line has trailing spaces."/>
<property name="format" value="\s+$"/>
<property name="minimum" value="0"/>
<property name="maximum" value="0"/>
<property name="message" value="Line has trailing spaces."/>
</module>
<module name="TreeWalker">
<!-- See https://checkstyle.org/config_javadoc.html -->
<module name="AtclauseOrder"/>
<module name="InvalidJavadocPosition"/>
<module name="JavadocBlockTagLocation"/>
<module name="JavadocContentLocationCheck"/>
<module name="JavadocMethod"/>
<module name="JavadocType"/>
<module name="MissingJavadocPackage"/>
<module name="NonEmptyAtclauseDescription"/>
<!-- See http://checkstyle.sourceforge.net/config_filters.html -->
<module name="SuppressionCommentFilter"/>
@ -35,14 +45,43 @@
<module name="RedundantImport"/>
<module name="UnusedImports"/>
<!-- See https://checkstyle.org/config_whitespace.html -->
<module name="GenericWhitespace"/>
<module name="MethodParamPad"/>
<module name="NoLineWrap"/>
<module name="NoWhitespaceAfter"/>
<module name="NoWhitespaceBefore"/>
<module name="OperatorWrap"/>
<module name="ParenPad">
<property name="option" value="nospace"/>
<property name="tokens" value="ANNOTATION, CTOR_DEF, METHOD_DEF"/>
</module>
<module name="ParenPad">
<property name="option" value="space"/>
<property name="tokens" value="ANNOTATION_FIELD_DEF, CTOR_CALL, DOT, ENUM_CONSTANT_DEF, EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW, LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, LAMBDA"/>
</module>
<module name="SingleSpaceSeparator"/>
<module name="TypecastParenPad"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround"/>
<!-- See http://checkstyle.sourceforge.net/config_modifiers.html -->
<module name="ModifierOrder"/>
<!-- See https://checkstyle.org/config_blocks.html -->
<module name="AvoidNestedBlocks"/>
<module name="LeftCurly">
<property name="option" value="nl"/>
</module>
<module name="RightCurly"/>
<!-- See http://checkstyle.sourceforge.net/config_design.html -->
<module name="FinalClass"/>
<!-- See http://checkstyle.sourceforge.net/config_misc.html -->
<module name="ArrayTypeStyle"/>
<module name="CommentsIndentation"/>
<module name="Indentation"/>
<module name="UpperEll"/>
</module>
</module>

View File

@ -6,24 +6,32 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-config</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Config</name>
<description>Generic java configuration API intended for use with BungeeCord</description>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.0</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.23</version>
<version>1.25</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -15,7 +15,21 @@ public abstract class ConfigurationProvider
static
{
providers.put( YamlConfiguration.class, new YamlConfiguration() );
try
{
providers.put( YamlConfiguration.class, new YamlConfiguration() );
} catch ( NoClassDefFoundError ex )
{
// Ignore, no SnakeYAML
}
try
{
providers.put( JsonConfiguration.class, new JsonConfiguration() );
} catch ( NoClassDefFoundError ex )
{
// Ignore, no Gson
}
}
public static ConfigurationProvider getProvider(Class<? extends ConfigurationProvider> provider)

View File

@ -0,0 +1,114 @@
package net.md_5.bungee.config;
import com.google.common.base.Charsets;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PACKAGE)
public class JsonConfiguration extends ConfigurationProvider
{
private final Gson json = new GsonBuilder().serializeNulls().setPrettyPrinting().registerTypeAdapter( Configuration.class, new JsonSerializer<Configuration>()
{
@Override
public JsonElement serialize(Configuration src, Type typeOfSrc, JsonSerializationContext context)
{
return context.serialize( ( (Configuration) src ).self );
}
} ).create();
@Override
public void save(Configuration config, File file) throws IOException
{
try ( Writer writer = new OutputStreamWriter( new FileOutputStream( file ), Charsets.UTF_8 ) )
{
save( config, writer );
}
}
@Override
public void save(Configuration config, Writer writer)
{
json.toJson( config.self, writer );
}
@Override
public Configuration load(File file) throws IOException
{
return load( file, null );
}
@Override
public Configuration load(File file, Configuration defaults) throws IOException
{
try ( FileInputStream is = new FileInputStream( file ) )
{
return load( is, defaults );
}
}
@Override
public Configuration load(Reader reader)
{
return load( reader, null );
}
@Override
@SuppressWarnings("unchecked")
public Configuration load(Reader reader, Configuration defaults)
{
Map<String, Object> map = json.fromJson( reader, LinkedHashMap.class );
if ( map == null )
{
map = new LinkedHashMap<>();
}
return new Configuration( map, defaults );
}
@Override
public Configuration load(InputStream is)
{
return load( is, null );
}
@Override
public Configuration load(InputStream is, Configuration defaults)
{
return load( new InputStreamReader( is, Charsets.UTF_8 ), defaults );
}
@Override
public Configuration load(String string)
{
return load( string, null );
}
@Override
@SuppressWarnings("unchecked")
public Configuration load(String string, Configuration defaults)
{
Map<String, Object> map = json.fromJson( string, LinkedHashMap.class );
if ( map == null )
{
map = new LinkedHashMap<>();
}
return new Configuration( map, defaults );
}
}

View File

@ -0,0 +1,229 @@
package net.md_5.bungee.config;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RequiredArgsConstructor
@RunWith(Parameterized.class)
public class CompoundConfigurationTest
{
@Parameters(name = "{0}")
public static Iterable<Object[]> data()
{
// CHECKSTYLE:OFF
return Arrays.asList( new Object[][]
{
{
// provider
YamlConfiguration.class,
// testDocument
""
+ "receipt: Oz-Ware Purchase Invoice\n"
+ "date: 2012-08-06\n"
+ "customer:\n"
+ " given: Dorothy\n"
+ " family: Gale\n"
+ "\n"
+ "items:\n"
+ " - part_no: A4786\n"
+ " descrip: Water Bucket (Filled)\n"
+ " price: 1.47\n"
+ " quantity: 4\n"
+ "\n"
+ " - part_no: E1628\n"
+ " descrip: High Heeled \"Ruby\" Slippers\n"
+ " size: 8\n"
+ " price: 100.27\n"
+ " quantity: 1\n"
+ "\n"
+ "bill-to: &id001\n"
+ " street: |\n"
+ " 123 Tornado Alley\n"
+ " Suite 16\n"
+ " city: East Centerville\n"
+ " state: KS\n"
+ "\n"
+ "ship-to: *id001\n"
+ "\n"
+ "specialDelivery: >\n"
+ " Follow the Yellow Brick\n"
+ " Road to the Emerald City.\n"
+ " Pay no attention to the\n"
+ " man behind the curtain.",
// numberTest
""
+ "someKey:\n"
+ " 1: 1\n"
+ " 2: 2\n"
+ " 3: 3\n"
+ " 4: 4",
// nullTest
""
+ "null:\n"
+ " null: object\n"
+ " object: null\n"
},
{
// provider
JsonConfiguration.class,
// testDocument
""
+ "{\n"
+ " \"customer\": {\n"
+ " \"given\": \"Dorothy\", \n"
+ " \"family\": \"Gale\"\n"
+ " }, \n"
+ " \"ship-to\": {\n"
+ " \"city\": \"East Centerville\", \n"
+ " \"state\": \"KS\", \n"
+ " \"street\": \"123 Tornado Alley\\nSuite 16\\n\"\n"
+ " }, \n"
+ " \"bill-to\": {\n"
+ " \"city\": \"East Centerville\", \n"
+ " \"state\": \"KS\", \n"
+ " \"street\": \"123 Tornado Alley\\nSuite 16\\n\"\n"
+ " }, \n"
+ " \"date\": \"2012-08-06\", \n"
+ " \"items\": [\n"
+ " {\n"
+ " \"part_no\": \"A4786\", \n"
+ " \"price\": 1.47, \n"
+ " \"descrip\": \"Water Bucket (Filled)\", \n"
+ " \"quantity\": 4\n"
+ " }, \n"
+ " {\n"
+ " \"part_no\": \"E1628\", \n"
+ " \"descrip\": \"High Heeled \\\"Ruby\\\" Slippers\", \n"
+ " \"price\": 100.27, \n"
+ " \"quantity\": 1, \n"
+ " \"size\": 8\n"
+ " }\n"
+ " ], \n"
+ " \"receipt\": \"Oz-Ware Purchase Invoice\", \n"
+ " \"specialDelivery\": \"Follow the Yellow Brick Road to the Emerald City. Pay no attention to the man behind the curtain.\"\n"
+ "}",
// numberTest
""
+ "{\n"
+ " \"someKey\": {\n"
+ " \"1\": 1, \n"
+ " \"2\": 2, \n"
+ " \"3\": 3, \n"
+ " \"4\": 4\n"
+ " }\n"
+ "}",
// nullTest
""
+ "{\n"
+ " \"null\": {\n"
+ " \"null\": \"object\", \n"
+ " \"object\": null\n"
+ " }\n"
+ "}"
}
} );
// CHECKSTYLE:ON
}
//
private final Class<? extends ConfigurationProvider> provider;
private final String testDocument;
private final String numberTest;
private final String nullTest;
@Test
public void testConfig() throws Exception
{
Configuration conf = ConfigurationProvider.getProvider( provider ).load( testDocument );
testSection( conf );
StringWriter sw = new StringWriter();
ConfigurationProvider.getProvider( provider ).save( conf, sw );
// Check nulls were saved, see #1094
Assert.assertFalse( "Config contains null", sw.toString().contains( "null" ) );
conf = ConfigurationProvider.getProvider( provider ).load( new StringReader( sw.toString() ) );
conf.set( "receipt", "Oz-Ware Purchase Invoice" ); // Add it back
testSection( conf );
}
private void testSection(Configuration conf)
{
Assert.assertEquals( "receipt", "Oz-Ware Purchase Invoice", conf.getString( "receipt" ) );
// Assert.assertEquals( "date", "2012-08-06", conf.get( "date" ).toString() );
Configuration customer = conf.getSection( "customer" );
Assert.assertEquals( "customer.given", "Dorothy", customer.getString( "given" ) );
Assert.assertEquals( "customer.given", "Dorothy", conf.getString( "customer.given" ) );
List items = conf.getList( "items" );
Map item = (Map) items.get( 0 );
Assert.assertEquals( "items[0].part_no", "A4786", item.get( "part_no" ) );
conf.set( "receipt", null );
Assert.assertEquals( null, conf.get( "receipt" ) );
Assert.assertEquals( "foo", conf.get( "receipt", "foo" ) );
Configuration newSection = conf.getSection( "new.section" );
newSection.set( "value", "foo" );
Assert.assertEquals( "foo", conf.get( "new.section.value" ) );
conf.set( "other.new.section", "bar" );
Assert.assertEquals( "bar", conf.get( "other.new.section" ) );
Assert.assertTrue( conf.contains( "customer.given" ) );
Assert.assertTrue( customer.contains( "given" ) );
Assert.assertFalse( conf.contains( "customer.foo" ) );
Assert.assertFalse( customer.contains( "foo" ) );
}
@Test
public void testNumberedKeys()
{
Configuration conf = ConfigurationProvider.getProvider( provider ).load( numberTest );
Configuration section = conf.getSection( "someKey" );
for ( String key : section.getKeys() )
{
// empty
}
}
@Test
public void testNull()
{
Configuration conf = ConfigurationProvider.getProvider( provider ).load( nullTest );
Assert.assertEquals( "object", conf.get( "null.null" ) );
Assert.assertEquals( "object", conf.getSection( "null" ).get( "null" ) );
Assert.assertEquals( null, conf.get( "null.object" ) );
Assert.assertEquals( "", conf.getString( "null.object" ) );
}
@Test
public void testMapAddition()
{
Configuration conf = ConfigurationProvider.getProvider( provider ).load( testDocument );
conf.set( "addition", Collections.singletonMap( "foo", "bar" ) );
// Order matters
Assert.assertEquals( "bar", conf.getSection( "addition" ).getString( "foo" ) );
Assert.assertEquals( "bar", conf.getString( "addition.foo" ) );
Assert.assertTrue( conf.get( "addition" ) instanceof Configuration );
}
}

View File

@ -1,143 +0,0 @@
package net.md_5.bungee.config;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
public class YamlConfigurationTest
{
private static final String TEST_DOCUMENT = ""
+ "receipt: Oz-Ware Purchase Invoice\n"
+ "date: 2012-08-06\n"
+ "customer:\n"
+ " given: Dorothy\n"
+ " family: Gale\n"
+ "\n"
+ "items:\n"
+ " - part_no: A4786\n"
+ " descrip: Water Bucket (Filled)\n"
+ " price: 1.47\n"
+ " quantity: 4\n"
+ "\n"
+ " - part_no: E1628\n"
+ " descrip: High Heeled \"Ruby\" Slippers\n"
+ " size: 8\n"
+ " price: 100.27\n"
+ " quantity: 1\n"
+ "\n"
+ "bill-to: &id001\n"
+ " street: |\n"
+ " 123 Tornado Alley\n"
+ " Suite 16\n"
+ " city: East Centerville\n"
+ " state: KS\n"
+ "\n"
+ "ship-to: *id001\n"
+ "\n"
+ "specialDelivery: >\n"
+ " Follow the Yellow Brick\n"
+ " Road to the Emerald City.\n"
+ " Pay no attention to the\n"
+ " man behind the curtain.";
private static final String NUMBER_TEST = ""
+ "someKey:\n"
+ " 1: 1\n"
+ " 2: 2\n"
+ " 3: 3\n"
+ " 4: 4";
private static final String NULL_TEST = ""
+ "null:\n"
+ " null: object\n"
+ " object: null\n";
@Test
public void testConfig() throws Exception
{
Configuration conf = ConfigurationProvider.getProvider( YamlConfiguration.class ).load( TEST_DOCUMENT );
testSection( conf );
StringWriter sw = new StringWriter();
ConfigurationProvider.getProvider( YamlConfiguration.class ).save( conf, sw );
// Check nulls were saved, see #1094
Assert.assertFalse( "Config contains null", sw.toString().contains( "null" ) );
conf = ConfigurationProvider.getProvider( YamlConfiguration.class ).load( new StringReader( sw.toString() ) );
conf.set( "receipt", "Oz-Ware Purchase Invoice" ); // Add it back
testSection( conf );
}
private void testSection(Configuration conf)
{
Assert.assertEquals( "receipt", "Oz-Ware Purchase Invoice", conf.getString( "receipt" ) );
// Assert.assertEquals( "date", "2012-08-06", conf.get( "date" ).toString() );
Configuration customer = conf.getSection( "customer" );
Assert.assertEquals( "customer.given", "Dorothy", customer.getString( "given" ) );
Assert.assertEquals( "customer.given", "Dorothy", conf.getString( "customer.given" ) );
List items = conf.getList( "items" );
Map item = (Map) items.get( 0 );
Assert.assertEquals( "items[0].part_no", "A4786", item.get( "part_no" ) );
conf.set( "receipt", null );
Assert.assertEquals( null, conf.get( "receipt" ) );
Assert.assertEquals( "foo", conf.get( "receipt", "foo" ) );
Configuration newSection = conf.getSection( "new.section" );
newSection.set( "value", "foo" );
Assert.assertEquals( "foo", conf.get( "new.section.value" ) );
conf.set( "other.new.section", "bar" );
Assert.assertEquals( "bar", conf.get( "other.new.section" ) );
Assert.assertTrue( conf.contains( "customer.given" ) );
Assert.assertTrue( customer.contains( "given" ) );
Assert.assertFalse( conf.contains( "customer.foo" ) );
Assert.assertFalse( customer.contains( "foo" ) );
}
@Test
public void testNumberedKeys()
{
Configuration conf = ConfigurationProvider.getProvider( YamlConfiguration.class ).load( NUMBER_TEST );
Configuration section = conf.getSection( "someKey" );
for ( String key : section.getKeys() )
{
// empty
}
}
@Test
public void testNull()
{
Configuration conf = ConfigurationProvider.getProvider( YamlConfiguration.class ).load( NULL_TEST );
Assert.assertEquals( "object", conf.get( "null.null" ) );
Assert.assertEquals( "object", conf.getSection( "null" ).get( "null" ) );
Assert.assertEquals( null, conf.get( "null.object" ) );
Assert.assertEquals( "", conf.getString( "null.object" ) );
}
@Test
public void testMapAddition()
{
Configuration conf = ConfigurationProvider.getProvider( YamlConfiguration.class ).load( TEST_DOCUMENT );
conf.set( "addition", Collections.singletonMap( "foo", "bar" ) );
// Order matters
Assert.assertEquals( "bar", conf.getSection( "addition" ).getString( "foo" ) );
Assert.assertEquals( "bar", conf.getString( "addition.foo" ) );
Assert.assertTrue( conf.get( "addition" ) instanceof Configuration );
}
}

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-event</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Event</name>

View File

@ -166,6 +166,8 @@ public class EventBus
* Shouldn't be called without first locking the writeLock; intended for use
* only inside {@link #register(java.lang.Object) register(Object)} or
* {@link #unregister(java.lang.Object) unregister(Object)}.
*
* @param eventClass event class
*/
private void bakeHandlers(Class<?> eventClass)
{

View File

@ -21,6 +21,8 @@ public @interface EventHandler
* <li>HIGH</li>
* <li>HIGHEST</li>
* </ol>
*
* @return handler priority
*/
byte priority() default EventPriority.NORMAL;
}

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-log</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Log</name>

View File

@ -3,7 +3,6 @@ package net.md_5.bungee.log;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
@ -12,7 +11,6 @@ import jline.console.ConsoleReader;
public class BungeeLogger extends Logger
{
private final Formatter formatter = new ConciseFormatter();
private final LogDispatcher dispatcher = new LogDispatcher( this );
@SuppressWarnings(
@ -28,12 +26,12 @@ public class BungeeLogger extends Logger
try
{
FileHandler fileHandler = new FileHandler( filePattern, 1 << 24, 8, true );
fileHandler.setFormatter( formatter );
fileHandler.setFormatter( new ConciseFormatter( false ) );
addHandler( fileHandler );
ColouredWriter consoleHandler = new ColouredWriter( reader );
consoleHandler.setLevel( Level.INFO );
consoleHandler.setFormatter( formatter );
consoleHandler.setLevel( Level.parse( System.getProperty( "net.md_5.bungee.console-log-level", "INFO" ) ) );
consoleHandler.setFormatter( new ConciseFormatter( true ) );
addHandler( consoleHandler );
} catch ( IOException ex )
{

View File

@ -1,11 +1,11 @@
package net.md_5.bungee.log;
import java.io.IOException;
import java.util.EnumMap;
import java.util.Map;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.regex.Pattern;
import jline.console.ConsoleReader;
import lombok.Data;
import net.md_5.bungee.api.ChatColor;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.Ansi.Erase;
@ -13,43 +13,57 @@ import org.fusesource.jansi.Ansi.Erase;
public class ColouredWriter extends Handler
{
private final Map<ChatColor, String> replacements = new EnumMap<>( ChatColor.class );
private final ChatColor[] colors = ChatColor.values();
@Data
private static class ReplacementSpecification
{
private final Pattern pattern;
private final String replacement;
}
private static ReplacementSpecification compile(ChatColor color, String ansi)
{
return new ReplacementSpecification( Pattern.compile( "(?i)" + color.toString() ), ansi );
}
private static final ReplacementSpecification[] REPLACEMENTS = new ReplacementSpecification[]
{
compile( ChatColor.BLACK, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.BLACK ).boldOff().toString() ),
compile( ChatColor.DARK_BLUE, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.BLUE ).boldOff().toString() ),
compile( ChatColor.DARK_GREEN, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.GREEN ).boldOff().toString() ),
compile( ChatColor.DARK_AQUA, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.CYAN ).boldOff().toString() ),
compile( ChatColor.DARK_RED, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.RED ).boldOff().toString() ),
compile( ChatColor.DARK_PURPLE, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.MAGENTA ).boldOff().toString() ),
compile( ChatColor.GOLD, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.YELLOW ).boldOff().toString() ),
compile( ChatColor.GRAY, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.WHITE ).boldOff().toString() ),
compile( ChatColor.DARK_GRAY, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.BLACK ).bold().toString() ),
compile( ChatColor.BLUE, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.BLUE ).bold().toString() ),
compile( ChatColor.GREEN, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.GREEN ).bold().toString() ),
compile( ChatColor.AQUA, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.CYAN ).bold().toString() ),
compile( ChatColor.RED, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.RED ).bold().toString() ),
compile( ChatColor.LIGHT_PURPLE, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.MAGENTA ).bold().toString() ),
compile( ChatColor.YELLOW, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.YELLOW ).bold().toString() ),
compile( ChatColor.WHITE, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.WHITE ).bold().toString() ),
compile( ChatColor.MAGIC, Ansi.ansi().a( Ansi.Attribute.BLINK_SLOW ).toString() ),
compile( ChatColor.BOLD, Ansi.ansi().a( Ansi.Attribute.UNDERLINE_DOUBLE ).toString() ),
compile( ChatColor.STRIKETHROUGH, Ansi.ansi().a( Ansi.Attribute.STRIKETHROUGH_ON ).toString() ),
compile( ChatColor.UNDERLINE, Ansi.ansi().a( Ansi.Attribute.UNDERLINE ).toString() ),
compile( ChatColor.ITALIC, Ansi.ansi().a( Ansi.Attribute.ITALIC ).toString() ),
compile( ChatColor.RESET, Ansi.ansi().a( Ansi.Attribute.RESET ).toString() ),
};
//
private final ConsoleReader console;
public ColouredWriter(ConsoleReader console)
{
this.console = console;
replacements.put( ChatColor.BLACK, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.BLACK ).boldOff().toString() );
replacements.put( ChatColor.DARK_BLUE, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.BLUE ).boldOff().toString() );
replacements.put( ChatColor.DARK_GREEN, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.GREEN ).boldOff().toString() );
replacements.put( ChatColor.DARK_AQUA, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.CYAN ).boldOff().toString() );
replacements.put( ChatColor.DARK_RED, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.RED ).boldOff().toString() );
replacements.put( ChatColor.DARK_PURPLE, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.MAGENTA ).boldOff().toString() );
replacements.put( ChatColor.GOLD, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.YELLOW ).boldOff().toString() );
replacements.put( ChatColor.GRAY, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.WHITE ).boldOff().toString() );
replacements.put( ChatColor.DARK_GRAY, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.BLACK ).bold().toString() );
replacements.put( ChatColor.BLUE, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.BLUE ).bold().toString() );
replacements.put( ChatColor.GREEN, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.GREEN ).bold().toString() );
replacements.put( ChatColor.AQUA, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.CYAN ).bold().toString() );
replacements.put( ChatColor.RED, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.RED ).bold().toString() );
replacements.put( ChatColor.LIGHT_PURPLE, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.MAGENTA ).bold().toString() );
replacements.put( ChatColor.YELLOW, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.YELLOW ).bold().toString() );
replacements.put( ChatColor.WHITE, Ansi.ansi().a( Ansi.Attribute.RESET ).fg( Ansi.Color.WHITE ).bold().toString() );
replacements.put( ChatColor.MAGIC, Ansi.ansi().a( Ansi.Attribute.BLINK_SLOW ).toString() );
replacements.put( ChatColor.BOLD, Ansi.ansi().a( Ansi.Attribute.UNDERLINE_DOUBLE ).toString() );
replacements.put( ChatColor.STRIKETHROUGH, Ansi.ansi().a( Ansi.Attribute.STRIKETHROUGH_ON ).toString() );
replacements.put( ChatColor.UNDERLINE, Ansi.ansi().a( Ansi.Attribute.UNDERLINE ).toString() );
replacements.put( ChatColor.ITALIC, Ansi.ansi().a( Ansi.Attribute.ITALIC ).toString() );
replacements.put( ChatColor.RESET, Ansi.ansi().a( Ansi.Attribute.RESET ).toString() );
}
public void print(String s)
{
for ( ChatColor color : colors )
for ( ReplacementSpecification replacement : REPLACEMENTS )
{
s = s.replaceAll( "(?i)" + color.toString(), replacements.get( color ) );
s = replacement.pattern.matcher( s ).replaceAll( replacement.replacement );
}
try
{

View File

@ -5,12 +5,17 @@ import java.io.StringWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.ChatColor;
@RequiredArgsConstructor
public class ConciseFormatter extends Formatter
{
private final DateFormat date = new SimpleDateFormat( System.getProperty( "net.md_5.bungee.log-date-format", "HH:mm:ss" ) );
private final boolean coloured;
@Override
@SuppressWarnings("ThrowableResultIgnored")
@ -20,7 +25,7 @@ public class ConciseFormatter extends Formatter
formatted.append( date.format( record.getMillis() ) );
formatted.append( " [" );
formatted.append( record.getLevel().getLocalizedName() );
appendLevel( formatted, record.getLevel() );
formatted.append( "] " );
formatted.append( formatMessage( record ) );
formatted.append( '\n' );
@ -34,4 +39,31 @@ public class ConciseFormatter extends Formatter
return formatted.toString();
}
private void appendLevel(StringBuilder builder, Level level)
{
if ( !coloured )
{
builder.append( level.getLocalizedName() );
return;
}
ChatColor color;
if ( level == Level.INFO )
{
color = ChatColor.BLUE;
} else if ( level == Level.WARNING )
{
color = ChatColor.YELLOW;
} else if ( level == Level.SEVERE )
{
color = ChatColor.RED;
} else
{
color = ChatColor.AQUA;
}
builder.append( color ).append( level.getLocalizedName() ).append( ChatColor.RESET );
}
}

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module-cmd-alert</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cmd_alert</name>

View File

@ -19,7 +19,7 @@ public class CommandAlert extends Command
{
if ( args.length == 0 )
{
sender.sendMessage( ChatColor.RED + "You must supply a message." );
sender.sendMessage( ProxyServer.getInstance().getTranslation( "message_needed" ) );
} else
{
StringBuilder builder = new StringBuilder();

View File

@ -23,7 +23,7 @@ public class CommandAlertRaw extends Command
{
if ( args.length == 0 )
{
sender.sendMessage( ChatColor.RED + "You must supply a message." );
sender.sendMessage( ProxyServer.getInstance().getTranslation( "message_needed" ) );
} else
{
String message = Joiner.on( ' ' ).join( args );
@ -40,15 +40,15 @@ public class CommandAlertRaw extends Command
}
if ( sender instanceof ProxiedPlayer )
{
sender.sendMessage( new ComponentBuilder( "An error occurred while parsing your message. (Hover for details)" )
.color( ChatColor.RED )
.underlined( true )
.event( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new ComponentBuilder( error.getMessage() ).color( ChatColor.RED ).create() ) )
sender.sendMessage( new ComponentBuilder( ProxyServer.getInstance().getTranslation( "error_occurred_player" ) )
.event( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new ComponentBuilder( error.getMessage() )
.color( ChatColor.RED )
.create() ) )
.create()
);
} else
{
sender.sendMessage( new ComponentBuilder( "An error occurred while parsing your message: " ).color( ChatColor.RED ).append( error.getMessage() ).create() );
sender.sendMessage( ProxyServer.getInstance().getTranslation( "error_occurred_console", error.getMessage() ) );
}
}
}

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module-cmd-find</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cmd_find</name>

View File

@ -1,9 +1,7 @@
package net.md_5.bungee.module.cmd.find;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.command.PlayerCommand;
@ -20,16 +18,16 @@ public class CommandFind extends PlayerCommand
{
if ( args.length != 1 )
{
sender.sendMessage( new ComponentBuilder( "Please follow this command by a user name" ).color( ChatColor.RED ).create() );
sender.sendMessage( ProxyServer.getInstance().getTranslation( "username_needed" ) );
} else
{
ProxiedPlayer player = ProxyServer.getInstance().getPlayer( args[0] );
if ( player == null || player.getServer() == null )
{
sender.sendMessage( new ComponentBuilder( "That user is not online" ).color( ChatColor.RED ).create() );
sender.sendMessage( ProxyServer.getInstance().getTranslation( "user_not_online" ) );
} else
{
sender.sendMessage( new ComponentBuilder( args[0] ).color( ChatColor.GREEN ).append( " is online at " ).append( player.getServer().getInfo().getName() ).create() );
sender.sendMessage( ProxyServer.getInstance().getTranslation( "user_online_at", player.getName(), player.getServer().getInfo().getName() ) );
}
}
}

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module-cmd-list</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cmd_list</name>

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module-cmd-send</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cmd_send</name>

View File

@ -1,12 +1,22 @@
package net.md_5.bungee.module.cmd.send;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ServerConnectRequest;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ServerConnectEvent;
@ -16,6 +26,71 @@ import net.md_5.bungee.api.plugin.TabExecutor;
public class CommandSend extends Command implements TabExecutor
{
protected static class SendCallback
{
private final Map<ServerConnectRequest.Result, List<String>> results = new HashMap<>();
private final CommandSender sender;
private int count = 0;
public SendCallback(CommandSender sender)
{
this.sender = sender;
for ( ServerConnectRequest.Result result : ServerConnectRequest.Result.values() )
{
results.put( result, new ArrayList<String>() );
}
}
public void lastEntryDone()
{
sender.sendMessage( ChatColor.GREEN.toString() + ChatColor.BOLD + "Send Results:" );
for ( Map.Entry<ServerConnectRequest.Result, List<String>> entry : results.entrySet() )
{
ComponentBuilder builder = new ComponentBuilder( "" );
if ( !entry.getValue().isEmpty() )
{
builder.event( new HoverEvent( HoverEvent.Action.SHOW_TEXT,
new ComponentBuilder( Joiner.on( ", " ).join( entry.getValue() ) ).color( ChatColor.YELLOW ).create() ) );
}
builder.append( entry.getKey().name() + ": " ).color( ChatColor.GREEN );
builder.append( "" + entry.getValue().size() ).bold( true );
sender.sendMessage( builder.create() );
}
}
public static class Entry implements Callback<ServerConnectRequest.Result>
{
private final SendCallback callback;
private final ProxiedPlayer player;
private final ServerInfo target;
public Entry(SendCallback callback, ProxiedPlayer player, ServerInfo target)
{
this.callback = callback;
this.player = player;
this.target = target;
this.callback.count++;
}
@Override
public void done(ServerConnectRequest.Result result, Throwable error)
{
callback.results.get( result ).add( player.getName() );
if ( result == ServerConnectRequest.Result.SUCCESS )
{
player.sendMessage( ProxyServer.getInstance().getTranslation( "you_got_summoned", target.getName(), callback.sender.getName() ) );
}
if ( --callback.count == 0 )
{
callback.lastEntryDone();
}
}
}
}
public CommandSend()
{
super( "send", "bungeecord.command.send" );
@ -26,65 +101,60 @@ public class CommandSend extends Command implements TabExecutor
{
if ( args.length != 2 )
{
sender.sendMessage( ChatColor.RED + "Not enough arguments, usage: /send <server|player|all|current> <target>" );
sender.sendMessage( ProxyServer.getInstance().getTranslation( "send_cmd_usage" ) );
return;
}
ServerInfo target = ProxyServer.getInstance().getServerInfo( args[1] );
if ( target == null )
ServerInfo server = ProxyServer.getInstance().getServerInfo( args[1] );
if ( server == null )
{
sender.sendMessage( ProxyServer.getInstance().getTranslation( "no_server" ) );
return;
}
List<ProxiedPlayer> targets;
if ( args[0].equalsIgnoreCase( "all" ) )
{
for ( ProxiedPlayer p : ProxyServer.getInstance().getPlayers() )
{
summon( p, target, sender );
}
targets = new ArrayList<>( ProxyServer.getInstance().getPlayers() );
} else if ( args[0].equalsIgnoreCase( "current" ) )
{
if ( !( sender instanceof ProxiedPlayer ) )
{
sender.sendMessage( ChatColor.RED + "Only in game players can use this command" );
sender.sendMessage( ProxyServer.getInstance().getTranslation( "player_only" ) );
return;
}
ProxiedPlayer player = (ProxiedPlayer) sender;
for ( ProxiedPlayer p : player.getServer().getInfo().getPlayers() )
{
summon( p, target, sender );
}
targets = new ArrayList<>( player.getServer().getInfo().getPlayers() );
} else
{
// If we use a server name, send the entire server. This takes priority over players.
ServerInfo serverTarget = ProxyServer.getInstance().getServerInfo( args[0] );
if ( serverTarget != null )
{
for ( ProxiedPlayer p : serverTarget.getPlayers() )
{
summon( p, target, sender );
}
targets = new ArrayList<>( serverTarget.getPlayers() );
} else
{
ProxiedPlayer player = ProxyServer.getInstance().getPlayer( args[0] );
if ( player == null )
{
sender.sendMessage( ChatColor.RED + "That player is not online" );
sender.sendMessage( ProxyServer.getInstance().getTranslation( "user_not_online" ) );
return;
}
summon( player, target, sender );
targets = Collections.singletonList( player );
}
}
sender.sendMessage( ChatColor.GREEN + "Successfully summoned player(s)" );
}
private void summon(ProxiedPlayer player, ServerInfo target, CommandSender sender)
{
if ( player.getServer() != null && !player.getServer().getInfo().equals( target ) )
final SendCallback callback = new SendCallback( sender );
for ( ProxiedPlayer player : targets )
{
player.connect( target, ServerConnectEvent.Reason.COMMAND );
player.sendMessage( ChatColor.GOLD + "Summoned to " + target.getName() + " by " + sender.getName() );
ServerConnectRequest request = ServerConnectRequest.builder()
.target( server )
.reason( ServerConnectEvent.Reason.COMMAND )
.callback( new SendCallback.Entry( callback, player, server ) )
.build();
player.connect( request );
}
sender.sendMessage( ChatColor.DARK_GREEN + "Attempting to send " + targets.size() + " players to " + server.getName() );
}
@Override

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module-cmd-server</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>cmd_server</name>

View File

@ -40,7 +40,7 @@ public class CommandServer extends Command implements TabExecutor
sender.sendMessage( ProxyServer.getInstance().getTranslation( "current_server", ( (ProxiedPlayer) sender ).getServer().getInfo().getName() ) );
}
ComponentBuilder serverList = new ComponentBuilder( "" ).append( TextComponent.fromLegacyText( ProxyServer.getInstance().getTranslation( "server_list" ) ) );
ComponentBuilder serverList = new ComponentBuilder().appendLegacy( ProxyServer.getInstance().getTranslation( "server_list" ) );
boolean first = true;
for ( ServerInfo server : servers.values() )
{
@ -50,7 +50,7 @@ public class CommandServer extends Command implements TabExecutor
int count = server.getPlayers().size();
serverTextComponent.setHoverEvent( new HoverEvent(
HoverEvent.Action.SHOW_TEXT,
new ComponentBuilder( count + ( count == 1 ? " player" : " players" ) + "\n" ).append( "Click to connect to the server" ).italic( true ).create() )
new ComponentBuilder( count + ( count == 1 ? " player" : " players" ) + "\n" ).appendLegacy( ProxyServer.getInstance().getTranslation( "click_to_connect" ) ).create() )
);
serverTextComponent.setClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, "/server " + server.getName() ) );
serverList.append( serverTextComponent );

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>BungeeCord Modules</name>
@ -30,6 +30,7 @@
<properties>
<module.author>SpigotMC</module.author>
<maven.deploy.skip>true</maven.deploy.skip>
<maven.javadoc.skip>true</maven.javadoc.skip>
</properties>
<dependencies>

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-module-reconnect-yaml</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>reconnect_yaml</name>

View File

@ -24,7 +24,7 @@ public class YamlReconnectHandler extends AbstractReconnectHandler
private final File file = new File( "locations.yml" );
private final ReadWriteLock lock = new ReentrantReadWriteLock();
/*========================================================================*/
private CaseInsensitiveMap< String> data;
private CaseInsensitiveMap<String> data;
@SuppressWarnings("unchecked")
public YamlReconnectHandler()

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-native</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Native</name>

View File

@ -28,8 +28,8 @@ public final class NativeCode<T>
{
try
{
return ( loaded ) ? nativeImpl.newInstance() : javaImpl.newInstance();
} catch ( IllegalAccessException | InstantiationException ex )
return ( loaded ) ? nativeImpl.getDeclaredConstructor().newInstance() : javaImpl.getDeclaredConstructor().newInstance();
} catch ( ReflectiveOperationException ex )
{
throw new RuntimeException( "Error getting instance", ex );
}

View File

@ -80,6 +80,9 @@ public class NativeCipherTest
/**
* Hackish test which can test both native and fallback ciphers using direct
* buffers.
*
* @param cipher cipher to test
* @throws java.lang.Exception any exceptions encountered
*/
public void testACipher(BungeeCipher cipher) throws Exception
{

129
pom.xml
View File

@ -5,7 +5,7 @@
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>BungeeCord-Parent</name>
@ -63,13 +63,17 @@
<id>sonatype-nexus-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
<repository>
<id>sonatype-nexus-staging</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
<properties>
<build.number>unknown</build.number>
<netty.version>4.1.34.Final</netty.version>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<netty.version>4.1.49.Final</netty.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
@ -77,13 +81,13 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
<version>21.0</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -95,7 +99,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
</dependencies>
@ -105,7 +109,7 @@
<plugin>
<groupId>net.md-5</groupId>
<artifactId>scriptus</artifactId>
<version>0.3.1</version>
<version>0.3.2</version>
<configuration>
<format>git:${project.name}:${project.version}:%s:${build.number}</format>
</configuration>
@ -121,7 +125,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
<executions>
<execution>
<phase>process-classes</phase>
@ -139,14 +143,14 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>8.19</version>
<version>8.29</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>1.16</version>
<version>1.18</version>
<executions>
<execution>
<phase>process-classes</phase>
@ -164,36 +168,77 @@
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>net.md-5</groupId>
<artifactId>scriptus</artifactId>
<versionRange>[0.3.1,)</versionRange>
<goals>
<goal>describe</goal>
</goals>
</pluginExecutionFilter>
<action>
<execute>
<runOnIncremental>false</runOnIncremental>
</execute>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>dist</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>1.18.10.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>delombok</goal>
</goals>
</execution>
</executions>
<configuration>
<addOutputDirectory>false</addOutputDirectory>
<outputDirectory>${project.build.directory}/delombok</outputDirectory>
<sourceDirectory>${project.build.sourceDirectory}</sourceDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.1.1</version>
<executions>
<!-- Execute Javadoc once normally to catch any warnings -->
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
<!-- Then execute it again to generate properly with delombok -->
<execution>
<id>delombok</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<!-- lombok does not add @return or @param which causes warnings, so ignore -->
<doclint>none</doclint>
<sourcepath>${project.build.directory}/delombok</sourcepath>
</configuration>
</execution>
</executions>
<configuration>
<quiet>true</quiet>
<failOnWarnings>true</failOnWarnings>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-protocol</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Protocol</name>
@ -58,5 +58,11 @@
<version>3.1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>se.llbit</groupId>
<artifactId>jo-nbt</artifactId>
<version>1.3.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -8,6 +8,7 @@ import net.md_5.bungee.protocol.packet.Commands;
import net.md_5.bungee.protocol.packet.EncryptionRequest;
import net.md_5.bungee.protocol.packet.EncryptionResponse;
import net.md_5.bungee.protocol.packet.EntityStatus;
import net.md_5.bungee.protocol.packet.GameState;
import net.md_5.bungee.protocol.packet.Handshake;
import net.md_5.bungee.protocol.packet.KeepAlive;
import net.md_5.bungee.protocol.packet.Kick;
@ -173,4 +174,8 @@ public abstract class AbstractPacketHandler
public void handle(ViewDistance viewDistance) throws Exception
{
}
public void handle(GameState gameState) throws Exception
{
}
}

View File

@ -16,6 +16,7 @@ import net.md_5.bungee.protocol.packet.Commands;
import net.md_5.bungee.protocol.packet.EncryptionRequest;
import net.md_5.bungee.protocol.packet.EncryptionResponse;
import net.md_5.bungee.protocol.packet.EntityStatus;
import net.md_5.bungee.protocol.packet.GameState;
import net.md_5.bungee.protocol.packet.Handshake;
import net.md_5.bungee.protocol.packet.KeepAlive;
import net.md_5.bungee.protocol.packet.Kick;
@ -65,19 +66,25 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_8, 0x00 ),
map( ProtocolConstants.MINECRAFT_1_9, 0x1F ),
map( ProtocolConstants.MINECRAFT_1_13, 0x21 ),
map( ProtocolConstants.MINECRAFT_1_14, 0x20 )
map( ProtocolConstants.MINECRAFT_1_14, 0x20 ),
map( ProtocolConstants.MINECRAFT_1_15, 0x21 ),
map( ProtocolConstants.MINECRAFT_1_16, 0x20 )
);
TO_CLIENT.registerPacket(
Login.class,
map( ProtocolConstants.MINECRAFT_1_8, 0x01 ),
map( ProtocolConstants.MINECRAFT_1_9, 0x23 ),
map( ProtocolConstants.MINECRAFT_1_13, 0x25 )
map( ProtocolConstants.MINECRAFT_1_13, 0x25 ),
map( ProtocolConstants.MINECRAFT_1_15, 0x26 ),
map( ProtocolConstants.MINECRAFT_1_16, 0x25 )
);
TO_CLIENT.registerPacket(
Chat.class,
map( ProtocolConstants.MINECRAFT_1_8, 0x02 ),
map( ProtocolConstants.MINECRAFT_1_9, 0x0F ),
map( ProtocolConstants.MINECRAFT_1_13, 0x0E )
map( ProtocolConstants.MINECRAFT_1_13, 0x0E ),
map( ProtocolConstants.MINECRAFT_1_15, 0x0F ),
map( ProtocolConstants.MINECRAFT_1_16, 0x0E )
);
TO_CLIENT.registerPacket(
Respawn.class,
@ -86,11 +93,15 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_12, 0x34 ),
map( ProtocolConstants.MINECRAFT_1_12_1, 0x35 ),
map( ProtocolConstants.MINECRAFT_1_13, 0x38 ),
map( ProtocolConstants.MINECRAFT_1_14, 0x3A )
map( ProtocolConstants.MINECRAFT_1_14, 0x3A ),
map( ProtocolConstants.MINECRAFT_1_15, 0x3B ),
map( ProtocolConstants.MINECRAFT_1_16, 0x3A )
);
TO_CLIENT.registerPacket(
BossBar.class,
map( ProtocolConstants.MINECRAFT_1_9, 0x0C )
map( ProtocolConstants.MINECRAFT_1_9, 0x0C ),
map( ProtocolConstants.MINECRAFT_1_15, 0x0D ),
map( ProtocolConstants.MINECRAFT_1_16, 0x0C )
);
TO_CLIENT.registerPacket(
PlayerListItem.class, // PlayerInfo
@ -98,13 +109,17 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_9, 0x2D ),
map( ProtocolConstants.MINECRAFT_1_12_1, 0x2E ),
map( ProtocolConstants.MINECRAFT_1_13, 0x30 ),
map( ProtocolConstants.MINECRAFT_1_14, 0x33 )
map( ProtocolConstants.MINECRAFT_1_14, 0x33 ),
map( ProtocolConstants.MINECRAFT_1_15, 0x34 ),
map( ProtocolConstants.MINECRAFT_1_16, 0x33 )
);
TO_CLIENT.registerPacket(
TabCompleteResponse.class,
map( ProtocolConstants.MINECRAFT_1_8, 0x3A ),
map( ProtocolConstants.MINECRAFT_1_9, 0x0E ),
map( ProtocolConstants.MINECRAFT_1_13, 0x10 )
map( ProtocolConstants.MINECRAFT_1_13, 0x10 ),
map( ProtocolConstants.MINECRAFT_1_15, 0x11 ),
map( ProtocolConstants.MINECRAFT_1_16, 0x10 )
);
TO_CLIENT.registerPacket(
ScoreboardObjective.class,
@ -113,7 +128,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_12, 0x41 ),
map( ProtocolConstants.MINECRAFT_1_12_1, 0x42 ),
map( ProtocolConstants.MINECRAFT_1_13, 0x45 ),
map( ProtocolConstants.MINECRAFT_1_14, 0x49 )
map( ProtocolConstants.MINECRAFT_1_14, 0x49 ),
map( ProtocolConstants.MINECRAFT_1_15, 0x4A )
);
TO_CLIENT.registerPacket(
ScoreboardScore.class,
@ -122,7 +138,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_12, 0x44 ),
map( ProtocolConstants.MINECRAFT_1_12_1, 0x45 ),
map( ProtocolConstants.MINECRAFT_1_13, 0x48 ),
map( ProtocolConstants.MINECRAFT_1_14, 0x4C )
map( ProtocolConstants.MINECRAFT_1_14, 0x4C ),
map( ProtocolConstants.MINECRAFT_1_15, 0x4D )
);
TO_CLIENT.registerPacket(
ScoreboardDisplay.class,
@ -131,7 +148,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_12, 0x3A ),
map( ProtocolConstants.MINECRAFT_1_12_1, 0x3B ),
map( ProtocolConstants.MINECRAFT_1_13, 0x3E ),
map( ProtocolConstants.MINECRAFT_1_14, 0x42 )
map( ProtocolConstants.MINECRAFT_1_14, 0x42 ),
map( ProtocolConstants.MINECRAFT_1_15, 0x43 )
);
TO_CLIENT.registerPacket(
Team.class,
@ -140,21 +158,26 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_12, 0x43 ),
map( ProtocolConstants.MINECRAFT_1_12_1, 0x44 ),
map( ProtocolConstants.MINECRAFT_1_13, 0x47 ),
map( ProtocolConstants.MINECRAFT_1_14, 0x4B )
map( ProtocolConstants.MINECRAFT_1_14, 0x4B ),
map( ProtocolConstants.MINECRAFT_1_15, 0x4C )
);
TO_CLIENT.registerPacket(
PluginMessage.class,
map( ProtocolConstants.MINECRAFT_1_8, 0x3F ),
map( ProtocolConstants.MINECRAFT_1_9, 0x18 ),
map( ProtocolConstants.MINECRAFT_1_13, 0x19 ),
map( ProtocolConstants.MINECRAFT_1_14, 0x18 )
map( ProtocolConstants.MINECRAFT_1_14, 0x18 ),
map( ProtocolConstants.MINECRAFT_1_15, 0x19 ),
map( ProtocolConstants.MINECRAFT_1_16, 0x18 )
);
TO_CLIENT.registerPacket(
Kick.class,
map( ProtocolConstants.MINECRAFT_1_8, 0x40 ),
map( ProtocolConstants.MINECRAFT_1_9, 0x1A ),
map( ProtocolConstants.MINECRAFT_1_13, 0x1B ),
map( ProtocolConstants.MINECRAFT_1_14, 0x1A )
map( ProtocolConstants.MINECRAFT_1_14, 0x1A ),
map( ProtocolConstants.MINECRAFT_1_15, 0x1B ),
map( ProtocolConstants.MINECRAFT_1_16, 0x1A )
);
TO_CLIENT.registerPacket(
Title.class,
@ -162,7 +185,9 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_12, 0x47 ),
map( ProtocolConstants.MINECRAFT_1_12_1, 0x48 ),
map( ProtocolConstants.MINECRAFT_1_13, 0x4B ),
map( ProtocolConstants.MINECRAFT_1_14, 0x4F )
map( ProtocolConstants.MINECRAFT_1_14, 0x4F ),
map( ProtocolConstants.MINECRAFT_1_15, 0x50 ),
map( ProtocolConstants.MINECRAFT_1_16, 0x4F )
);
TO_CLIENT.registerPacket(
PlayerListHeaderFooter.class,
@ -172,22 +197,35 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_12, 0x49 ),
map( ProtocolConstants.MINECRAFT_1_12_1, 0x4A ),
map( ProtocolConstants.MINECRAFT_1_13, 0x4E ),
map( ProtocolConstants.MINECRAFT_1_14, 0x53 )
map( ProtocolConstants.MINECRAFT_1_14, 0x53 ),
map( ProtocolConstants.MINECRAFT_1_15, 0x54 ),
map( ProtocolConstants.MINECRAFT_1_16, 0x53 )
);
TO_CLIENT.registerPacket(
EntityStatus.class,
map( ProtocolConstants.MINECRAFT_1_8, 0x1A ),
map( ProtocolConstants.MINECRAFT_1_9, 0x1B ),
map( ProtocolConstants.MINECRAFT_1_13, 0x1C ),
map( ProtocolConstants.MINECRAFT_1_14, 0x1B )
map( ProtocolConstants.MINECRAFT_1_14, 0x1B ),
map( ProtocolConstants.MINECRAFT_1_15, 0x1C ),
map( ProtocolConstants.MINECRAFT_1_16, 0x1B )
);
TO_CLIENT.registerPacket(
Commands.class,
map( ProtocolConstants.MINECRAFT_1_13, 0x11 )
map( ProtocolConstants.MINECRAFT_1_13, 0x11 ),
map( ProtocolConstants.MINECRAFT_1_15, 0x12 ),
map( ProtocolConstants.MINECRAFT_1_16, 0x11 )
);
TO_CLIENT.registerPacket(
GameState.class,
map( ProtocolConstants.MINECRAFT_1_15, 0x1F ),
map( ProtocolConstants.MINECRAFT_1_16, 0x1E )
);
TO_CLIENT.registerPacket(
ViewDistance.class,
map( ProtocolConstants.MINECRAFT_1_14, 0x41 )
map( ProtocolConstants.MINECRAFT_1_14, 0x41 ),
map( ProtocolConstants.MINECRAFT_1_15, 0x42 ),
map( ProtocolConstants.MINECRAFT_1_16, 0x41 )
);
TO_SERVER.registerPacket(
@ -197,7 +235,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_12, 0x0C ),
map( ProtocolConstants.MINECRAFT_1_12_1, 0x0B ),
map( ProtocolConstants.MINECRAFT_1_13, 0x0E ),
map( ProtocolConstants.MINECRAFT_1_14, 0x0F )
map( ProtocolConstants.MINECRAFT_1_14, 0x0F ),
map( ProtocolConstants.MINECRAFT_1_16, 0x10 )
);
TO_SERVER.registerPacket(
Chat.class,

View File

@ -25,6 +25,10 @@ public class ProtocolConstants
public static final int MINECRAFT_1_14_2 = 485;
public static final int MINECRAFT_1_14_3 = 490;
public static final int MINECRAFT_1_14_4 = 498;
public static final int MINECRAFT_1_15 = 573;
public static final int MINECRAFT_1_15_1 = 575;
public static final int MINECRAFT_1_15_2 = 578;
public static final int MINECRAFT_1_16 = 735;
public static final List<String> SUPPORTED_VERSIONS = Arrays.asList(
"1.8.x",
"1.9.x",
@ -32,7 +36,9 @@ public class ProtocolConstants
"1.11.x",
"1.12.x",
"1.13.x",
"1.14.x"
"1.14.x",
"1.15.x",
"1.16.x"
);
public static final List<Integer> SUPPORTED_VERSION_IDS = Arrays.asList(
ProtocolConstants.MINECRAFT_1_8,
@ -53,7 +59,11 @@ public class ProtocolConstants
ProtocolConstants.MINECRAFT_1_14_1,
ProtocolConstants.MINECRAFT_1_14_2,
ProtocolConstants.MINECRAFT_1_14_3,
ProtocolConstants.MINECRAFT_1_14_4
ProtocolConstants.MINECRAFT_1_14_4,
ProtocolConstants.MINECRAFT_1_15,
ProtocolConstants.MINECRAFT_1_15_1,
ProtocolConstants.MINECRAFT_1_15_2,
ProtocolConstants.MINECRAFT_1_16
);
public enum Direction

View File

@ -1,6 +1,7 @@
package net.md_5.bungee.protocol.packet;
import io.netty.buffer.ByteBuf;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -16,14 +17,21 @@ import net.md_5.bungee.protocol.ProtocolConstants;
public class Chat extends DefinedPacket
{
private static final UUID EMPTY_UUID = new UUID( 0L, 0L );
private String message;
private byte position;
private UUID sender;
public Chat(String message)
{
this( message, (byte) 0 );
}
public Chat(String message, byte position)
{
this( message, position, EMPTY_UUID );
}
@Override
public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
@ -31,6 +39,10 @@ public class Chat extends DefinedPacket
if ( direction == ProtocolConstants.Direction.TO_CLIENT )
{
position = buf.readByte();
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_16 )
{
sender = readUUID( buf );
}
}
}
@ -41,6 +53,10 @@ public class Chat extends DefinedPacket
if ( direction == ProtocolConstants.Direction.TO_CLIENT )
{
buf.writeByte( position );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_16 )
{
writeUUID( sender, buf );
}
}
}

View File

@ -7,6 +7,7 @@ import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.LongArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
@ -437,6 +438,35 @@ public class Commands extends DefinedPacket
}
}
};
private static final ArgumentSerializer<LongArgumentType> LONG = new ArgumentSerializer<LongArgumentType>()
{
@Override
protected LongArgumentType read(ByteBuf buf)
{
byte flags = buf.readByte();
long min = ( flags & 0x1 ) != 0 ? buf.readLong() : Long.MIN_VALUE;
long max = ( flags & 0x2 ) != 0 ? buf.readLong() : Long.MAX_VALUE;
return LongArgumentType.longArg( min, max );
}
@Override
protected void write(ByteBuf buf, LongArgumentType t)
{
boolean hasMin = t.getMinimum() != Long.MIN_VALUE;
boolean hasMax = t.getMaximum() != Long.MAX_VALUE;
buf.writeByte( binaryFlag( hasMin, hasMax ) );
if ( hasMin )
{
buf.writeLong( t.getMinimum() );
}
if ( hasMax )
{
buf.writeLong( t.getMaximum() );
}
}
};
private static final ProperArgumentSerializer<StringArgumentType> STRING = new ProperArgumentSerializer<StringArgumentType>()
{
@Override
@ -475,6 +505,7 @@ public class Commands extends DefinedPacket
PROVIDERS.put( "brigadier:float", FLOAT );
PROVIDERS.put( "brigadier:double", DOUBLE );
PROVIDERS.put( "brigadier:integer", INTEGER );
PROVIDERS.put( "brigadier:long", LONG );
PROVIDERS.put( "brigadier:string", STRING );
PROPER_PROVIDERS.put( StringArgumentType.class, STRING );
@ -516,6 +547,9 @@ public class Commands extends DefinedPacket
PROVIDERS.put( "minecraft:entity_summon", VOID );
PROVIDERS.put( "minecraft:dimension", VOID );
PROVIDERS.put( "minecraft:time", VOID ); // 1.14
PROVIDERS.put( "minecraft:uuid", VOID ); // 1.16
PROVIDERS.put( "minecraft:test_argument", VOID ); // 1.16, debug
PROVIDERS.put( "minecraft:test_class", VOID ); // 1.16, debug
}
private static ArgumentType<?> read(String key, ByteBuf buf)
@ -586,6 +620,7 @@ public class Commands extends DefinedPacket
PROVIDERS.put( "minecraft:ask_server", ASK_SERVER );
registerDummy( "minecraft:all_recipes" );
registerDummy( "minecraft:available_sounds" );
registerDummy( "minecraft:available_biomes" );
registerDummy( "minecraft:summonable_entities" );
}

View File

@ -0,0 +1,42 @@
package net.md_5.bungee.protocol.packet;
import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import net.md_5.bungee.protocol.AbstractPacketHandler;
import net.md_5.bungee.protocol.DefinedPacket;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class GameState extends DefinedPacket
{
public static final short IMMEDIATE_RESPAWN = 11;
//
private short state;
private float value;
@Override
public void read(ByteBuf buf)
{
state = buf.readUnsignedByte();
value = buf.readFloat();
}
@Override
public void write(ByteBuf buf)
{
buf.writeByte( state );
buf.writeFloat( value );
}
@Override
public void handle(AbstractPacketHandler handler) throws Exception
{
handler.handle( this );
}
}

View File

@ -1,6 +1,14 @@
package net.md_5.bungee.protocol.packet;
import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -8,6 +16,8 @@ import lombok.NoArgsConstructor;
import net.md_5.bungee.protocol.AbstractPacketHandler;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.ProtocolConstants;
import se.llbit.nbt.NamedTag;
import se.llbit.nbt.Tag;
@Data
@NoArgsConstructor
@ -18,31 +28,67 @@ public class Login extends DefinedPacket
private int entityId;
private short gameMode;
private int dimension;
private short previousGameMode;
private Set<String> worldNames;
private Tag dimensions;
private Object dimension;
private String worldName;
private long seed;
private short difficulty;
private short maxPlayers;
private String levelType;
private int viewDistance;
private boolean reducedDebugInfo;
private boolean normalRespawn;
private boolean debug;
private boolean flat;
@Override
public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
entityId = buf.readInt();
gameMode = buf.readUnsignedByte();
if ( protocolVersion > ProtocolConstants.MINECRAFT_1_9 )
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_16 )
{
previousGameMode = buf.readUnsignedByte();
worldNames = new HashSet<>();
int worldCount = readVarInt( buf );
Preconditions.checkArgument( worldCount < 128, "Too many worlds %s", worldCount );
for ( int i = 0; i < worldCount; i++ )
{
worldNames.add( readString( buf ) );
}
dimensions = NamedTag.read( new DataInputStream( new ByteBufInputStream( buf ) ) );
Preconditions.checkArgument( !dimensions.isError(), "Error reading dimensions: %s", dimensions.error() );
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_16 )
{
dimension = readString( buf );
worldName = readString( buf );
} else if ( protocolVersion > ProtocolConstants.MINECRAFT_1_9 )
{
dimension = buf.readInt();
} else
{
dimension = buf.readByte();
dimension = (int) buf.readByte();
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_15 )
{
seed = buf.readLong();
}
if ( protocolVersion < ProtocolConstants.MINECRAFT_1_14 )
{
difficulty = buf.readUnsignedByte();
}
maxPlayers = buf.readUnsignedByte();
levelType = readString( buf );
if ( protocolVersion < ProtocolConstants.MINECRAFT_1_16 )
{
levelType = readString( buf );
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_14 )
{
viewDistance = readVarInt( buf );
@ -51,6 +97,15 @@ public class Login extends DefinedPacket
{
reducedDebugInfo = buf.readBoolean();
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_15 )
{
normalRespawn = buf.readBoolean();
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_16 )
{
debug = buf.readBoolean();
flat = buf.readBoolean();
}
}
@Override
@ -58,19 +113,49 @@ public class Login extends DefinedPacket
{
buf.writeInt( entityId );
buf.writeByte( gameMode );
if ( protocolVersion > ProtocolConstants.MINECRAFT_1_9 )
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_16 )
{
buf.writeInt( dimension );
buf.writeByte( previousGameMode );
writeVarInt( worldNames.size(), buf );
for ( String world : worldNames )
{
writeString( world, buf );
}
try
{
dimensions.write( new DataOutputStream( new ByteBufOutputStream( buf ) ) );
} catch ( IOException ex )
{
throw new RuntimeException( "Exception writing dimensions", ex );
}
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_16 )
{
writeString( (String) dimension, buf );
writeString( worldName, buf );
} else if ( protocolVersion > ProtocolConstants.MINECRAFT_1_9 )
{
buf.writeInt( (Integer) dimension );
} else
{
buf.writeByte( dimension );
buf.writeByte( (Integer) dimension );
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_15 )
{
buf.writeLong( seed );
}
if ( protocolVersion < ProtocolConstants.MINECRAFT_1_14 )
{
buf.writeByte( difficulty );
}
buf.writeByte( maxPlayers );
writeString( levelType, buf );
if ( protocolVersion < ProtocolConstants.MINECRAFT_1_16 )
{
writeString( levelType, buf );
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_14 )
{
writeVarInt( viewDistance, buf );
@ -79,6 +164,15 @@ public class Login extends DefinedPacket
{
buf.writeBoolean( reducedDebugInfo );
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_15 )
{
buf.writeBoolean( normalRespawn );
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_16 )
{
buf.writeBoolean( debug );
buf.writeBoolean( flat );
}
}
@Override

View File

@ -1,12 +1,14 @@
package net.md_5.bungee.protocol.packet;
import io.netty.buffer.ByteBuf;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import net.md_5.bungee.protocol.AbstractPacketHandler;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.ProtocolConstants;
@Data
@NoArgsConstructor
@ -15,20 +17,32 @@ import net.md_5.bungee.protocol.DefinedPacket;
public class LoginSuccess extends DefinedPacket
{
private String uuid;
private UUID uuid;
private String username;
@Override
public void read(ByteBuf buf)
public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
uuid = readString( buf );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_16 )
{
uuid = readUUID( buf );
} else
{
uuid = UUID.fromString( readString( buf ) );
}
username = readString( buf );
}
@Override
public void write(ByteBuf buf)
public void write(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
writeString( uuid, buf );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_16 )
{
writeUUID( uuid, buf );
} else
{
writeString( uuid.toString(), buf );
}
writeString( username, buf );
}

View File

@ -16,33 +16,79 @@ import net.md_5.bungee.protocol.ProtocolConstants;
public class Respawn extends DefinedPacket
{
private int dimension;
private Object dimension;
private String worldName;
private long seed;
private short difficulty;
private short gameMode;
private short previousGameMode;
private String levelType;
private boolean debug;
private boolean flat;
private boolean copyMeta;
@Override
public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
dimension = buf.readInt();
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_16 )
{
dimension = readString( buf );
worldName = readString( buf );
} else
{
dimension = buf.readInt();
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_15 )
{
seed = buf.readLong();
}
if ( protocolVersion < ProtocolConstants.MINECRAFT_1_14 )
{
difficulty = buf.readUnsignedByte();
}
gameMode = buf.readUnsignedByte();
levelType = readString( buf );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_16 )
{
previousGameMode = buf.readUnsignedByte();
debug = buf.readBoolean();
flat = buf.readBoolean();
copyMeta = buf.readBoolean();
} else
{
levelType = readString( buf );
}
}
@Override
public void write(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
buf.writeInt( dimension );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_16 )
{
writeString( (String) dimension, buf );
writeString( worldName, buf );
} else
{
buf.writeInt( ( (Integer) dimension ) );
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_15 )
{
buf.writeLong( seed );
}
if ( protocolVersion < ProtocolConstants.MINECRAFT_1_14 )
{
buf.writeByte( difficulty );
}
buf.writeByte( gameMode );
writeString( levelType, buf );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_16 )
{
buf.writeByte( previousGameMode );
buf.writeBoolean( debug );
buf.writeBoolean( flat );
buf.writeBoolean( copyMeta );
} else
{
writeString( levelType, buf );
}
}
@Override

View File

@ -32,6 +32,8 @@ public class Team extends DefinedPacket
/**
* Packet to destroy a team.
*
* @param name team name
*/
public Team(String name)
{

View File

@ -6,13 +6,13 @@
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-proxy</artifactId>
<version>1.14-SNAPSHOT</version>
<version>1.16-R0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Proxy</name>
@ -20,15 +20,10 @@
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
<maven.javadoc.skip>true</maven.javadoc.skip>
</properties>
<dependencies>
<dependency>
<groupId>com.flowpowered</groupId>
<artifactId>flow-nbt</artifactId>
<version>1.0.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-haproxy</artifactId>
@ -87,13 +82,13 @@
<dependency>
<groupId>net.sf.jopt-simple</groupId>
<artifactId>jopt-simple</artifactId>
<version>4.9</version>
<version>5.0.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<version>5.1.48</version>
<scope>runtime</scope>
</dependency>
</dependencies>

View File

@ -23,6 +23,7 @@ import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
@ -39,6 +40,7 @@ import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Handler;
import java.util.logging.Level;
@ -136,6 +138,11 @@ public class BungeeCord extends ProxyServer
private final Map<UUID, UserConnection> connectionsByOfflineUUID = new HashMap<>();
private final Map<UUID, UserConnection> connectionsByUUID = new HashMap<>();
private final ReadWriteLock connectionLock = new ReentrantReadWriteLock();
/**
* Lock to protect the shutdown process from being triggered simultaneously
* from multiple sources.
*/
private final ReentrantLock shutdownLock = new ReentrantLock();
/**
* Plugin manager.
*/
@ -245,7 +252,7 @@ public class BungeeCord extends ProxyServer
* Start this proxy instance by loading the configuration, plugins and
* starting the connect thread.
*
* @throws Exception
* @throws Exception any critical errors encountered
*/
@SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
public void start() throws Exception
@ -299,6 +306,15 @@ public class BungeeCord extends ProxyServer
}
}, 0, TimeUnit.MINUTES.toMillis( 5 ) );
metricsThread.scheduleAtFixedRate( new Metrics(), 0, TimeUnit.MINUTES.toMillis( Metrics.PING_INTERVAL ) );
Runtime.getRuntime().addShutdownHook( new Thread()
{
@Override
public void run()
{
independentThreadStop( getTranslation( "restart" ), false );
}
} );
}
public void startListeners()
@ -307,7 +323,7 @@ public class BungeeCord extends ProxyServer
{
if ( info.isProxyProtocol() )
{
getLogger().log( Level.WARNING, "Using PROXY protocol for listener {0}, please ensure this listener is adequately firewalled.", info.getHost() );
getLogger().log( Level.WARNING, "Using PROXY protocol for listener {0}, please ensure this listener is adequately firewalled.", info.getSocketAddress() );
if ( connectionThrottle != null )
{
@ -324,24 +340,26 @@ public class BungeeCord extends ProxyServer
if ( future.isSuccess() )
{
listeners.add( future.channel() );
getLogger().log( Level.INFO, "Listening on {0}", info.getHost() );
getLogger().log( Level.INFO, "Listening on {0}", info.getSocketAddress() );
} else
{
getLogger().log( Level.WARNING, "Could not bind to host " + info.getHost(), future.cause() );
getLogger().log( Level.WARNING, "Could not bind to host " + info.getSocketAddress(), future.cause() );
}
}
};
new ServerBootstrap()
.channel( PipelineUtils.getServerChannel() )
.channel( PipelineUtils.getServerChannel( info.getSocketAddress() ) )
.option( ChannelOption.SO_REUSEADDR, true ) // TODO: Move this elsewhere!
.childAttr( PipelineUtils.LISTENER, info )
.childHandler( PipelineUtils.SERVER_CHILD )
.group( eventLoops )
.localAddress( info.getHost() )
.localAddress( info.getSocketAddress() )
.bind().addListener( listener );
if ( info.isQueryEnabled() )
{
Preconditions.checkArgument( info.getSocketAddress() instanceof InetSocketAddress, "Can only create query listener on UDP address" );
ChannelFutureListener bindListener = new ChannelFutureListener()
{
@Override
@ -353,7 +371,7 @@ public class BungeeCord extends ProxyServer
getLogger().log( Level.INFO, "Started query on {0}", future.channel().localAddress() );
} else
{
getLogger().log( Level.WARNING, "Could not bind to host " + info.getHost(), future.cause() );
getLogger().log( Level.WARNING, "Could not bind to host " + info.getSocketAddress(), future.cause() );
}
}
};
@ -385,90 +403,110 @@ public class BungeeCord extends ProxyServer
}
@Override
public synchronized void stop(final String reason)
public void stop(final String reason)
{
new Thread( "Shutdown Thread" )
{
@Override
public void run()
{
independentThreadStop( reason, true );
}
}.start();
}
// This must be run on a separate thread to avoid deadlock!
@SuppressFBWarnings("DM_EXIT")
@SuppressWarnings("TooBroadCatch")
private void independentThreadStop(final String reason, boolean callSystemExit)
{
// Acquire the shutdown lock
// This needs to actually block here, otherwise running 'end' and then ctrl+c will cause the thread to terminate prematurely
shutdownLock.lock();
// Acquired the shutdown lock
if ( !isRunning )
{
// Server is already shutting down - nothing to do
shutdownLock.unlock();
return;
}
isRunning = false;
new Thread( "Shutdown Thread" )
stopListeners();
getLogger().info( "Closing pending connections" );
connectionLock.readLock().lock();
try
{
@Override
@SuppressFBWarnings("DM_EXIT")
@SuppressWarnings("TooBroadCatch")
public void run()
getLogger().log( Level.INFO, "Disconnecting {0} connections", connections.size() );
for ( UserConnection user : connections.values() )
{
stopListeners();
getLogger().info( "Closing pending connections" );
user.disconnect( reason );
}
} finally
{
connectionLock.readLock().unlock();
}
connectionLock.readLock().lock();
try
{
getLogger().log( Level.INFO, "Disconnecting {0} connections", connections.size() );
for ( UserConnection user : connections.values() )
{
user.disconnect( reason );
}
} finally
{
connectionLock.readLock().unlock();
}
try
{
Thread.sleep( 500 );
} catch ( InterruptedException ex )
{
}
try
{
Thread.sleep( 500 );
} catch ( InterruptedException ex )
{
}
if ( reconnectHandler != null )
{
getLogger().info( "Saving reconnect locations" );
reconnectHandler.save();
reconnectHandler.close();
}
saveThread.cancel();
metricsThread.cancel();
if ( reconnectHandler != null )
{
getLogger().info( "Saving reconnect locations" );
reconnectHandler.save();
reconnectHandler.close();
}
saveThread.cancel();
metricsThread.cancel();
// TODO: Fix this shit
getLogger().info( "Disabling plugins" );
for ( Plugin plugin : Lists.reverse( new ArrayList<>( pluginManager.getPlugins() ) ) )
{
try
{
plugin.onDisable();
for ( Handler handler : plugin.getLogger().getHandlers() )
{
handler.close();
}
} catch ( Throwable t )
{
getLogger().log( Level.SEVERE, "Exception disabling plugin " + plugin.getDescription().getName(), t );
}
getScheduler().cancel( plugin );
plugin.getExecutorService().shutdownNow();
}
getLogger().info( "Closing IO threads" );
eventLoops.shutdownGracefully();
try
{
eventLoops.awaitTermination( Long.MAX_VALUE, TimeUnit.NANOSECONDS );
} catch ( InterruptedException ex )
{
}
getLogger().info( "Thank you and goodbye" );
// Need to close loggers after last message!
for ( Handler handler : getLogger().getHandlers() )
getLogger().info( "Disabling plugins" );
for ( Plugin plugin : Lists.reverse( new ArrayList<>( pluginManager.getPlugins() ) ) )
{
try
{
plugin.onDisable();
for ( Handler handler : plugin.getLogger().getHandlers() )
{
handler.close();
}
System.exit( 0 );
} catch ( Throwable t )
{
getLogger().log( Level.SEVERE, "Exception disabling plugin " + plugin.getDescription().getName(), t );
}
}.start();
getScheduler().cancel( plugin );
plugin.getExecutorService().shutdownNow();
}
getLogger().info( "Closing IO threads" );
eventLoops.shutdownGracefully();
try
{
eventLoops.awaitTermination( Long.MAX_VALUE, TimeUnit.NANOSECONDS );
} catch ( InterruptedException ex )
{
}
getLogger().info( "Thank you and goodbye" );
// Need to close loggers after last message!
for ( Handler handler : getLogger().getHandlers() )
{
handler.close();
}
// Unlock the thread before optionally calling system exit, which might invoke this function again.
// If that happens, the system will obtain the lock, and then see that isRunning == false and return without doing anything.
shutdownLock.unlock();
if ( callSystemExit )
{
System.exit( 0 );
}
}
/**
@ -646,6 +684,12 @@ public class BungeeCord extends ProxyServer
@Override
public ServerInfo constructServerInfo(String name, InetSocketAddress address, String motd, boolean restricted)
{
return constructServerInfo( name, (SocketAddress) address, motd, restricted );
}
@Override
public ServerInfo constructServerInfo(String name, SocketAddress address, String motd, boolean restricted)
{
return new BungeeServerInfo( name, address, motd, restricted );
}

View File

@ -6,6 +6,7 @@ import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -30,18 +31,20 @@ import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.packet.PluginMessage;
// CHECKSTYLE:OFF
@RequiredArgsConstructor
@ToString(of =
{
"name", "address", "restricted"
"name", "socketAddress", "restricted"
})
// CHECKSTYLE:ON
public class BungeeServerInfo implements ServerInfo
{
@Getter
private final String name;
@Getter
private final InetSocketAddress address;
private final SocketAddress socketAddress;
private final Collection<ProxiedPlayer> players = new ArrayList<>();
@Getter
private final String motd;
@ -91,7 +94,7 @@ public class BungeeServerInfo implements ServerInfo
@Override
public int hashCode()
{
return address.hashCode();
return socketAddress.hashCode();
}
@Override
@ -122,6 +125,24 @@ public class BungeeServerInfo implements ServerInfo
}
}
private long lastPing;
private ServerPing cachedPing;
public void cachePing(ServerPing serverPing)
{
if ( ProxyServer.getInstance().getConfig().getRemotePingCache() > 0 )
{
this.cachedPing = serverPing;
this.lastPing = System.currentTimeMillis();
}
}
@Override
public InetSocketAddress getAddress()
{
return (InetSocketAddress) socketAddress;
}
@Override
public void ping(final Callback<ServerPing> callback)
{
@ -132,6 +153,18 @@ public class BungeeServerInfo implements ServerInfo
{
Preconditions.checkNotNull( callback, "callback" );
int pingCache = ProxyServer.getInstance().getConfig().getRemotePingCache();
if ( pingCache > 0 && cachedPing != null && ( lastPing - System.currentTimeMillis() ) > pingCache )
{
cachedPing = null;
}
if ( cachedPing != null )
{
callback.done( cachedPing, null );
return;
}
ChannelFutureListener listener = new ChannelFutureListener()
{
@Override
@ -147,11 +180,11 @@ public class BungeeServerInfo implements ServerInfo
}
};
new Bootstrap()
.channel( PipelineUtils.getChannel() )
.channel( PipelineUtils.getChannel( socketAddress ) )
.group( BungeeCord.getInstance().eventLoops )
.handler( PipelineUtils.BASE )
.option( ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000 ) // TODO: Configurable
.remoteAddress( getAddress() )
.option( ChannelOption.CONNECT_TIMEOUT_MILLIS, BungeeCord.getInstance().getConfig().getRemotePingTimeout() )
.remoteAddress( socketAddress )
.connect()
.addListener( listener );
}

View File

@ -6,48 +6,65 @@ import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ConnectionThrottle
{
private final LoadingCache<InetAddress, Integer> throttle;
private final LoadingCache<InetAddress, AtomicInteger> throttle;
private final int throttleLimit;
public ConnectionThrottle(int throttleTime, int throttleLimit)
{
this(Ticker.systemTicker(), throttleTime, throttleLimit);
this( Ticker.systemTicker(), throttleTime, throttleLimit );
}
@VisibleForTesting
ConnectionThrottle(Ticker ticker, int throttleTime, int throttleLimit)
{
this.throttle = CacheBuilder.newBuilder()
.ticker(ticker)
.ticker( ticker )
.concurrencyLevel( Runtime.getRuntime().availableProcessors() )
.initialCapacity( 100 )
.expireAfterWrite( throttleTime, TimeUnit.MILLISECONDS )
.build( new CacheLoader<InetAddress, Integer>()
.build( new CacheLoader<InetAddress, AtomicInteger>()
{
@Override
public Integer load(InetAddress key) throws Exception
public AtomicInteger load(InetAddress key) throws Exception
{
return 0;
return new AtomicInteger();
}
} );
this.throttleLimit = throttleLimit;
}
public void unthrottle(InetAddress address)
public void unthrottle(SocketAddress socketAddress)
{
int throttleCount = throttle.getUnchecked( address ) - 1;
throttle.put( address, throttleCount );
if ( !( socketAddress instanceof InetSocketAddress ) )
{
return;
}
InetAddress address = ( (InetSocketAddress) socketAddress ).getAddress();
AtomicInteger throttleCount = throttle.getIfPresent( address );
if ( throttleCount != null )
{
throttleCount.decrementAndGet();
}
}
public boolean throttle(InetAddress address)
public boolean throttle(SocketAddress socketAddress)
{
int throttleCount = throttle.getUnchecked( address ) + 1;
throttle.put( address, throttleCount );
if ( !( socketAddress instanceof InetSocketAddress ) )
{
return false;
}
InetAddress address = ( (InetSocketAddress) socketAddress ).getAddress();
int throttleCount = throttle.getUnchecked( address ).incrementAndGet();
return throttleCount > throttleLimit;
}

View File

@ -52,7 +52,10 @@ public class Metrics extends TimerTask
}
/**
* Generic method that posts a plugin to the metrics website
* Generic method that posts a plugin to the metrics website.
*
* @param isPing first post or not
* @throws IOException any errors encountered
*/
private void postPlugin(boolean isPing) throws IOException
{
@ -110,6 +113,7 @@ public class Metrics extends TimerTask
* @param buffer the StringBuilder to append the data pair onto
* @param key the key value
* @param value the value
* @throws UnsupportedEncodingException if UTF-8 encoding not supported
*/
private static void encodeDataPair(final StringBuilder buffer, final String key, final String value) throws UnsupportedEncodingException
{
@ -121,6 +125,7 @@ public class Metrics extends TimerTask
*
* @param text the text to encode
* @return the encoded text, as UTF-8
* @throws UnsupportedEncodingException if UTF-8 encoding not supported
*/
private static String encode(final String text) throws UnsupportedEncodingException
{

View File

@ -1,17 +0,0 @@
package net.md_5.bungee;
import net.md_5.bungee.protocol.packet.ClientStatus;
import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.protocol.packet.Respawn;
public class PacketConstants
{
public static final Respawn DIM1_SWITCH = new Respawn( (byte) 1, (byte) 0, (byte) 0, "default" );
public static final Respawn DIM2_SWITCH = new Respawn( (byte) -1, (byte) 0, (byte) 0, "default" );
public static final ClientStatus CLIENT_LOGIN = new ClientStatus( (byte) 0 );
public static final PluginMessage FORGE_MOD_REQUEST = new PluginMessage( "FML", new byte[]
{
0, 0, 0, 0, 0, 2
}, false );
}

View File

@ -2,6 +2,10 @@ package net.md_5.bungee;
import com.google.common.base.Preconditions;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayDeque;
import java.util.Queue;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
@ -25,8 +29,7 @@ public class ServerConnection implements Server
@Getter
private final boolean forgeServer = false;
@Getter
@Setter
private long sentPingId = -1;
private final Queue<KeepAliveData> keepAlives = new ArrayDeque<>();
private final Unsafe unsafe = new Unsafe()
{
@ -54,7 +57,7 @@ public class ServerConnection implements Server
{
Preconditions.checkArgument( reason.length == 0, "Server cannot have disconnect reason" );
ch.delayedClose( null );
ch.close();
}
@Override
@ -65,6 +68,12 @@ public class ServerConnection implements Server
@Override
public InetSocketAddress getAddress()
{
return (InetSocketAddress) getSocketAddress();
}
@Override
public SocketAddress getSocketAddress()
{
return getInfo().getAddress();
}
@ -80,4 +89,12 @@ public class ServerConnection implements Server
{
return unsafe;
}
@Data
public static class KeepAliveData
{
private final long id;
private final long time;
}
}

View File

@ -3,6 +3,7 @@ package net.md_5.bungee;
import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import java.net.InetSocketAddress;
import java.util.Locale;
import java.util.Queue;
import java.util.Set;
@ -36,6 +37,7 @@ import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.packet.EncryptionRequest;
import net.md_5.bungee.protocol.packet.EntityStatus;
import net.md_5.bungee.protocol.packet.GameState;
import net.md_5.bungee.protocol.packet.Handshake;
import net.md_5.bungee.protocol.packet.Kick;
import net.md_5.bungee.protocol.packet.Login;
@ -96,7 +98,7 @@ public class ServerConnector extends PacketHandler
Handshake originalHandshake = user.getPendingConnection().getHandshake();
Handshake copiedHandshake = new Handshake( originalHandshake.getProtocolVersion(), originalHandshake.getHost(), originalHandshake.getPort(), 2 );
if ( BungeeCord.getInstance().config.isIpForward() )
if ( BungeeCord.getInstance().config.isIpForward() && user.getSocketAddress() instanceof InetSocketAddress )
{
String newHost = copiedHandshake.getHost() + "\00" + user.getAddress().getHostString() + "\00" + user.getUUID();
@ -210,8 +212,8 @@ public class ServerConnector extends PacketHandler
user.setServerEntityId( login.getEntityId() );
// Set tab list size, TODO: what shall we do about packet mutability
Login modLogin = new Login( login.getEntityId(), login.getGameMode(), (byte) login.getDimension(), login.getDifficulty(),
(byte) user.getPendingConnection().getListener().getTabListSize(), login.getLevelType(), login.getViewDistance(), login.isReducedDebugInfo() );
Login modLogin = new Login( login.getEntityId(), login.getGameMode(), login.getPreviousGameMode(), login.getWorldNames(), login.getDimensions(), login.getDimension(), login.getWorldName(), login.getSeed(), login.getDifficulty(),
(byte) user.getPendingConnection().getListener().getTabListSize(), login.getLevelType(), login.getViewDistance(), login.isReducedDebugInfo(), login.isNormalRespawn(), login.isDebug(), login.isFlat() );
user.unsafe().sendPacket( modLogin );
@ -250,15 +252,30 @@ public class ServerConnector extends PacketHandler
// Update debug info from login packet
user.unsafe().sendPacket( new EntityStatus( user.getClientEntityId(), login.isReducedDebugInfo() ? EntityStatus.DEBUG_INFO_REDUCED : EntityStatus.DEBUG_INFO_NORMAL ) );
// And immediate respawn
if ( user.getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_1_15 )
{
user.unsafe().sendPacket( new GameState( GameState.IMMEDIATE_RESPAWN, login.isNormalRespawn() ? 0 : 1 ) );
}
user.setDimensionChange( true );
if ( login.getDimension() == user.getDimension() )
if ( login.getDimension().equals( user.getDimension() ) )
{
user.unsafe().sendPacket( new Respawn( ( login.getDimension() >= 0 ? -1 : 0 ), login.getDifficulty(), login.getGameMode(), login.getLevelType() ) );
Object newDim;
String worldName = login.getWorldName();
if ( login.getDimension() instanceof Integer )
{
newDim = ( (Integer) login.getDimension() >= 0 ? -1 : 0 );
} else
{
newDim = worldName = ( "minecraft:overworld".equals( (String) login.getDimension() ) ) ? "minecraft:the_nether" : "minecraft:overworld";
}
user.unsafe().sendPacket( new Respawn( newDim, worldName, login.getSeed(), login.getDifficulty(), login.getGameMode(), login.getPreviousGameMode(), login.getLevelType(), login.isDebug(), login.isFlat(), false ) );
}
user.setServerEntityId( login.getEntityId() );
user.unsafe().sendPacket( new Respawn( login.getDimension(), login.getDifficulty(), login.getGameMode(), login.getLevelType() ) );
user.unsafe().sendPacket( new Respawn( login.getDimension(), login.getWorldName(), login.getSeed(), login.getDifficulty(), login.getGameMode(), login.getPreviousGameMode(), login.getLevelType(), login.isDebug(), login.isFlat(), false ) );
if ( user.getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_1_14 )
{
user.unsafe().sendPacket( new ViewDistance( login.getViewDistance() ) );

View File

@ -10,6 +10,7 @@ import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.util.internal.PlatformDependent;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@ -51,6 +52,7 @@ import net.md_5.bungee.protocol.MinecraftDecoder;
import net.md_5.bungee.protocol.MinecraftEncoder;
import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.packet.Chat;
import net.md_5.bungee.protocol.packet.ClientSettings;
import net.md_5.bungee.protocol.packet.Kick;
@ -82,7 +84,7 @@ public final class UserConnection implements ProxiedPlayer
private ServerConnection server;
@Getter
@Setter
private int dimension;
private Object dimension;
@Getter
@Setter
private boolean dimensionChange = true;
@ -91,9 +93,6 @@ public final class UserConnection implements ProxiedPlayer
/*========================================================================*/
@Getter
@Setter
private long sentPingTime;
@Getter
@Setter
private int ping = 100;
@Getter
@Setter
@ -275,7 +274,7 @@ public final class UserConnection implements ProxiedPlayer
Preconditions.checkNotNull( request, "request" );
final Callback<ServerConnectRequest.Result> callback = request.getCallback();
ServerConnectEvent event = new ServerConnectEvent( this, request.getTarget(), request.getReason() );
ServerConnectEvent event = new ServerConnectEvent( this, request.getTarget(), request.getReason(), request );
if ( bungee.getPluginManager().callEvent( event ).isCancelled() )
{
if ( callback != null )
@ -358,13 +357,13 @@ public final class UserConnection implements ProxiedPlayer
}
};
Bootstrap b = new Bootstrap()
.channel( PipelineUtils.getChannel() )
.channel( PipelineUtils.getChannel( target.getAddress() ) )
.group( ch.getHandle().eventLoop() )
.handler( initializer )
.option( ChannelOption.CONNECT_TIMEOUT_MILLIS, request.getConnectTimeout() )
.remoteAddress( target.getAddress() );
// Windows is bugged, multi homed users will just have to live with random connecting IPs
if ( getPendingConnection().getListener().isSetLocalAddress() && !PlatformDependent.isWindows() )
if ( getPendingConnection().getListener().isSetLocalAddress() && !PlatformDependent.isWindows() && getPendingConnection().getListener().getSocketAddress() instanceof InetSocketAddress )
{
b.localAddress( getPendingConnection().getListener().getHost().getHostString(), 0 );
}
@ -398,7 +397,7 @@ public final class UserConnection implements ProxiedPlayer
getName(), BaseComponent.toLegacyText( reason )
} );
ch.delayedClose( new Kick( ComponentSerializer.toString( reason ) ) );
ch.close( new Kick( ComponentSerializer.toString( reason ) ) );
if ( server != null )
{
@ -453,10 +452,20 @@ public final class UserConnection implements ProxiedPlayer
// transform score components
message = ChatComponentTransformer.getInstance().transform( this, message );
// Action bar doesn't display the new JSON formattings, legacy works - send it using this for now
if ( position == ChatMessageType.ACTION_BAR )
{
sendMessage( position, ComponentSerializer.toString( new TextComponent( BaseComponent.toLegacyText( message ) ) ) );
// Versions older than 1.11 cannot send the Action bar with the new JSON formattings
// Fix by converting to a legacy message, see https://bugs.mojang.com/browse/MC-119145
if ( getPendingConnection().getVersion() <= ProtocolConstants.MINECRAFT_1_10 )
{
sendMessage( position, ComponentSerializer.toString( new TextComponent( BaseComponent.toLegacyText( message ) ) ) );
} else
{
net.md_5.bungee.protocol.packet.Title title = new net.md_5.bungee.protocol.packet.Title();
title.setAction( net.md_5.bungee.protocol.packet.Title.Action.ACTIONBAR );
title.setText( ComponentSerializer.toString( message ) );
unsafe.sendPacket( title );
}
} else
{
sendMessage( position, ComponentSerializer.toString( message ) );
@ -486,6 +495,12 @@ public final class UserConnection implements ProxiedPlayer
@Override
public InetSocketAddress getAddress()
{
return (InetSocketAddress) getSocketAddress();
}
@Override
public SocketAddress getSocketAddress()
{
return ch.getRemoteAddress();
}

View File

@ -1,6 +1,5 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
@ -18,16 +17,16 @@ public class CommandIP extends PlayerCommand
{
if ( args.length < 1 )
{
sender.sendMessage( ChatColor.RED + "Please follow this command by a user name" );
sender.sendMessage( ProxyServer.getInstance().getTranslation( "username_needed" ) );
return;
}
ProxiedPlayer user = ProxyServer.getInstance().getPlayer( args[0] );
if ( user == null )
{
sender.sendMessage( ChatColor.RED + "That user is not online" );
sender.sendMessage( ProxyServer.getInstance().getTranslation( "user_not_online" ) );
} else
{
sender.sendMessage( ChatColor.BLUE + "IP of " + args[0] + " is " + user.getAddress() );
sender.sendMessage( ProxyServer.getInstance().getTranslation( "command_ip", args[0], user.getSocketAddress() ) );
}
}
}

View File

@ -3,7 +3,6 @@ package net.md_5.bungee.command;
import java.util.HashSet;
import java.util.Set;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Command;
@ -24,11 +23,11 @@ public class CommandPerms extends Command
{
permissions.addAll( ProxyServer.getInstance().getConfigurationAdapter().getPermissions( group ) );
}
sender.sendMessage( ChatColor.GOLD + "You have the following groups: " + Util.csv( sender.getGroups() ) );
sender.sendMessage( ProxyServer.getInstance().getTranslation( "command_perms_groups", Util.csv( sender.getGroups() ) ) );
for ( String permission : permissions )
{
sender.sendMessage( ChatColor.BLUE + "- " + permission );
sender.sendMessage( ProxyServer.getInstance().getTranslation( "command_perms_permission", permission ) );
}
}
}

View File

@ -52,8 +52,11 @@ public class Configuration implements ProxyConfig
*/
private boolean logCommands;
private boolean logPings = true;
private int remotePingCache = -1;
private int playerLimit = -1;
private Collection<String> disabledCommands;
private int serverConnectTimeout = 5000;
private int remotePingTimeout = 5000;
private int throttle = 4000;
private int throttleLimit = 3;
private boolean ipForward;
@ -85,7 +88,10 @@ public class Configuration implements ProxyConfig
onlineMode = adapter.getBoolean( "online_mode", onlineMode );
logCommands = adapter.getBoolean( "log_commands", logCommands );
logPings = adapter.getBoolean( "log_pings", logPings );
remotePingCache = adapter.getInt( "remote_ping_cache", remotePingCache );
playerLimit = adapter.getInt( "player_limit", playerLimit );
serverConnectTimeout = adapter.getInt( "server_connect_timeout", serverConnectTimeout );
remotePingTimeout = adapter.getInt( "remote_ping_timeout", remotePingTimeout );
throttle = adapter.getInt( "connection_throttle", throttle );
throttleLimit = adapter.getInt( "connection_throttle_limit", throttleLimit );
ipForward = adapter.getBoolean( "ip_forward", ipForward );

View File

@ -1,12 +1,15 @@
package net.md_5.bungee.conf;
import com.google.common.base.Charsets;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -173,7 +176,7 @@ public class YamlConfig implements ConfigurationAdapter
{
try
{
try ( FileWriter wr = new FileWriter( file ) )
try ( Writer wr = new OutputStreamWriter( new FileOutputStream( file ), Charsets.UTF_8 ) )
{
yaml.dump( config, wr );
}
@ -215,7 +218,7 @@ public class YamlConfig implements ConfigurationAdapter
String addr = get( "address", "localhost:25565", val );
String motd = ChatColor.translateAlternateColorCodes( '&', get( "motd", "&1Just another BungeeCord - Forced Host", val ) );
boolean restricted = get( "restricted", false, val );
InetSocketAddress address = Util.getAddr( addr );
SocketAddress address = Util.getAddr( addr );
ServerInfo info = ProxyServer.getInstance().constructServerInfo( name, address, motd, restricted );
ret.put( name, info );
}
@ -246,7 +249,7 @@ public class YamlConfig implements ConfigurationAdapter
boolean forceDefault = get( "force_default_server", false, val );
String host = get( "host", "0.0.0.0:25577", val );
int tabListSize = get( "tab_size", 60, val );
InetSocketAddress address = Util.getAddr( host );
SocketAddress address = Util.getAddr( host );
Map<String, String> forced = new CaseInsensitiveMap<>( get( "forced_hosts", forcedDef, val ) );
String tabListName = get( "tab_list", "GLOBAL_PING", val );
DefaultTabList value = DefaultTabList.valueOf( tabListName.toUpperCase( Locale.ROOT ) );

View File

@ -15,12 +15,15 @@ import com.mojang.brigadier.tree.LiteralCommandNode;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.unix.DomainSocketAddress;
import java.io.DataInput;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.ServerConnection;
import net.md_5.bungee.ServerConnection.KeepAliveData;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ProxyServer;
@ -122,8 +125,11 @@ public class DownstreamBridge extends PacketHandler
@Override
public void handle(KeepAlive alive) throws Exception
{
server.setSentPingId( alive.getRandomId() );
con.setSentPingTime( System.currentTimeMillis() );
int timeout = bungee.getConfig().getTimeout();
if ( timeout <= 0 || server.getKeepAlives().size() < timeout / 50 ) // Some people disable timeout, otherwise allow a theoretical maximum of 1 keepalive per tick
{
server.getKeepAlives().add( new KeepAliveData( alive.getRandomId(), System.currentTimeMillis() ) );
}
}
@Override
@ -359,8 +365,15 @@ public class DownstreamBridge extends PacketHandler
if ( subChannel.equals( "IP" ) )
{
out.writeUTF( "IP" );
out.writeUTF( con.getAddress().getHostString() );
out.writeInt( con.getAddress().getPort() );
if ( con.getSocketAddress() instanceof InetSocketAddress )
{
out.writeUTF( con.getAddress().getHostString() );
out.writeInt( con.getAddress().getPort() );
} else
{
out.writeUTF( "unix://" + ( (DomainSocketAddress) con.getSocketAddress() ).path() );
out.writeInt( 0 );
}
}
if ( subChannel.equals( "PlayerCount" ) )
{

View File

@ -5,6 +5,7 @@ import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.List;
@ -117,6 +118,11 @@ public class InitialHandler extends PacketHandler implements PendingConnection
HANDSHAKE, STATUS, PING, USERNAME, ENCRYPT, FINISHED;
}
private boolean canSendKickMessage()
{
return thisState == State.USERNAME || thisState == State.ENCRYPT || thisState == State.FINISHED;
}
@Override
public void connected(ChannelWrapper channel) throws Exception
{
@ -126,7 +132,13 @@ public class InitialHandler extends PacketHandler implements PendingConnection
@Override
public void exception(Throwable t) throws Exception
{
disconnect( ChatColor.RED + Util.exception( t ) );
if ( canSendKickMessage() )
{
disconnect( ChatColor.RED + Util.exception( t ) );
} else
{
ch.close();
}
}
@Override
@ -207,6 +219,15 @@ public class InitialHandler extends PacketHandler implements PendingConnection
return pos == -1 ? str : str.substring( 0, pos );
}
private ServerPing getPingInfo(String motd, int protocol)
{
return new ServerPing(
new ServerPing.Protocol( bungee.getName() + " " + bungee.getGameVersion(), protocol ),
new ServerPing.Players( listener.getMaxPlayers(), bungee.getOnlineCount(), null ),
motd, BungeeCord.getInstance().config.getFaviconObject()
);
}
@Override
public void handle(StatusRequest statusRequest) throws Exception
{
@ -214,6 +235,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
ServerInfo forced = AbstractReconnectHandler.getForcedHost( this );
final String motd = ( forced != null ) ? forced.getMotd() : listener.getMotd();
final int protocol = ( ProtocolConstants.SUPPORTED_VERSION_IDS.contains( handshake.getProtocolVersion() ) ) ? handshake.getProtocolVersion() : bungee.getProtocolVersion();
Callback<ServerPing> pingBack = new Callback<ServerPing>()
{
@ -222,8 +244,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
{
if ( error != null )
{
result = new ServerPing();
result.setDescription( bungee.getTranslation( "ping_cannot_connect" ) );
result = getPingInfo( bungee.getTranslation( "ping_cannot_connect" ), protocol );
bungee.getLogger().log( Level.WARNING, "Error pinging remote server", error );
}
@ -236,7 +257,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
unsafe.sendPacket( new StatusResponse( gson.toJson( pingResult.getResponse() ) ) );
if ( bungee.getConnectionThrottle() != null )
{
bungee.getConnectionThrottle().unthrottle( getAddress().getAddress() );
bungee.getConnectionThrottle().unthrottle( getSocketAddress() );
}
}
};
@ -250,12 +271,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
( (BungeeServerInfo) forced ).ping( pingBack, handshake.getProtocolVersion() );
} else
{
int protocol = ( ProtocolConstants.SUPPORTED_VERSION_IDS.contains( handshake.getProtocolVersion() ) ) ? handshake.getProtocolVersion() : bungee.getProtocolVersion();
pingBack.done( new ServerPing(
new ServerPing.Protocol( bungee.getName() + " " + bungee.getGameVersion(), protocol ),
new ServerPing.Players( listener.getMaxPlayers(), bungee.getOnlineCount(), null ),
motd, BungeeCord.getInstance().config.getFaviconObject() ),
null );
pingBack.done( getPingInfo( motd, protocol ), null );
}
thisState = State.PING;
@ -295,10 +311,6 @@ public class InitialHandler extends PacketHandler implements PendingConnection
}
this.virtualHost = InetSocketAddress.createUnresolved( handshake.getHost(), handshake.getPort() );
if ( bungee.getConfig().isLogPings() )
{
bungee.getLogger().log( Level.INFO, "{0} has connected", this );
}
bungee.getPluginManager().callEvent( new PlayerHandshakeEvent( InitialHandler.this, handshake ) );
@ -306,15 +318,16 @@ public class InitialHandler extends PacketHandler implements PendingConnection
{
case 1:
// Ping
if ( bungee.getConfig().isLogPings() )
{
bungee.getLogger().log( Level.INFO, "{0} has pinged", this );
}
thisState = State.STATUS;
ch.setProtocol( Protocol.STATUS );
break;
case 2:
// Login
if ( !bungee.getConfig().isLogPings() )
{
bungee.getLogger().log( Level.INFO, "{0} has connected", this );
}
bungee.getLogger().log( Level.INFO, "{0} has connected", this );
thisState = State.USERNAME;
ch.setProtocol( Protocol.LOGIN );
@ -331,7 +344,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
}
break;
default:
throw new IllegalArgumentException( "Cannot request protocol " + handshake.getRequestedProtocol() );
throw new QuietException( "Cannot request protocol " + handshake.getRequestedProtocol() );
}
}
@ -421,7 +434,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
}
String encodedHash = URLEncoder.encode( new BigInteger( sha.digest() ).toString( 16 ), "UTF-8" );
String preventProxy = ( ( BungeeCord.getInstance().config.isPreventProxyConnections() ) ? "&ip=" + URLEncoder.encode( getAddress().getAddress().getHostAddress(), "UTF-8" ) : "" );
String preventProxy = ( BungeeCord.getInstance().config.isPreventProxyConnections() && getSocketAddress() instanceof InetSocketAddress ) ? "&ip=" + URLEncoder.encode( getAddress().getAddress().getHostAddress(), "UTF-8" ) : "";
String authURL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" + encName + "&serverId=" + encodedHash + preventProxy;
Callback<String> handler = new Callback<String>()
@ -516,7 +529,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
userCon.setCompressionThreshold( BungeeCord.getInstance().config.getCompressionThreshold() );
userCon.init();
unsafe.sendPacket( new LoginSuccess( getUniqueId().toString(), getName() ) ); // With dashes in between
unsafe.sendPacket( new LoginSuccess( getUniqueId(), getName() ) );
ch.setProtocol( Protocol.GAME );
ch.getHandle().pipeline().get( HandlerBoss.class ).setHandler( new UpstreamBridge( bungee, userCon ) );
@ -556,13 +569,19 @@ public class InitialHandler extends PacketHandler implements PendingConnection
@Override
public void disconnect(String reason)
{
disconnect( TextComponent.fromLegacyText( reason ) );
if ( canSendKickMessage() )
{
disconnect( TextComponent.fromLegacyText( reason ) );
} else
{
ch.close();
}
}
@Override
public void disconnect(final BaseComponent... reason)
{
if ( thisState != State.STATUS && thisState != State.PING )
if ( canSendKickMessage() )
{
ch.delayedClose( new Kick( ComponentSerializer.toString( reason ) ) );
} else
@ -594,6 +613,12 @@ public class InitialHandler extends PacketHandler implements PendingConnection
@Override
public InetSocketAddress getAddress()
{
return (InetSocketAddress) getSocketAddress();
}
@Override
public SocketAddress getSocketAddress()
{
return ch.getRemoteAddress();
}
@ -628,7 +653,20 @@ public class InitialHandler extends PacketHandler implements PendingConnection
@Override
public String toString()
{
return "[" + ( ( getName() != null ) ? getName() : getAddress() ) + "] <-> InitialHandler";
StringBuilder sb = new StringBuilder();
sb.append( '[' );
String currentName = getName();
if ( currentName != null )
{
sb.append( currentName );
sb.append( ',' );
}
sb.append( getSocketAddress() );
sb.append( "] <-> InitialHandler" );
return sb.toString();
}
@Override

View File

@ -4,6 +4,7 @@ import com.google.gson.Gson;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.BungeeServerInfo;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ServerPing;
@ -65,7 +66,9 @@ public class PingHandler extends PacketHandler
public void handle(StatusResponse statusResponse) throws Exception
{
Gson gson = BungeeCord.getInstance().gson;
callback.done( gson.fromJson( statusResponse.getResponse(), ServerPing.class ), null );
ServerPing serverPing = gson.fromJson( statusResponse.getResponse(), ServerPing.class );
( (BungeeServerInfo) target ).cachePing( serverPing );
callback.done( serverPing, null );
channel.close();
}

View File

@ -9,6 +9,7 @@ import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.ServerConnection.KeepAliveData;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ProxyServer;
@ -121,9 +122,12 @@ public class UpstreamBridge extends PacketHandler
@Override
public void handle(KeepAlive alive) throws Exception
{
if ( alive.getRandomId() == con.getServer().getSentPingId() )
KeepAliveData keepAliveData = con.getServer().getKeepAlives().peek();
if ( keepAliveData != null && alive.getRandomId() == keepAliveData.getId() )
{
int newPing = (int) ( System.currentTimeMillis() - con.getSentPingTime() );
Preconditions.checkState( keepAliveData == con.getServer().getKeepAlives().poll(), "keepalive queue mismatch" );
int newPing = (int) ( System.currentTimeMillis() - keepAliveData.getTime() );
con.getTabListHandler().onPingChange( newPing );
con.setPing( newPing );
} else

View File

@ -1,15 +1,15 @@
package net.md_5.bungee.entitymap;
import com.flowpowered.nbt.stream.NBTInputStream;
import com.google.common.base.Throwables;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import java.io.IOException;
import java.io.DataInputStream;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.ProtocolConstants;
import se.llbit.nbt.NamedTag;
import se.llbit.nbt.Tag;
/**
* Class to rewrite integers within packets.
@ -57,6 +57,12 @@ public abstract class EntityMap
case ProtocolConstants.MINECRAFT_1_14_3:
case ProtocolConstants.MINECRAFT_1_14_4:
return EntityMap_1_14.INSTANCE;
case ProtocolConstants.MINECRAFT_1_15:
case ProtocolConstants.MINECRAFT_1_15_1:
case ProtocolConstants.MINECRAFT_1_15_2:
return EntityMap_1_15.INSTANCE;
case ProtocolConstants.MINECRAFT_1_16:
return EntityMap_1_16.INSTANCE;
}
throw new RuntimeException( "Version " + version + " has no entity map" );
}
@ -249,12 +255,10 @@ public abstract class EntityMap
DefinedPacket.readVarInt( packet );
break;
case 13:
try
Tag tag = NamedTag.read( new DataInputStream( new ByteBufInputStream( packet ) ) );
if ( tag.isError() )
{
new NBTInputStream( new ByteBufInputStream( packet ), false ).readTag();
} catch ( IOException ex )
{
throw Throwables.propagate( ex );
throw new RuntimeException( tag.error() );
}
break;
case 15:
@ -297,12 +301,10 @@ public abstract class EntityMap
{
packet.readerIndex( position );
try
Tag tag = NamedTag.read( new DataInputStream( new ByteBufInputStream( packet ) ) );
if ( tag.isError() )
{
new NBTInputStream( new ByteBufInputStream( packet ), false ).readTag();
} catch ( IOException ex )
{
throw Throwables.propagate( ex );
throw new RuntimeException( tag.error() );
}
}
}

View File

@ -149,6 +149,7 @@ class EntityMap_1_10 extends EntityMap
case 0x39 /* EntityMetadata : PacketPlayOutEntityMetadata */:
DefinedPacket.readVarInt( packet ); // Entity ID
rewriteMetaVarInt( packet, oldId + 1, newId + 1, 6 ); // fishing hook
rewriteMetaVarInt( packet, oldId, newId, 13 ); // guardian beam
break;
}
packet.readerIndex( readerIndex );

View File

@ -150,6 +150,7 @@ class EntityMap_1_11 extends EntityMap
DefinedPacket.readVarInt( packet ); // Entity ID
rewriteMetaVarInt( packet, oldId + 1, newId + 1, 6 ); // fishing hook
rewriteMetaVarInt( packet, oldId, newId, 7 ); // fireworks (et al)
rewriteMetaVarInt( packet, oldId, newId, 13 ); // guardian beam
break;
}
packet.readerIndex( readerIndex );

View File

@ -150,6 +150,7 @@ class EntityMap_1_12 extends EntityMap
DefinedPacket.readVarInt( packet ); // Entity ID
rewriteMetaVarInt( packet, oldId + 1, newId + 1, 6 ); // fishing hook
rewriteMetaVarInt( packet, oldId, newId, 7 ); // fireworks (et al)
rewriteMetaVarInt( packet, oldId, newId, 13 ); // guardian beam
break;
}
packet.readerIndex( readerIndex );

View File

@ -150,6 +150,7 @@ class EntityMap_1_12_1 extends EntityMap
DefinedPacket.readVarInt( packet ); // Entity ID
rewriteMetaVarInt( packet, oldId + 1, newId + 1, 6 ); // fishing hook
rewriteMetaVarInt( packet, oldId, newId, 7 ); // fireworks (et al)
rewriteMetaVarInt( packet, oldId, newId, 13 ); // guardian beam
break;
}
packet.readerIndex( readerIndex );

View File

@ -150,6 +150,7 @@ class EntityMap_1_13 extends EntityMap
DefinedPacket.readVarInt( packet ); // Entity ID
rewriteMetaVarInt( packet, oldId + 1, newId + 1, 6, protocolVersion ); // fishing hook
rewriteMetaVarInt( packet, oldId, newId, 7, protocolVersion ); // fireworks (et al)
rewriteMetaVarInt( packet, oldId, newId, 13, protocolVersion ); // guardian beam
break;
}
packet.readerIndex( readerIndex );

View File

@ -149,6 +149,7 @@ class EntityMap_1_14 extends EntityMap
DefinedPacket.readVarInt( packet ); // Entity ID
rewriteMetaVarInt( packet, oldId + 1, newId + 1, 7, protocolVersion ); // fishing hook
rewriteMetaVarInt( packet, oldId, newId, 8, protocolVersion ); // fireworks (et al)
rewriteMetaVarInt( packet, oldId, newId, 15, protocolVersion ); // guardian beam
break;
case 0x50 /* Entity Sound Effect : PacketPlayOutEntitySound */:
DefinedPacket.readVarInt( packet );

Some files were not shown because too many files have changed in this diff Show More