Add support for Minecraft 1.8.x

This commit allows BungeeCord to support Minecraft clients both of versions 1.7.x and of 1.8.x. There should be no breakages to any other support, however following their deprecation and uselessness within 1.8, the Tab list APIs have been removed.

Please report any issues to GitHub and be sure to mention client, server and BungeeCord versions.

When used with an appropriate server jar (such as multi protocol Spigot), this will allow clients of many versions to concurrently be connected to the same set of servers.
This commit is contained in:
Thinkofdeath
2014-08-31 09:24:38 +10:00
parent e99bbff22e
commit 26521cf2ff
38 changed files with 857 additions and 533 deletions

View File

@@ -36,6 +36,7 @@ import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
@@ -65,7 +66,6 @@ import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.api.tab.CustomTabList;
import net.md_5.bungee.command.*;
import net.md_5.bungee.conf.YamlConfig;
import net.md_5.bungee.log.LoggingOutputStream;
@@ -76,7 +76,6 @@ import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.packet.Chat;
import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.query.RemoteQuery;
import net.md_5.bungee.tab.Custom;
import net.md_5.bungee.util.CaseInsensitiveMap;
import org.fusesource.jansi.AnsiConsole;
@@ -113,6 +112,8 @@ public class BungeeCord extends ProxyServer
* Fully qualified connections.
*/
private final Map<String, UserConnection> connections = new CaseInsensitiveMap<>();
// Used to help with packet rewriting
private final Map<UUID, UserConnection> connectionsByOfflineUUID = new HashMap<>();
private final ReadWriteLock connectionLock = new ReentrantReadWriteLock();
/**
* Plugin manager.
@@ -144,7 +145,7 @@ public class BungeeCord extends ProxyServer
private ConnectionThrottle connectionThrottle;
private final ModuleManager moduleManager = new ModuleManager();
{
// TODO: Proper fallback when we interface the manager
getPluginManager().registerCommand( null, new CommandReload() );
@@ -468,6 +469,18 @@ public class BungeeCord extends ProxyServer
}
}
public UserConnection getPlayerByOfflineUUID(UUID name)
{
connectionLock.readLock().lock();
try
{
return connectionsByOfflineUUID.get( name );
} finally
{
connectionLock.readLock().unlock();
}
}
@Override
public ProxiedPlayer getPlayer(UUID uuid)
{
@@ -577,6 +590,7 @@ public class BungeeCord extends ProxyServer
try
{
connections.put( con.getName(), con );
connectionsByOfflineUUID.put( con.getPendingConnection().getOfflineId(), con );
} finally
{
connectionLock.writeLock().unlock();
@@ -589,19 +603,13 @@ public class BungeeCord extends ProxyServer
try
{
connections.remove( con.getName() );
connectionsByOfflineUUID.remove( con.getPendingConnection().getOfflineId() );
} finally
{
connectionLock.writeLock().unlock();
}
}
@Override
public CustomTabList customTabList(ProxiedPlayer player)
{
return new Custom( player );
}
@Override
public Collection<String> getDisabledCommands()
{
return config.getDisabledCommands();

View File

@@ -31,6 +31,7 @@ import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.protocol.packet.Kick;
import net.md_5.bungee.protocol.packet.LoginSuccess;
import net.md_5.bungee.protocol.packet.SetCompression;
@RequiredArgsConstructor
public class ServerConnector extends PacketHandler
@@ -102,6 +103,13 @@ public class ServerConnector extends PacketHandler
throw CancelSendSignal.INSTANCE;
}
@Override
public void handle(SetCompression setCompression) throws Exception
{
user.setCompressionThreshold( setCompression.getThreshold() );
ch.setCompressionThreshold( setCompression.getThreshold() );
}
@Override
public void handle(Login login) throws Exception
{
@@ -139,7 +147,7 @@ public class ServerConnector extends PacketHandler
// Set tab list size, this sucks balls, 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() );
(byte) user.getPendingConnection().getListener().getTabListSize(), login.getLevelType(), login.isReducedDebugInfo() );
user.unsafe().sendPacket( modLogin );
@@ -148,7 +156,7 @@ public class ServerConnector extends PacketHandler
user.unsafe().sendPacket( new PluginMessage( "MC|Brand", out.toArray() ) );
} else
{
user.getTabList().onServerChange();
user.getTabListHandler().onServerChange();
Scoreboard serverScoreboard = user.getServerSentScoreboard();
for ( Objective objective : serverScoreboard.getObjectives() )

View File

@@ -30,7 +30,6 @@ 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.chat.ComponentSerializer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.entitymap.EntityMap;
@@ -46,6 +45,11 @@ import net.md_5.bungee.protocol.packet.Chat;
import net.md_5.bungee.protocol.packet.ClientSettings;
import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.protocol.packet.Kick;
import net.md_5.bungee.protocol.packet.SetCompression;
import net.md_5.bungee.tab.Global;
import net.md_5.bungee.tab.GlobalPing;
import net.md_5.bungee.tab.ServerUnique;
import net.md_5.bungee.tab.TabList;
import net.md_5.bungee.util.CaseInsensitiveSet;
@RequiredArgsConstructor
@@ -73,8 +77,6 @@ public final class UserConnection implements ProxiedPlayer
private final Collection<ServerInfo> pendingConnects = new HashSet<>();
/*========================================================================*/
@Getter
private TabListHandler tabList;
@Getter
@Setter
private int sentPingId;
@Getter
@@ -86,6 +88,13 @@ public final class UserConnection implements ProxiedPlayer
@Getter
@Setter
private ServerInfo reconnectServer;
@Getter
private TabList tabListHandler;
@Getter
@Setter
private int gamemode;
@Getter
private int compressionThreshold = -1;
/*========================================================================*/
private final Collection<String> groups = new CaseInsensitiveSet();
private final Collection<String> permissions = new CaseInsensitiveSet();
@@ -121,14 +130,19 @@ public final class UserConnection implements ProxiedPlayer
this.entityRewrite = EntityMap.getEntityMap( getPendingConnection().getVersion() );
this.displayName = name;
try
switch ( getPendingConnection().getListener().getTabListType() )
{
this.tabList = getPendingConnection().getListener().getTabList().getDeclaredConstructor().newInstance();
} catch ( ReflectiveOperationException ex )
{
throw new RuntimeException( ex );
case "GLOBAL":
tabListHandler = new Global( this );
break;
case "SERVER":
tabListHandler = new ServerUnique( this );
break;
default:
tabListHandler = new GlobalPing( this );
break;
}
this.tabList.init( this );
Collection<String> g = bungee.getConfigurationAdapter().getGroups( name );
for ( String s : g )
@@ -137,13 +151,6 @@ public final class UserConnection implements ProxiedPlayer
}
}
@Override
public void setTabList(TabListHandler tabList)
{
tabList.init( this );
this.tabList = tabList;
}
public void sendPacket(PacketWrapper packet)
{
ch.write( packet );
@@ -160,9 +167,7 @@ public final class UserConnection implements ProxiedPlayer
{
Preconditions.checkNotNull( name, "displayName" );
Preconditions.checkArgument( name.length() <= 16, "Display name cannot be longer than 16 characters" );
getTabList().onDisconnect();
displayName = name;
getTabList().onConnect();
}
@Override
@@ -461,4 +466,14 @@ public final class UserConnection implements ProxiedPlayer
{
return ( locale == null && settings != null ) ? locale = Locale.forLanguageTag( settings.getLocale().replaceAll( "_", "-" ) ) : locale;
}
public void setCompressionThreshold(int compressionThreshold)
{
if ( this.compressionThreshold == -1 )
{
this.compressionThreshold = compressionThreshold;
unsafe.sendPacket( new SetCompression( compressionThreshold ) );
ch.setCompressionThreshold( compressionThreshold );
}
}
}

