diff --git a/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java b/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java index a3408748a..5657f407a 100644 --- a/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java +++ b/api/src/main/java/net/md_5/bungee/api/connection/ProxiedPlayer.java @@ -3,6 +3,7 @@ package net.md_5.bungee.api.connection; import java.util.Locale; import net.md_5.bungee.api.Callback; import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.config.ServerInfo; import java.util.UUID; @@ -121,4 +122,23 @@ public interface ProxiedPlayer extends Connection, CommandSender * @return the locale */ Locale getLocale(); + + /** + * Set the header and footer displayed in the tab player list. + * @param header The header for the tab player list, null to clear it. + * @param footer The footer for the tab player list, null to clear it. + */ + void setTabHeader(BaseComponent header, BaseComponent footer); + + /** + * Set the header and footer displayed in the tab player list. + * @param header The header for the tab player list, null to clear it. + * @param footer The footer for the tab player list, null to clear it. + */ + void setTabHeader(BaseComponent[] header, BaseComponent[] footer); + + /** + * Clears the header and footer displayed in the tab player list. + */ + void resetTabHeader(); } diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/AbstractPacketHandler.java b/protocol/src/main/java/net/md_5/bungee/protocol/AbstractPacketHandler.java index 5e7a04e03..29f1b9b1b 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/AbstractPacketHandler.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/AbstractPacketHandler.java @@ -6,6 +6,7 @@ import net.md_5.bungee.protocol.packet.ClientStatus; import net.md_5.bungee.protocol.packet.Login; import net.md_5.bungee.protocol.packet.Chat; import net.md_5.bungee.protocol.packet.EncryptionRequest; +import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter; import net.md_5.bungee.protocol.packet.PlayerListItem; import net.md_5.bungee.protocol.packet.SetCompression; import net.md_5.bungee.protocol.packet.TabCompleteRequest; @@ -86,6 +87,10 @@ public abstract class AbstractPacketHandler { } + public void handle(PlayerListHeaderFooter playerListHeaderFooter) throws Exception + { + } + public void handle(TabCompleteRequest tabComplete) throws Exception { } diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java b/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java index d1b019811..2a156bc5e 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java @@ -19,6 +19,7 @@ import net.md_5.bungee.protocol.packet.Login; import net.md_5.bungee.protocol.packet.LoginRequest; import net.md_5.bungee.protocol.packet.LoginSuccess; import net.md_5.bungee.protocol.packet.PingPacket; +import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter; import net.md_5.bungee.protocol.packet.PlayerListItem; import net.md_5.bungee.protocol.packet.PluginMessage; import net.md_5.bungee.protocol.packet.Respawn; @@ -61,6 +62,7 @@ public enum Protocol TO_CLIENT.registerPacket( 0x3F, PluginMessage.class ); TO_CLIENT.registerPacket( 0x40, Kick.class ); TO_CLIENT.registerPacket( 0x46, SetCompression.class ); + TO_CLIENT.registerPacket( 0x47, PlayerListHeaderFooter.class ); TO_SERVER.registerPacket( 0x00, KeepAlive.class ); TO_SERVER.registerPacket( 0x01, Chat.class ); diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/packet/PlayerListHeaderFooter.java b/protocol/src/main/java/net/md_5/bungee/protocol/packet/PlayerListHeaderFooter.java new file mode 100644 index 000000000..f71c6ba9b --- /dev/null +++ b/protocol/src/main/java/net/md_5/bungee/protocol/packet/PlayerListHeaderFooter.java @@ -0,0 +1,41 @@ +package net.md_5.bungee.protocol.packet; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import io.netty.buffer.ByteBuf; +import net.md_5.bungee.protocol.AbstractPacketHandler; +import net.md_5.bungee.protocol.DefinedPacket; +import net.md_5.bungee.protocol.ProtocolConstants; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class PlayerListHeaderFooter extends DefinedPacket +{ + private String header; + private String footer; + + @Override + public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) + { + header = readString( buf ); + footer = readString( buf ); + } + + @Override + public void write(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) + { + writeString( header, buf ); + writeString( footer, buf ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/UserConnection.java b/proxy/src/main/java/net/md_5/bungee/UserConnection.java index 916422672..63a2c6607 100644 --- a/proxy/src/main/java/net/md_5/bungee/UserConnection.java +++ b/proxy/src/main/java/net/md_5/bungee/UserConnection.java @@ -41,8 +41,10 @@ import net.md_5.bungee.protocol.DefinedPacket; import net.md_5.bungee.protocol.MinecraftDecoder; import net.md_5.bungee.protocol.MinecraftEncoder; 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.PlayerListHeaderFooter; import net.md_5.bungee.protocol.packet.PluginMessage; import net.md_5.bungee.protocol.packet.Kick; import net.md_5.bungee.protocol.packet.SetCompression; @@ -469,6 +471,39 @@ public final class UserConnection implements ProxiedPlayer return ( locale == null && settings != null ) ? locale = Locale.forLanguageTag( settings.getLocale().replaceAll( "_", "-" ) ) : locale; } + private static final String EMPTY_TEXT = ComponentSerializer.toString( new TextComponent( "" ) ); + + @Override + public void setTabHeader(BaseComponent header, BaseComponent footer) + { + if ( pendingConnection.getVersion() >= ProtocolConstants.MINECRAFT_SNAPSHOT ) + { + unsafe().sendPacket( new PlayerListHeaderFooter( + ( header != null ) ? ComponentSerializer.toString( header ) : EMPTY_TEXT, + ( footer != null ) ? ComponentSerializer.toString( footer ) : EMPTY_TEXT + ) ); + } + } + + @Override + public void setTabHeader(BaseComponent[] header, BaseComponent[] footer) + { + if ( pendingConnection.getVersion() >= ProtocolConstants.MINECRAFT_SNAPSHOT ) + { + unsafe().sendPacket( new PlayerListHeaderFooter( + ( header != null ) ? ComponentSerializer.toString( header ) : EMPTY_TEXT, + ( footer != null ) ? ComponentSerializer.toString( footer ) : EMPTY_TEXT + ) ); + } + } + + @Override + public void resetTabHeader() + { + // Mojang did not add a way to remove the header / footer completely, we can only set it to empty + setTabHeader( (BaseComponent) null, null ); + } + public void setCompressionThreshold(int compressionThreshold) { if ( this.compressionThreshold == -1 )