diff --git a/api/src/main/java/net/md_5/bungee/api/TabAPI.java b/api/src/main/java/net/md_5/bungee/api/TabAPI.java new file mode 100644 index 000000000..4881ed949 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/TabAPI.java @@ -0,0 +1,58 @@ +package net.md_5.bungee.api; + +/** + * Represents a custom tab list, which may have slots manipulated. + */ +public interface TabAPI +{ + + /** + * Blank out this tab list and update immediately. + */ + void clear(); + + /** + * Gets the columns in this list. + * + * @return the width of this list + */ + int getColumns(); + + /** + * Gets the rows in this list. + * + * @return the height of this list + */ + int getRows(); + + /** + * Get the total size of this list. + * + * @return {@link #getRows()} * {@link #getColumns()} + */ + int getSize(); + + /** + * Set the text in the specified slot and update immediately. + * + * @param row the row to set + * @param column the column to set + * @param text the text to set + */ + void setSlot(int row, int column, String text); + + /** + * Set the text in the specified slot. + * + * @param row the row to set + * @param column the column to set + * @param text the text to set + * @param update whether or not to invoke {@link #update()} upon completion + */ + void setSlot(int row, int column, String text, boolean update); + + /** + * Flush all queued changes to the user. + */ + void update(); +} diff --git a/proxy/src/main/java/net/md_5/bungee/tablist/Custom.java b/proxy/src/main/java/net/md_5/bungee/tablist/Custom.java new file mode 100644 index 000000000..317a03ed7 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/tablist/Custom.java @@ -0,0 +1,130 @@ +package net.md_5.bungee.tablist; + +import net.md_5.bungee.api.TabAPI; +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.TabListHandler; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.protocol.packet.PacketC9PlayerListItem; + +public class Custom extends TabListHandler implements TabAPI +{ + + private static final int ROWS = 20; + private static final int COLUMNS = 3; + /*========================================================================*/ + private final Collection sentStuff = new HashSet<>(); + /*========================================================================*/ + private String[][] sent = new String[ ROWS ][ COLUMNS ]; + private String[][] pending = new String[ ROWS ][ COLUMNS ]; + + public Custom(ProxiedPlayer player) + { + super( player ); + } + + @Override + public synchronized void setSlot(int row, int column, String text) + { + setSlot( row, column, text, true ); + } + + @Override + public synchronized void 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" ); + Preconditions.checkNotNull( text, "text" ); + Preconditions.checkArgument( text.length() <= 16, "text must be <= 16 chars" ); + Preconditions.checkArgument( !sentStuff.contains( text ), "list already contains %s", text ); + Preconditions.checkArgument( !ChatColor.stripColor( text ).equals( text ), "Text cannot consist entirely of colour codes" ); + + pending[ROWS][COLUMNS] = text; + if ( update ) + { + update(); + } + } + + @Override + public synchronized void update() + { + clear(); + + for ( int i = 0; i < ROWS; i++ ) + { + for ( int j = 0; j < COLUMNS; j++ ) + { + String text; + if ( pending[i][j] != null ) + { + text = pending[i][j]; + sentStuff.add( text ); + } else + { + text = new StringBuilder().append( ChatColor.COLOR_CHAR ).append( base( i ) ).append( ChatColor.COLOR_CHAR ).append( base( j ) ).toString(); + } + getPlayer().unsafe().sendPacket( new PacketC9PlayerListItem( text, true, (short) 0 ) ); + sent[i][j] = text; + pending[i][j] = null; + } + } + } + + @Override + public synchronized void clear() + { + for ( int i = 0; i < ROWS; i++ ) + { + for ( int j = 0; j < COLUMNS; j++ ) + { + getPlayer().unsafe().sendPacket( new PacketC9PlayerListItem( sent[i][j], false, (short) 9999 ) ); + } + } + sent = new String[ ROWS ][ COLUMNS ]; + sentStuff.clear(); + } + + @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 ); + 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; + } +}