View File

@@ -22,10 +22,6 @@ import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ConfigurationAdapter;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.tab.TabListHandler;
import net.md_5.bungee.tab.Global;
import net.md_5.bungee.tab.GlobalPing;
import net.md_5.bungee.tab.ServerUnique;
import net.md_5.bungee.util.CaseInsensitiveMap;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
@@ -40,8 +36,7 @@ public class YamlConfig implements ConfigurationAdapter
private enum DefaultTabList
{
GLOBAL( Global.class ), GLOBAL_PING( GlobalPing.class ), SERVER( ServerUnique.class );
private final Class<? extends TabListHandler> clazz;
GLOBAL(), GLOBAL_PING(), SERVER();
}
private final Yaml yaml;
private Map config;
@@ -224,7 +219,7 @@ public class YamlConfig implements ConfigurationAdapter
boolean query = get( "query_enabled", false, val );
int queryPort = get( "query_port", 25577, val );
ListenerInfo info = new ListenerInfo( address, motd, maxPlayers, tabListSize, defaultServer, fallbackServer, forceDefault, forced, value.clazz, setLocalAddress, pingPassthrough, queryPort, query );
ListenerInfo info = new ListenerInfo( address, motd, maxPlayers, tabListSize, defaultServer, fallbackServer, forceDefault, forced, value.toString(), setLocalAddress, pingPassthrough, queryPort, query );
ret.add( info );
}

