442 lines
13 KiB
Java
442 lines
13 KiB
Java
package net.md_5.bungee;
|
|
|
|
import com.google.common.base.Preconditions;
|
|
import io.netty.bootstrap.Bootstrap;
|
|
import io.netty.channel.*;
|
|
import io.netty.channel.socket.nio.NioSocketChannel;
|
|
import io.netty.util.concurrent.GenericFutureListener;
|
|
import io.netty.util.internal.PlatformDependent;
|
|
|
|
import java.beans.ConstructorProperties;
|
|
import java.net.InetSocketAddress;
|
|
import java.net.SocketAddress;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.*;
|
|
import java.util.logging.Level;
|
|
import net.md_5.bungee.api.ChatColor;
|
|
import net.md_5.bungee.api.ProxyServer;
|
|
import net.md_5.bungee.api.config.ServerInfo;
|
|
import net.md_5.bungee.api.connection.Connection;
|
|
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
|
import net.md_5.bungee.api.event.PermissionCheckEvent;
|
|
import net.md_5.bungee.api.event.ServerConnectEvent;
|
|
import net.md_5.bungee.api.score.Scoreboard;
|
|
import net.md_5.bungee.api.tab.TabListHandler;
|
|
import net.md_5.bungee.connection.InitialHandler;
|
|
import net.md_5.bungee.eaglercraft.RedirectServerInfo;
|
|
import net.md_5.bungee.netty.ChannelWrapper;
|
|
import net.md_5.bungee.netty.HandlerBoss;
|
|
import net.md_5.bungee.netty.PacketWrapper;
|
|
import net.md_5.bungee.netty.PipelineUtils;
|
|
import net.md_5.bungee.protocol.packet.DefinedPacket;
|
|
import net.md_5.bungee.protocol.packet.Packet3Chat;
|
|
import net.md_5.bungee.protocol.packet.PacketCCSettings;
|
|
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
|
|
import net.md_5.bungee.protocol.packet.PacketFFKick;
|
|
import net.md_5.bungee.util.CaseInsensitiveSet;
|
|
|
|
public final class UserConnection implements ProxiedPlayer
|
|
{
|
|
|
|
/*========================================================================*/
|
|
private final ProxyServer bungee;
|
|
private final ChannelWrapper ch;
|
|
private final String name;
|
|
private final InitialHandler pendingConnection;
|
|
/*========================================================================*/
|
|
private ServerConnection server;
|
|
private Object switchMutex = new Object();
|
|
private Collection<ServerInfo> pendingConnects = new HashSet<>();
|
|
/*========================================================================*/
|
|
private TabListHandler tabList;
|
|
private int sentPingId;
|
|
|
|
private long sentPingTime;
|
|
private int ping = 100;
|
|
private ServerInfo reconnectServer;
|
|
/*========================================================================*/
|
|
private Collection<String> groups = new CaseInsensitiveSet();
|
|
private Collection<String> permissions = new CaseInsensitiveSet();
|
|
/*========================================================================*/
|
|
|
|
|
|
private int serverEntityId;
|
|
private PacketCCSettings settings;
|
|
|
|
private Scoreboard serverSentScoreboard = new Scoreboard();
|
|
/*========================================================================*/
|
|
|
|
private String displayName;
|
|
/*========================================================================*/
|
|
|
|
private int clientEntityId;
|
|
|
|
private final Map<String, Object> attachment = new WeakHashMap();
|
|
|
|
public void setServer(ServerConnection server) {
|
|
this.server = server;
|
|
}
|
|
|
|
public void setSettings(PacketCCSettings settings){
|
|
this.settings = settings;
|
|
}
|
|
|
|
public void setServerEntityId(int serverEntityId) {
|
|
this.serverEntityId = serverEntityId;
|
|
}
|
|
|
|
public void setSentPingId(int sentPingId) {
|
|
this.sentPingId = sentPingId;
|
|
}
|
|
|
|
public void setSentPingTime(long sentPingTime) {
|
|
this.sentPingTime = sentPingTime;
|
|
}
|
|
|
|
public void setPing(int ping) {
|
|
this.ping = ping;
|
|
}
|
|
|
|
@Override
|
|
public void setReconnectServer(ServerInfo reconnectServer) {
|
|
this.reconnectServer = reconnectServer;
|
|
}
|
|
|
|
public void setClientEntityId(int clientEntityId) {
|
|
this.clientEntityId = clientEntityId;
|
|
}
|
|
|
|
@Override
|
|
public String getName() {
|
|
return name;
|
|
}
|
|
|
|
@Override
|
|
public InitialHandler getPendingConnection() {
|
|
return pendingConnection;
|
|
}
|
|
|
|
@Override
|
|
public TabListHandler getTabList() {
|
|
return tabList;
|
|
}
|
|
|
|
public Collection<ServerInfo> getPendingConnects() {
|
|
return pendingConnects;
|
|
}
|
|
|
|
public Object getSwitchMutex() {
|
|
return switchMutex;
|
|
}
|
|
|
|
@Override
|
|
public ServerConnection getServer() {
|
|
return server;
|
|
}
|
|
|
|
public int getSentPingId() {
|
|
return sentPingId;
|
|
}
|
|
|
|
public long getSentPingTime() {
|
|
return sentPingTime;
|
|
}
|
|
|
|
@Override
|
|
public int getPing() {
|
|
return ping;
|
|
}
|
|
|
|
@Override
|
|
public ServerInfo getReconnectServer() {
|
|
return reconnectServer;
|
|
}
|
|
|
|
public Collection<String> getPermissions() {
|
|
return permissions;
|
|
}
|
|
|
|
public int getClientEntityId() {
|
|
return clientEntityId;
|
|
}
|
|
|
|
public int getServerEntityId() {
|
|
return serverEntityId;
|
|
}
|
|
|
|
public PacketCCSettings getSettings() {
|
|
return settings;
|
|
}
|
|
|
|
public Scoreboard getServerSentScoreboard() {
|
|
return serverSentScoreboard;
|
|
}
|
|
|
|
@Override
|
|
public String getDisplayName() {
|
|
return displayName;
|
|
}
|
|
|
|
private final Connection.Unsafe unsafe;
|
|
|
|
@ConstructorProperties({ "bungee", "ch", "name", "pendingConnection" })
|
|
public UserConnection(final ProxyServer bungee, final ChannelWrapper ch, final String name, final InitialHandler pendingConnection) {
|
|
this.switchMutex = new Object();
|
|
this.pendingConnects = new HashSet<ServerInfo>();
|
|
this.ping = 100;
|
|
this.groups = (Collection<String>) new CaseInsensitiveSet();
|
|
this.permissions = (Collection<String>) new CaseInsensitiveSet();
|
|
this.serverSentScoreboard = new Scoreboard();
|
|
this.unsafe = new Connection.Unsafe() {
|
|
@Override
|
|
public void sendPacket(final DefinedPacket packet) {
|
|
UserConnection.this.ch.write(packet);
|
|
}
|
|
};
|
|
if (bungee == null) {
|
|
throw new NullPointerException("bungee");
|
|
}
|
|
if (ch == null) {
|
|
throw new NullPointerException("ch");
|
|
}
|
|
if (name == null) {
|
|
throw new NullPointerException("name");
|
|
}
|
|
this.bungee = bungee;
|
|
this.ch = ch;
|
|
this.name = name;
|
|
this.pendingConnection = pendingConnection;
|
|
}
|
|
|
|
public void init()
|
|
{
|
|
this.displayName = name;
|
|
try
|
|
{
|
|
this.tabList = getPendingConnection().getListener().getTabList().getDeclaredConstructor().newInstance();
|
|
} catch ( ReflectiveOperationException ex )
|
|
{
|
|
throw new RuntimeException( ex );
|
|
}
|
|
this.tabList.init( this );
|
|
|
|
Collection<String> g = bungee.getConfigurationAdapter().getGroups( name );
|
|
for ( String s : g )
|
|
{
|
|
addGroups( s );
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setTabList(TabListHandler tabList)
|
|
{
|
|
tabList.init( this );
|
|
this.tabList = tabList;
|
|
}
|
|
|
|
public void sendPacket(final byte[] b) {
|
|
this.ch.write(b);
|
|
}
|
|
|
|
|
|
@Deprecated
|
|
public boolean isActive()
|
|
{
|
|
return !ch.isClosed();
|
|
}
|
|
|
|
@Override
|
|
public void setDisplayName(String name)
|
|
{
|
|
Preconditions.checkNotNull( name, "displayName" );
|
|
Preconditions.checkArgument( name.length() <= 16, "Display name cannot be longer than 16 characters" );
|
|
getTabList().onDisconnect();
|
|
displayName = name;
|
|
getTabList().onConnect();
|
|
}
|
|
|
|
@Override
|
|
public void connect(ServerInfo target)
|
|
{
|
|
connect( target, false );
|
|
}
|
|
|
|
void sendDimensionSwitch()
|
|
{
|
|
unsafe().sendPacket( PacketConstants.DIM1_SWITCH );
|
|
unsafe().sendPacket( PacketConstants.DIM2_SWITCH );
|
|
}
|
|
|
|
public void connectNow(ServerInfo target)
|
|
{
|
|
sendDimensionSwitch();
|
|
connect( target );
|
|
}
|
|
|
|
public void connect(final ServerInfo info, final boolean retry) {
|
|
if(info instanceof RedirectServerInfo) {
|
|
sendData("EAG|Reconnect", ((RedirectServerInfo)info).getRedirect().getBytes(StandardCharsets.UTF_8));
|
|
return;
|
|
}
|
|
final ServerConnectEvent event = new ServerConnectEvent(this, info);
|
|
if (this.bungee.getPluginManager().callEvent(event).isCancelled()) {
|
|
return;
|
|
}
|
|
final BungeeServerInfo target = (BungeeServerInfo) event.getTarget();
|
|
if (this.getServer() != null && this.getServer().getInfo() != null && Objects.equals(this.getServer().getInfo(), target)) {
|
|
//this.sendMessage(ChatColor.RED + "Cannot connect to server you are already on!");
|
|
return;
|
|
}
|
|
if (this.pendingConnects.contains(target)) {
|
|
this.sendMessage(ChatColor.RED + "Already connecting to this server!");
|
|
return;
|
|
}
|
|
this.pendingConnects.add(target);
|
|
final ChannelInitializer initializer = new ChannelInitializer() {
|
|
protected void initChannel(final Channel ch) throws Exception {
|
|
PipelineUtils.BASE.initChannel(ch);
|
|
((HandlerBoss) ch.pipeline().get((Class) HandlerBoss.class)).setHandler(new ServerConnector(UserConnection.this.bungee, UserConnection.this, target));
|
|
}
|
|
};
|
|
final ChannelFutureListener listener = (ChannelFutureListener) new ChannelFutureListener() {
|
|
public void operationComplete(final ChannelFuture future) throws Exception {
|
|
if (!future.isSuccess()) {
|
|
future.channel().close();
|
|
UserConnection.this.pendingConnects.remove(target);
|
|
final ServerInfo def = ProxyServer.getInstance().getServers().get(UserConnection.this.getPendingConnection().getListener().getFallbackServer());
|
|
if ((retry & target != def) && ((UserConnection.this.getServer() == null || UserConnection.this.getServer().getInfo() == null) || def != UserConnection.this.getServer().getInfo())) {
|
|
UserConnection.this.sendMessage(UserConnection.this.bungee.getTranslation("fallback_lobby"));
|
|
UserConnection.this.connect(def, false);
|
|
} else if (UserConnection.this.getServer() == null || UserConnection.this.getServer().getInfo() == null) {
|
|
UserConnection.this.disconnect(UserConnection.this.bungee.getTranslation("fallback_kick") + future.cause().getClass().getName());
|
|
} else {
|
|
UserConnection.this.sendMessage(UserConnection.this.bungee.getTranslation("fallback_kick") + future.cause().getClass().getName());
|
|
}
|
|
}
|
|
}
|
|
};
|
|
final Bootstrap b = ((Bootstrap) ((Bootstrap) ((Bootstrap) ((Bootstrap) new Bootstrap().channel((Class) NioSocketChannel.class)).group((EventLoopGroup) BungeeCord.getInstance().eventLoops)).handler((ChannelHandler) initializer))
|
|
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Integer.valueOf(5000))).remoteAddress((SocketAddress) target.getAddress());
|
|
if (!PlatformDependent.isWindows()) {
|
|
b.localAddress(this.getPendingConnection().getListener().getHost().getHostString(), 0);
|
|
}
|
|
b.connect().addListener((GenericFutureListener) listener);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void disconnect(String reason)
|
|
{
|
|
if ( ch.getHandle().isActive() )
|
|
{
|
|
bungee.getLogger().log( Level.INFO, "[" + getName() + "] disconnected with: " + reason );
|
|
unsafe().sendPacket( new PacketFFKick( reason ) );
|
|
ch.close();
|
|
if ( server != null )
|
|
{
|
|
server.disconnect( "Quitting" );
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void chat(String message)
|
|
{
|
|
Preconditions.checkState( server != null, "Not connected to server" );
|
|
server.getCh().write( new Packet3Chat( message ) );
|
|
}
|
|
|
|
@Override
|
|
public void sendMessage(String message)
|
|
{
|
|
this.unsafe().sendPacket(new Packet3Chat(message));
|
|
}
|
|
|
|
@Override
|
|
public void sendMessages(String... messages)
|
|
{
|
|
for ( String message : messages )
|
|
{
|
|
sendMessage( message );
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void sendData(String channel, byte[] data)
|
|
{
|
|
unsafe().sendPacket( new PacketFAPluginMessage( channel, data ) );
|
|
}
|
|
|
|
@Override
|
|
public InetSocketAddress getAddress()
|
|
{
|
|
return (InetSocketAddress) ch.getHandle().remoteAddress();
|
|
}
|
|
|
|
@Override
|
|
public Collection<String> getGroups()
|
|
{
|
|
return Collections.unmodifiableCollection( groups );
|
|
}
|
|
|
|
@Override
|
|
public void addGroups(String... groups)
|
|
{
|
|
for ( String group : groups )
|
|
{
|
|
this.groups.add( group );
|
|
for ( String permission : bungee.getConfigurationAdapter().getPermissions( group ) )
|
|
{
|
|
setPermission( permission, true );
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void removeGroups(String... groups)
|
|
{
|
|
for ( String group : groups )
|
|
{
|
|
this.groups.remove( group );
|
|
for ( String permission : bungee.getConfigurationAdapter().getPermissions( group ) )
|
|
{
|
|
setPermission( permission, false );
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean hasPermission(String permission)
|
|
{
|
|
return bungee.getPluginManager().callEvent( new PermissionCheckEvent( this, permission, permissions.contains( permission ) ) ).hasPermission();
|
|
}
|
|
|
|
@Override
|
|
public void setPermission(String permission, boolean value)
|
|
{
|
|
if ( value )
|
|
{
|
|
permissions.add( permission );
|
|
} else
|
|
{
|
|
permissions.remove( permission );
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Map<String, Object> getAttachment() { // fix this (maybe)
|
|
return attachment;
|
|
}
|
|
|
|
@Override
|
|
public String toString()
|
|
{
|
|
return name;
|
|
}
|
|
|
|
@Override
|
|
public Unsafe unsafe()
|
|
{
|
|
return unsafe;
|
|
}
|
|
}
|