View File

@@ -31,6 +31,8 @@ import net.md_5.bungee.protocol.packet.ScoreboardScore;
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.protocol.packet.Kick;
import net.md_5.bungee.protocol.packet.SetCompression;
import net.md_5.bungee.tab.TabList;
@RequiredArgsConstructor
public class DownstreamBridge extends PacketHandler
@@ -100,11 +102,8 @@ public class DownstreamBridge extends PacketHandler
@Override
public void handle(PlayerListItem playerList) throws Exception
{
if ( !con.getTabList().onListUpdate( playerList.getUsername(), playerList.isOnline(), playerList.getPing() ) )
{
throw CancelSendSignal.INSTANCE;
}
con.getTabListHandler().onUpdate( TabList.rewrite( playerList ) );
throw CancelSendSignal.INSTANCE; // Always throw because of profile rewriting
}
@Override
@@ -448,6 +447,13 @@ public class DownstreamBridge extends PacketHandler
throw CancelSendSignal.INSTANCE;
}
@Override
public void handle(SetCompression setCompression) throws Exception
{
con.setCompressionThreshold( setCompression.getThreshold() );
server.getCh().setCompressionThreshold( setCompression.getThreshold() );
}
@Override
public String toString()
{

View File

@@ -34,7 +34,7 @@ public class UpstreamBridge extends PacketHandler
this.con = con;
BungeeCord.getInstance().addConnection( con );
con.getTabList().onConnect();
con.getTabListHandler().onConnect();
con.unsafe().sendPacket( BungeeCord.getInstance().registerChannels() );
}
@@ -50,7 +50,7 @@ public class UpstreamBridge extends PacketHandler
// We lost connection to the client
PlayerDisconnectEvent event = new PlayerDisconnectEvent( con );
bungee.getPluginManager().callEvent( event );
con.getTabList().onDisconnect();
con.getTabListHandler().onDisconnect();
BungeeCord.getInstance().removeConnection( con );
if ( con.getServer() != null )
@@ -62,10 +62,7 @@ public class UpstreamBridge extends PacketHandler
@Override
public void handle(PacketWrapper packet) throws Exception
{
if ( con.getPendingConnection().getVersion() <= ProtocolConstants.MINECRAFT_1_7_6 )
{
con.getEntityRewrite().rewriteServerbound( packet.buf, con.getClientEntityId(), con.getServerEntityId() );
}
con.getEntityRewrite().rewriteServerbound( packet.buf, con.getClientEntityId(), con.getServerEntityId() );
if ( con.getServer() != null )
{
con.getServer().getCh().write( packet );
@@ -78,7 +75,7 @@ public class UpstreamBridge extends PacketHandler
if ( alive.getRandomId() == con.getSentPingId() )
{
int newPing = (int) ( System.currentTimeMillis() - con.getSentPingTime() );
con.getTabList().onPingChange( newPing );
con.getTabListHandler().onPingChange( newPing );
con.setPing( newPing );
}
}

View File

@@ -30,8 +30,8 @@ public abstract class EntityMap
return new EntityMap_1_7_2();
case ProtocolConstants.MINECRAFT_1_7_6:
return new EntityMap_1_7_6();
case ProtocolConstants.MINECRAFT_14_11_a:
return new EntityMap_14_11_a();
case ProtocolConstants.MINECRAFT_SNAPSHOT:
return new EntityMap_14_21_b();
}
throw new RuntimeException( "Version " + version + " has no entity map" );
}

View File

@@ -4,14 +4,15 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.netty.buffer.ByteBuf;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.ProtocolConstants;
import java.util.UUID;
class EntityMap_14_11_a extends EntityMap
class EntityMap_14_21_b extends EntityMap
{
EntityMap_14_11_a()
EntityMap_14_21_b()
{
addRewrite( 0x04, ProtocolConstants.Direction.TO_CLIENT, true ); // Entity Equipment
addRewrite( 0x0A, ProtocolConstants.Direction.TO_CLIENT, true ); // Use bed
@@ -38,6 +39,7 @@ class EntityMap_14_11_a extends EntityMap
addRewrite( 0x25, ProtocolConstants.Direction.TO_CLIENT, true ); // Block Break Animation
addRewrite( 0x2C, ProtocolConstants.Direction.TO_CLIENT, true ); // Spawn Global Entity
addRewrite( 0x43, ProtocolConstants.Direction.TO_CLIENT, true ); // Camera
addRewrite( 0x49, ProtocolConstants.Direction.TO_CLIENT, true ); // Update Entity NBT
addRewrite( 0x02, ProtocolConstants.Direction.TO_SERVER, true ); // Use Entity
addRewrite( 0x0B, ProtocolConstants.Direction.TO_SERVER, true ); // Entity Action
@@ -84,23 +86,24 @@ class EntityMap_14_11_a extends EntityMap
}
} else if ( packetId == 0x0E /* Spawn Object */ )
{
DefinedPacket.readVarInt( packet );
int idLength = packet.readerIndex() - readerIndex - packetIdLength;
int type = packet.getByte( readerIndex + packetIdLength + idLength );
DefinedPacket.readVarInt( packet );
int type = packet.readUnsignedByte();
if ( type == 60 || type == 90 )
{
int readId = packet.getInt( packetIdLength + idLength + 15 );
packet.skipBytes( 14 );
int position = packet.readerIndex();
int readId = packet.readInt();
int changedId = -1;
if ( readId == oldId )
{
packet.setInt( packetIdLength + idLength + 15, newId );
packet.setInt( position, newId );
changedId = newId;
} else if ( readId == newId )
{
packet.setInt( packetIdLength + idLength + 15, oldId );
changedId = newId;
packet.setInt( position, oldId );
changedId = oldId;
}
if ( changedId != -1 )
{
@@ -118,36 +121,17 @@ class EntityMap_14_11_a extends EntityMap
}
} else if ( packetId == 0x0C /* Spawn Player */ )
{
DefinedPacket.readVarInt( packet );
DefinedPacket.readVarInt( packet ); // Entity ID
int idLength = packet.readerIndex() - readerIndex - packetIdLength;
String uuid = DefinedPacket.readString( packet );
String username = DefinedPacket.readString( packet );
int props = DefinedPacket.readVarInt( packet );
if ( props == 0 )
UUID uuid = DefinedPacket.readUUID( packet );
ProxiedPlayer player;
if ( ( player = BungeeCord.getInstance().getPlayerByOfflineUUID( uuid ) ) != null )
{
UserConnection player = (UserConnection) BungeeCord.getInstance().getPlayer( username );
if ( player != null )
{
LoginResult profile = player.getPendingConnection().getLoginProfile();
if ( profile != null && profile.getProperties() != null
&& profile.getProperties().length >= 1 )
{
ByteBuf rest = packet.slice().copy();
packet.readerIndex( readerIndex );
packet.writerIndex( readerIndex + packetIdLength + idLength );
DefinedPacket.writeString( player.getUniqueId().toString(), packet );
DefinedPacket.writeString( username, packet );
DefinedPacket.writeVarInt( profile.getProperties().length, packet );
for ( LoginResult.Property property : profile.getProperties() )
{
DefinedPacket.writeString( property.getName(), packet );
DefinedPacket.writeString( property.getValue(), packet );
DefinedPacket.writeString( property.getSignature(), packet );
}
packet.writeBytes( rest );
rest.release();
}
}
int previous = packet.writerIndex();
packet.readerIndex( readerIndex );
packet.writerIndex( readerIndex + packetIdLength + idLength );
DefinedPacket.writeUUID( player.getUniqueId(), packet );
packet.writerIndex( previous );
}
} else if ( packetId == 0x42 /* Combat Event */ )
{
@@ -167,4 +151,29 @@ class EntityMap_14_11_a extends EntityMap
}
packet.readerIndex( readerIndex );
}
@Override
public void rewriteServerbound(ByteBuf packet, int oldId, int newId)
{
super.rewriteServerbound( packet, oldId, newId );
//Special cases
int readerIndex = packet.readerIndex();
int packetId = DefinedPacket.readVarInt( packet );
int packetIdLength = packet.readerIndex() - readerIndex;
if ( packetId == 0x18 /* Spectate */ )
{
UUID uuid = DefinedPacket.readUUID( packet );
ProxiedPlayer player;
if ( ( player = BungeeCord.getInstance().getPlayer( uuid ) ) != null )
{
int previous = packet.writerIndex();
packet.readerIndex( readerIndex );
packet.writerIndex( readerIndex + packetIdLength );
DefinedPacket.writeUUID( ( (UserConnection) player ).getPendingConnection().getOfflineId(), packet );
packet.writerIndex( previous );
}
}
packet.readerIndex( readerIndex );
}
}

View File

@@ -1,5 +1,7 @@
package net.md_5.bungee.netty;
import net.md_5.bungee.protocol.PacketCompressor;
import net.md_5.bungee.protocol.PacketDecompressor;
import net.md_5.bungee.protocol.PacketWrapper;
import com.google.common.base.Preconditions;
import io.netty.channel.Channel;
@@ -71,4 +73,28 @@ public class ChannelWrapper
{
return ch;
}
public void setCompressionThreshold(int compressionThreshold)
{
if ( ch.pipeline().get( PacketCompressor.class ) == null && compressionThreshold != -1 )
{
addBefore( PipelineUtils.PACKET_ENCODER, "compress", new PacketCompressor() );
}
if ( compressionThreshold != -1 )
{
ch.pipeline().get( PacketCompressor.class ).setThreshold( compressionThreshold );
} else
{
ch.pipeline().remove( "compress" );
}
if ( ch.pipeline().get( PacketDecompressor.class ) == null && compressionThreshold != -1 )
{
addBefore( PipelineUtils.PACKET_DECODER, "decompress", new PacketDecompressor() );
}
if ( compressionThreshold == -1 )
{
ch.pipeline().remove( "decompress" );
}
}
}

View File

@@ -1,158 +0,0 @@
package net.md_5.bungee.tab;
import com.google.common.base.Preconditions;
import java.util.Collection;
import java.util.HashSet;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.tab.CustomTabList;
import net.md_5.bungee.api.tab.TabListAdapter;
import net.md_5.bungee.protocol.packet.PlayerListItem;
public class Custom extends TabListAdapter implements CustomTabList
{
private static final int ROWS = 20;
private static final int COLUMNS = 3;
private static final char[] FILLER = new char[]
{
'0', '1', '2', '2', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
private static final int MAX_LEN = 16;
/*========================================================================*/
private final Collection<String> sentStuff = new HashSet<>();
/*========================================================================*/
private final String[][] sent = new String[ ROWS ][ COLUMNS ];
private final String[][] slots = new String[ ROWS ][ COLUMNS ];
private int rowLim;
private int colLim;
public Custom(ProxiedPlayer player)
{
this.init( player );
}
@Override
public synchronized String setSlot(int row, int column, String text)
{
return setSlot( row, column, text, true );
}
@Override
public synchronized String setSlot(int row, int column, String text, boolean update)
{
Preconditions.checkArgument( row > 0 && row <= ROWS, "row out of range" );
Preconditions.checkArgument( column > 0 && column <= COLUMNS, "column out of range" );
if ( text != null )
{
Preconditions.checkArgument( text.length() <= MAX_LEN - 2, "text must be <= %s chars", MAX_LEN - 2 );
Preconditions.checkArgument( !ChatColor.stripColor( text ).isEmpty(), "Text cannot consist entirely of colour codes" );
text = attempt( text );
sentStuff.add( text );
if ( rowLim < row || colLim < column )
{
rowLim = row;
colLim = column;
}
}
slots[--row][--column] = text;
if ( update )
{
update();
}
return text;
}
private String attempt(String s)
{
for ( char c : FILLER )
{
String attempt = s + Character.toString( ChatColor.COLOR_CHAR ) + c;
if ( !sentStuff.contains( attempt ) )
{
return attempt;
}
}
if ( s.length() <= MAX_LEN - 4 )
{
return attempt( s + Character.toString( ChatColor.COLOR_CHAR ) + FILLER[0] );
}
throw new IllegalArgumentException( "List already contains all variants of string" );
}
@Override
public synchronized void update()
{
clear();
for ( int i = 0; i < rowLim; i++ )
{
for ( int j = 0; j < colLim; j++ )
{
String text = ( slots[i][j] != null ) ? slots[i][j] : new StringBuilder().append( base( i ) ).append( base( j ) ).toString();
sent[i][j] = text;
getPlayer().unsafe().sendPacket( new PlayerListItem( text, true, (short) 0 ) );
}
}
}
@Override
public synchronized void clear()
{
for ( int i = 0; i < rowLim; i++ )
{
for ( int j = 0; j < colLim; j++ )
{
if ( sent[i][j] != null )
{
String text = sent[i][j];
sent[i][j] = null;
getPlayer().unsafe().sendPacket( new PlayerListItem( text, false, (short) 9999 ) );
}
}
}
}
@Override
public synchronized int getRows()
{
return ROWS;
}
@Override
public synchronized int getColumns()
{
return COLUMNS;
}
@Override
public synchronized int getSize()
{
return ROWS * COLUMNS;
}
@Override
public boolean onListUpdate(String name, boolean online, int ping)
{
return false;
}
private static char[] base(int n)
{
String hex = Integer.toHexString( n + 1 );
char[] alloc = new char[ hex.length() * 2 ];
for ( int i = 0; i < alloc.length; i++ )
{
if ( i % 2 == 0 )
{
alloc[i] = ChatColor.COLOR_CHAR;
} else
{
alloc[i] = hex.charAt( i / 2 );
}
}
return alloc;
}
}

View File

@@ -1,24 +1,30 @@
package net.md_5.bungee.tab;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.tab.TabListAdapter;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.packet.PlayerListItem;
public class Global extends TabListAdapter
import java.util.Collection;
public class Global extends TabList
{
private boolean sentPing;
@Override
public void onConnect()
public Global(ProxiedPlayer player)
{
for ( ProxiedPlayer p : ProxyServer.getInstance().getPlayers() )
{
getPlayer().unsafe().sendPacket( new PlayerListItem( p.getDisplayName(), true, (short) p.getPing() ) );
}
BungeeCord.getInstance().broadcast( new PlayerListItem( getPlayer().getDisplayName(), true, (short) getPlayer().getPing() ) );
super( player );
}
@Override
public void onUpdate(PlayerListItem playerListItem)
{
}
@Override
@@ -27,19 +33,112 @@ public class Global extends TabListAdapter
if ( !sentPing )
{
sentPing = true;
BungeeCord.getInstance().broadcast( new PlayerListItem( getPlayer().getDisplayName(), true, (short) getPlayer().getPing() ) );
PlayerListItem packet = new PlayerListItem();
packet.setAction( PlayerListItem.Action.UPDATE_LATENCY );
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUuid( player.getUniqueId() );
item.setUsername( player.getName() );
item.setDisplayName( ComponentSerializer.toString( TextComponent.fromLegacyText( player.getDisplayName() ) ) );
item.setPing( player.getPing() );
packet.setItems( new PlayerListItem.Item[]
{
item
} );
BungeeCord.getInstance().broadcast( packet );
}
}
@Override
public void onServerChange()
{
}
@Override
public void onConnect()
{
PlayerListItem playerListItem = new PlayerListItem();
playerListItem.setAction( PlayerListItem.Action.ADD_PLAYER );
Collection<ProxiedPlayer> players = BungeeCord.getInstance().getPlayers();
PlayerListItem.Item[] items = new PlayerListItem.Item[ players.size() ];
playerListItem.setItems( items );
int i = 0;
for ( ProxiedPlayer p : players )
{
PlayerListItem.Item item = items[i++] = new PlayerListItem.Item();
item.setUuid( p.getUniqueId() );
item.setUsername( p.getName() );
item.setDisplayName( ComponentSerializer.toString( TextComponent.fromLegacyText( p.getDisplayName() ) ) );
LoginResult loginResult = ( (UserConnection) p ).getPendingConnection().getLoginProfile();
String[][] props = new String[ loginResult.getProperties().length ][];
for ( int j = 0; j < props.length; j++ )
{
props[ j] = new String[]
{
loginResult.getProperties()[j].getName(),
loginResult.getProperties()[j].getValue(),
loginResult.getProperties()[j].getSignature()
};
}
item.setProperties( props );
item.setGamemode( ( (UserConnection) p ).getGamemode() );
item.setPing( p.getPing() );
}
if ( player.getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_SNAPSHOT )
{
player.unsafe().sendPacket( playerListItem );
} else
{
// Split up the packet
for ( PlayerListItem.Item item : playerListItem.getItems() )
{
PlayerListItem packet = new PlayerListItem();
packet.setAction( playerListItem.getAction() );
PlayerListItem.Item[] it = new PlayerListItem.Item[ 1 ];
it[0] = item;
packet.setItems( it );
player.unsafe().sendPacket( packet );
}
}
PlayerListItem packet = new PlayerListItem();
packet.setAction( PlayerListItem.Action.ADD_PLAYER );
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUuid( player.getUniqueId() );
item.setUsername( player.getName() );
item.setDisplayName( ComponentSerializer.toString( TextComponent.fromLegacyText( player.getDisplayName() ) ) );
LoginResult loginResult = ( (UserConnection) player ).getPendingConnection().getLoginProfile();
String[][] props = new String[ loginResult.getProperties().length ][];
for ( int j = 0; j < props.length; j++ )
{
props[ j] = new String[]
{
loginResult.getProperties()[j].getName(),
loginResult.getProperties()[j].getValue(),
loginResult.getProperties()[j].getSignature()
};
}
item.setProperties( props );
item.setGamemode( ( (UserConnection) player ).getGamemode() );
item.setPing( player.getPing() );
packet.setItems( new PlayerListItem.Item[]
{
item
} );
BungeeCord.getInstance().broadcast( packet );
}
@Override
public void onDisconnect()
{
BungeeCord.getInstance().broadcast( new PlayerListItem( getPlayer().getDisplayName(), false, (short) 9999 ) );
}
@Override
public boolean onListUpdate(String name, boolean online, int ping)
{
return false;
PlayerListItem packet = new PlayerListItem();
packet.setAction( PlayerListItem.Action.REMOVE_PLAYER );
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUuid( player.getUniqueId() );
item.setUsername( player.getName() );
packet.setItems( new PlayerListItem.Item[]
{
item
} );
BungeeCord.getInstance().broadcast( packet );
}
}

View File

@@ -1,6 +1,9 @@
package net.md_5.bungee.tab;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.protocol.packet.PlayerListItem;
public class GlobalPing extends Global
@@ -10,13 +13,29 @@ public class GlobalPing extends Global
/*========================================================================*/
private int lastPing;
public GlobalPing(ProxiedPlayer player)
{
super( player );
}
@Override
public void onPingChange(int ping)
{
if ( ping - PING_THRESHOLD > lastPing && ping + PING_THRESHOLD < lastPing )
{
lastPing = ping;
BungeeCord.getInstance().broadcast( new PlayerListItem( getPlayer().getDisplayName(), true, (short) ping ) );
PlayerListItem packet = new PlayerListItem();
packet.setAction( PlayerListItem.Action.UPDATE_LATENCY );
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUuid( player.getUniqueId() );
item.setUsername( player.getName() );
item.setDisplayName( ComponentSerializer.toString( TextComponent.fromLegacyText( player.getDisplayName() ) ) );
item.setPing( player.getPing() );
packet.setItems( new PlayerListItem.Item[]
{
item
} );
BungeeCord.getInstance().broadcast( packet );
}
}
}

View File

@@ -2,38 +2,105 @@ package net.md_5.bungee.tab;
import java.util.Collection;
import java.util.HashSet;
import net.md_5.bungee.api.tab.TabListAdapter;
import java.util.UUID;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.packet.PlayerListItem;
public class ServerUnique extends TabListAdapter
public class ServerUnique extends TabList
{
private final Collection<String> usernames = new HashSet<>();
private final Collection<UUID> uuids = new HashSet<>();
private final Collection<String> usernames = new HashSet<>(); // Support for <=1.7.9
public ServerUnique(ProxiedPlayer player)
{
super( player );
}
@Override
public void onUpdate(PlayerListItem playerListItem)
{
for ( PlayerListItem.Item item : playerListItem.getItems() )
{
if ( playerListItem.getAction() == PlayerListItem.Action.ADD_PLAYER )
{
if ( item.getUuid() != null )
{
uuids.add( item.getUuid() );
} else
{
usernames.add( item.getUsername() );
}
} else if ( playerListItem.getAction() == PlayerListItem.Action.REMOVE_PLAYER )
{
if ( item.getUuid() != null )
{
uuids.remove( item.getUuid() );
} else
{
usernames.remove( item.getUsername() );
}
}
}
player.unsafe().sendPacket( playerListItem );
}
@Override
public void onPingChange(int ping)
{
}
@Override
public void onServerChange()
{
synchronized ( usernames )
PlayerListItem packet = new PlayerListItem();
packet.setAction( PlayerListItem.Action.REMOVE_PLAYER );
PlayerListItem.Item[] items = new PlayerListItem.Item[ uuids.size() + usernames.size() ];
int i = 0;
for ( UUID uuid : uuids )
{
for ( String username : usernames )
{
getPlayer().unsafe().sendPacket( new PlayerListItem( username, false, (short) 9999 ) );
}
usernames.clear();
PlayerListItem.Item item = items[i++] = new PlayerListItem.Item();
item.setUuid( uuid );
}
for ( String username : usernames )
{
PlayerListItem.Item item = items[i++] = new PlayerListItem.Item();
item.setUsername( username );
item.setDisplayName( username );
}
packet.setItems( items );
if ( player.getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_SNAPSHOT )
{
player.unsafe().sendPacket( packet );
} else
{
// Split up the packet
for ( PlayerListItem.Item item : packet.getItems() )
{
PlayerListItem p2 = new PlayerListItem();
p2.setAction( packet.getAction() );
PlayerListItem.Item[] it = new PlayerListItem.Item[ 1 ];
it[0] = item;
p2.setItems( it );
player.unsafe().sendPacket( p2 );
}
}
uuids.clear();
usernames.clear();
}
@Override
public boolean onListUpdate(String name, boolean online, int ping)
public void onConnect()
{
}
@Override
public void onDisconnect()
{
if ( online )
{
usernames.add( name );
} else
{
usernames.remove( name );
}
return true;
}
}

View File

@@ -0,0 +1,59 @@
package net.md_5.bungee.tab;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.protocol.packet.PlayerListItem;
@RequiredArgsConstructor
public abstract class TabList
{
protected final ProxiedPlayer player;
public abstract void onUpdate(PlayerListItem playerListItem);
public abstract void onPingChange(int ping);
public abstract void onServerChange();
public abstract void onConnect();
public abstract void onDisconnect();
public static PlayerListItem rewrite(PlayerListItem playerListItem)
{
for ( PlayerListItem.Item item : playerListItem.getItems() )
{
if ( item.getUuid() == null ) // Old style ping
{
continue;
}
UserConnection player = BungeeCord.getInstance().getPlayerByOfflineUUID( item.getUuid() );
if ( player != null )
{
item.setUuid( player.getUniqueId() );
LoginResult loginResult = player.getPendingConnection().getLoginProfile();
String[][] props = new String[ loginResult.getProperties().length ][];
for ( int i = 0; i < props.length; i++ )
{
props[ i] = new String[]
{
loginResult.getProperties()[i].getName(),
loginResult.getProperties()[i].getValue(),
loginResult.getProperties()[i].getSignature()
};
}
item.setProperties( props );
if ( playerListItem.getAction() == PlayerListItem.Action.ADD_PLAYER || playerListItem.getAction() == PlayerListItem.Action.UPDATE_GAMEMODE )
{
player.setGamemode( item.getGamemode() );
}
player.setPing( player.getPing() );
}
}
return playerListItem;
}
}