13
0
geforkt von Mirrors/Velocity

Merge pull request #105 from Desetude/feature/tablist

Tab List Modification
Dieser Commit ist enthalten in:
Andrew Steinborn 2018-09-30 17:45:30 -04:00 committet von GitHub
Commit 49b09713f8
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
13 geänderte Dateien mit 690 neuen und 17 gelöschten Zeilen

Datei anzeigen

@ -6,6 +6,7 @@ import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import com.velocitypowered.api.proxy.player.PlayerSettings; import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.proxy.player.TabList;
import com.velocitypowered.api.util.MessagePosition; import com.velocitypowered.api.util.MessagePosition;
import com.velocitypowered.api.util.title.Title; import com.velocitypowered.api.util.title.Title;
import java.util.List; import java.util.List;
@ -88,14 +89,24 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage
* Sets the tab list header and footer for the player. * Sets the tab list header and footer for the player.
* @param header the header component * @param header the header component
* @param footer the footer component * @param footer the footer component
* @deprecated Use {@link TabList#setHeaderAndFooter(Component, Component)}.
*/ */
@Deprecated
void setHeaderAndFooter(Component header, Component footer); void setHeaderAndFooter(Component header, Component footer);
/** /**
* Clears the tab list header and footer for the player. * Clears the tab list header and footer for the player.
* @deprecated Use {@link TabList#clearHeaderAndFooter()}.
*/ */
@Deprecated
void clearHeaderAndFooter(); void clearHeaderAndFooter();
/**
* Returns {@link this} {@link Player}'s tab list.
* @return this player's tab list
*/
TabList getTabList();
/** /**
* Disconnects the player with the specified reason. Once this method is called, further calls to other {@link Player} * Disconnects the player with the specified reason. Once this method is called, further calls to other {@link Player}
* methods will become undefined. * methods will become undefined.

Datei anzeigen

@ -0,0 +1,51 @@
package com.velocitypowered.api.proxy.player;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.util.GameProfile;
import net.kyori.text.Component;
import java.util.Collection;
import java.util.Optional;
import java.util.UUID;
/**
* Represents the tab list of a {@link Player}.
*/
public interface TabList {
/**
* Sets the tab list header and footer for the player.
* @param header the header component
* @param footer the footer component
*/
void setHeaderAndFooter(Component header, Component footer);
/**
* Clears the tab list header and footer for the player.
*/
void clearHeaderAndFooter();
/**
* Adds a {@link TabListEntry} to the {@link Player}'s tab list.
* @param entry to add to the tab list
*/
void addEntry(TabListEntry entry);
/**
* Removes the {@link TabListEntry} from the tab list with the {@link GameProfile}
* identified with the specified {@link UUID}.
* @param uuid of the
* @return {@link Optional} containing the removed {@link TabListEntry} if present,
* otherwise {@link Optional#empty()}
*/
Optional<TabListEntry> removeEntry(UUID uuid);
/**
* Returns an immutable {@link Collection} of the {@link TabListEntry}s in the tab list.
* @return immutable {@link Collection} of tab list entries
*/
Collection<TabListEntry> getEntries();
// Necessary because the TabListEntry implementation isn't in the api
@Deprecated
TabListEntry buildEntry(GameProfile profile, Component displayName, int latency, int gameMode);
}

Datei anzeigen

@ -0,0 +1,176 @@
package com.velocitypowered.api.proxy.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.util.GameProfile;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Optional;
/**
* Represents a single entry in a {@link TabList}.
*/
public interface TabListEntry {
/**
* Returns the parent {@link TabList} of this {@code this} {@link TabListEntry}.
* @return parent {@link TabList}
*/
@NonNull TabList getTabList();
/**
* Returns the {@link GameProfile} of the entry, which uniquely identifies the entry
* with the containing {@link java.util.UUID}, as well as deciding what is shown
* as the player head in the tab list.
* @return {@link GameProfile} of the entry
*/
@NonNull GameProfile getProfile();
/**
* Returns {@link Optional} text {@link Component}, which if present is the text displayed for
* {@code this} entry in the {@link TabList}, otherwise {@link GameProfile#getName()} is shown.
* @return {@link Optional} text {@link Component} of name displayed in the tab list
*/
@NonNull Optional<Component> getDisplayName();
/**
* Sets the text {@link Component} to be displayed for {@code this} {@link TabListEntry}.
* If {@code null}, {@link GameProfile#getName()} will be shown.
* @param displayName to show in the {@link TabList} for {@code this} entry
* @return {@code this}, for chaining
*/
@NonNull TabListEntry setDisplayName(@Nullable Component displayName);
/**
* Returns the latency for {@code this} entry.
* <p>The icon shown in the tab list is calculated by the latency in the following way:<p>
* <ul>
* <li>A negative latency will display the no connection icon</li>
* <li>0-150 will display 5 bars</li>
* <li>150-300 will display 4 bars</li>
* <li>300-600 will display 3 bars</li>
* <li>600-1000 will display 2 bars</li>
* <li>A latency move than 1 second will display 1 bar</li>
* <li></li>
* </ul>
* @return latency set for {@code this} entry
*/
int getLatency();
/**
* Sets the latency for {@code this} entry to the specified value
* @see this#getLatency()
* @param latency to changed to
* @return {@code this}, for chaining
*/
@NonNull TabListEntry setLatency(int latency);
/**
* Gets the game mode {@code this} entry has been set to.
* <p>The number corresponds to the game mode in the following way:</p>
* <ol start="0">
* <li>Survival</li>
* <li>Creative</li>
* <li>Adventure</li>
* <li>Spectator</li>
* </ol>
* @return
*/
int getGameMode();
/**
* Sets the game mode for {@code this} entry to the specified value
* @see this#getGameMode()
* @param gameMode to change to
* @return {@code this}, for chaining
*/
TabListEntry setGameMode(int gameMode);
/**
* Returns a {@link Builder} to create a {@link TabListEntry}.
* @return {@link TabListEntry} builder
*/
static Builder builder() {
return new Builder();
}
/**
* Represents a builder which creates {@link TabListEntry}s.
* @see TabListEntry
*/
class Builder {
private TabList tabList;
private GameProfile profile;
private Component displayName;
private int latency = 0;
private int gameMode = 0;
private Builder() {}
/**
* Sets the parent {@link TabList} for this entry,
* the entry will only be able to be added to that specific {@link TabList}.
* @param tabList to set
* @return {@code this}, for chaining
*/
public Builder tabList(TabList tabList) {
this.tabList = tabList;
return this;
}
/**
* Sets the {@link GameProfile} of the {@link TabListEntry}.
* @see TabListEntry#getProfile()
* @param profile to set
* @return {@code this}, for chaining
*/
public Builder profile(GameProfile profile) {
this.profile = profile;
return this;
}
/**
* Sets the displayed name of the {@link TabListEntry}
* @see TabListEntry#getDisplayName()
* @param displayName to set
* @return {@code this}, for chaining
*/
public Builder displayName(@Nullable Component displayName) {
this.displayName = displayName;
return this;
}
/**
* Sets the latency of the {@link TabListEntry}
* @see TabListEntry#getLatency()
* @param latency to set
* @return {@code this}, for chaining
*/
public Builder latency(int latency) {
this.latency = latency;
return this;
}
/**
* Sets the game mode of the {@link TabListEntry}
* @see TabListEntry#getGameMode()
* @param gameMode to set
* @return {@code this}, for chaining
*/
public Builder gameMode(int gameMode) {
this.gameMode = gameMode;
return this;
}
/**
* Constructs the {@link TabListEntry} specified by {@code this} {@link Builder}.
* @return the constructed {@link TabListEntry}
*/
public TabListEntry build() {
Preconditions.checkState(tabList != null, "The Tablist must be set when building a TabListEntry");
Preconditions.checkState(profile != null, "The GameProfile must be set when building a TabListEntry");
return tabList.buildEntry(profile, displayName, latency, gameMode);
}
}
}

Datei anzeigen

@ -66,4 +66,5 @@ public interface MinecraftSessionHandler {
default boolean handle(TabCompleteRequest packet) { return false; } default boolean handle(TabCompleteRequest packet) { return false; }
default boolean handle(TabCompleteResponse packet) { return false; } default boolean handle(TabCompleteResponse packet) { return false; }
default boolean handle(TitlePacket packet) { return false; } default boolean handle(TitlePacket packet) { return false; }
default boolean handle(PlayerListItem packet) { return false; }
} }

Datei anzeigen

@ -12,6 +12,7 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.packet.*; import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import com.velocitypowered.proxy.tablist.VelocityTabList;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
public class BackendPlaySessionHandler implements MinecraftSessionHandler { public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@ -118,6 +119,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return true; return true;
} }
@Override
public boolean handle(PlayerListItem packet) {
serverConn.getPlayer().getTabList().processBackendPacket(packet);
return false; //Forward packet to player
}
@Override @Override
public void handleGeneric(MinecraftPacket packet) { public void handleGeneric(MinecraftPacket packet) {
if (!serverConn.getPlayer().isActive()) { if (!serverConn.getPlayer().isActive()) {

Datei anzeigen

@ -126,18 +126,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
ProtocolUtils.writeString(dataToForward, address); ProtocolUtils.writeString(dataToForward, address);
ProtocolUtils.writeUuid(dataToForward, profile.idAsUuid()); ProtocolUtils.writeUuid(dataToForward, profile.idAsUuid());
ProtocolUtils.writeString(dataToForward, profile.getName()); ProtocolUtils.writeString(dataToForward, profile.getName());
ProtocolUtils.writeVarInt(dataToForward, profile.getProperties().size()); ProtocolUtils.writeProperties(dataToForward, profile.getProperties());
for (GameProfile.Property property : profile.getProperties()) {
ProtocolUtils.writeString(dataToForward, property.getName());
ProtocolUtils.writeString(dataToForward, property.getValue());
String signature = property.getSignature();
if (signature != null) {
dataToForward.writeBoolean(true);
ProtocolUtils.writeString(dataToForward, signature);
} else {
dataToForward.writeBoolean(false);
}
}
SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256"); SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256");

Datei anzeigen

@ -4,6 +4,7 @@ import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.PluginMessageEvent; import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.PlayerChatEvent; import com.velocitypowered.api.event.player.PlayerChatEvent;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.player.TabList;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityConstants; import com.velocitypowered.proxy.connection.VelocityConstants;
@ -12,6 +13,7 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.packet.*; import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
import com.velocitypowered.proxy.tablist.VelocityTabList;
import com.velocitypowered.proxy.util.ThrowableUtils; import com.velocitypowered.proxy.util.ThrowableUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
@ -237,6 +239,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// The call will handle if the player is not a Forge player appropriately. // The call will handle if the player is not a Forge player appropriately.
player.getConnection().setCanSendLegacyFMLResetPacket(true); player.getConnection().setCanSendLegacyFMLResetPacket(true);
} else { } else {
// Clear tab list to avoid duplicate entries
TabList tabList = player.getTabList();
tabList.getEntries().forEach(entry -> tabList.removeEntry(entry.getProfile().idAsUuid()));
// Ah, this is the meat and potatoes of the whole venture! // Ah, this is the meat and potatoes of the whole venture!
// //
// In order to handle switching to another server, you will need to send three packets: // In order to handle switching to another server, you will need to send three packets:

Datei anzeigen

@ -15,6 +15,7 @@ import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.player.PlayerSettings; import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.proxy.player.TabList;
import com.velocitypowered.api.util.MessagePosition; import com.velocitypowered.api.util.MessagePosition;
import com.velocitypowered.api.util.title.TextTitle; import com.velocitypowered.api.util.title.TextTitle;
import com.velocitypowered.api.util.title.Title; import com.velocitypowered.api.util.title.Title;
@ -29,6 +30,10 @@ import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.packet.*; import com.velocitypowered.proxy.protocol.packet.*;
import com.velocitypowered.proxy.server.VelocityRegisteredServer; import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import com.velocitypowered.proxy.protocol.packet.Chat;
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.tablist.VelocityTabList;
import com.velocitypowered.proxy.util.ThrowableUtils; import com.velocitypowered.proxy.util.ThrowableUtils;
import net.kyori.text.Component; import net.kyori.text.Component;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
@ -62,10 +67,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
private VelocityServerConnection connectedServer; private VelocityServerConnection connectedServer;
private VelocityServerConnection connectionInFlight; private VelocityServerConnection connectionInFlight;
private PlayerSettings settings; private PlayerSettings settings;
private final VelocityTabList tabList;
private final VelocityServer server; private final VelocityServer server;
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, InetSocketAddress virtualHost) { ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, InetSocketAddress virtualHost) {
this.server = server; this.server = server;
this.tabList = new VelocityTabList(connection);
this.profile = profile; this.profile = profile;
this.connection = connection; this.connection = connection;
this.virtualHost = virtualHost; this.virtualHost = virtualHost;
@ -185,15 +192,18 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
} }
@Override @Override
public void setHeaderAndFooter(@NonNull Component header, @NonNull Component footer) { public void setHeaderAndFooter(Component header, Component footer) {
Preconditions.checkNotNull(header, "header"); tabList.setHeaderAndFooter(header, footer);
Preconditions.checkNotNull(footer, "footer");
connection.write(HeaderAndFooter.create(header, footer));
} }
@Override @Override
public void clearHeaderAndFooter() { public void clearHeaderAndFooter() {
connection.write(HeaderAndFooter.reset()); tabList.clearHeaderAndFooter();
}
@Override
public VelocityTabList getTabList() {
return tabList;
} }
@Override @Override

Datei anzeigen

@ -1,10 +1,13 @@
package com.velocitypowered.proxy.protocol; package com.velocitypowered.proxy.protocol;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.util.GameProfile;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID; import java.util.UUID;
public enum ProtocolUtils { ; public enum ProtocolUtils { ;
@ -79,4 +82,35 @@ public enum ProtocolUtils { ;
buf.writeLong(uuid.getMostSignificantBits()); buf.writeLong(uuid.getMostSignificantBits());
buf.writeLong(uuid.getLeastSignificantBits()); buf.writeLong(uuid.getLeastSignificantBits());
} }
public static void writeProperties(ByteBuf buf, List<GameProfile.Property> properties) {
writeVarInt(buf, properties.size());
for (GameProfile.Property property : properties) {
writeString(buf, property.getName());
writeString(buf, property.getValue());
String signature = property.getSignature();
if (signature != null) {
buf.writeBoolean(true);
writeString(buf, signature);
} else {
buf.writeBoolean(false);
}
}
}
public static List<GameProfile.Property> readProperties(ByteBuf buf) {
List<GameProfile.Property> properties = new ArrayList<>();
int size = readVarInt(buf);
for (int i = 0; i < size; i++) {
String name = readString(buf);
String value = readString(buf);
String signature = "";
boolean hasSignature = buf.readBoolean();
if (hasSignature) {
signature = readString(buf);
}
properties.add(new GameProfile.Property(name, value, signature));
}
return properties;
}
} }

Datei anzeigen

@ -118,6 +118,12 @@ public enum StateRegistry {
map(0x47, MINECRAFT_1_12, true), map(0x47, MINECRAFT_1_12, true),
map(0x48, MINECRAFT_1_12_1, true), map(0x48, MINECRAFT_1_12_1, true),
map(0x4B, MINECRAFT_1_13, true)); map(0x4B, MINECRAFT_1_13, true));
CLIENTBOUND.register(PlayerListItem.class, PlayerListItem::new,
map(0x38, MINECRAFT_1_8, false),
map(0x2D, MINECRAFT_1_9, false),
map(0x2D, MINECRAFT_1_12, false),
map(0x2E, MINECRAFT_1_12_1, false),
map(0x30, MINECRAFT_1_13, false));
} }
}, },
LOGIN { LOGIN {

Datei anzeigen

@ -0,0 +1,196 @@
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializers;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class PlayerListItem implements MinecraftPacket {
private Action action;
private List<Item> items;
public PlayerListItem(Action action, List<Item> items) {
this.action = action;
this.items = items;
}
public PlayerListItem() {}
public Action getAction() {
return action;
}
public List<Item> getItems() {
return items;
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
action = Action.values()[ProtocolUtils.readVarInt(buf)];
items = new ArrayList<>();
int length = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < length; i++) {
Item item = new Item(ProtocolUtils.readUuid(buf));
items.add(item);
switch (action) {
case ADD_PLAYER: {
item.setName(ProtocolUtils.readString(buf));
item.setProperties(ProtocolUtils.readProperties(buf));
item.setGameMode(ProtocolUtils.readVarInt(buf));
item.setLatency(ProtocolUtils.readVarInt(buf));
boolean hasDisplayName = buf.readBoolean();
if (hasDisplayName) {
item.setDisplayName(ComponentSerializers.JSON.deserialize(ProtocolUtils.readString(buf)));
}
} break;
case UPDATE_GAMEMODE:
item.setGameMode(ProtocolUtils.readVarInt(buf));
break;
case UPDATE_LATENCY:
item.setLatency(ProtocolUtils.readVarInt(buf));
break;
case UPDATE_DISPLAY_NAME: {
boolean hasDisplayName = buf.readBoolean();
if (hasDisplayName) {
item.setDisplayName(ComponentSerializers.JSON.deserialize(ProtocolUtils.readString(buf)));
}
} break;
case REMOVE_PLAYER:
//Do nothing, all that is needed is the uuid
break;
}
}
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
ProtocolUtils.writeVarInt(buf, action.ordinal());
ProtocolUtils.writeVarInt(buf, items.size());
for (Item item: items) {
ProtocolUtils.writeUuid(buf, item.getUuid());
switch (action) {
case ADD_PLAYER:
ProtocolUtils.writeString(buf, item.getName());
ProtocolUtils.writeProperties(buf, item.getProperties());
ProtocolUtils.writeVarInt(buf, item.getGameMode());
ProtocolUtils.writeVarInt(buf, item.getLatency());
writeDisplayName(buf, item.getDisplayName());
break;
case UPDATE_GAMEMODE:
ProtocolUtils.writeVarInt(buf, item.getGameMode());
break;
case UPDATE_LATENCY:
ProtocolUtils.writeVarInt(buf, item.getLatency());
break;
case UPDATE_DISPLAY_NAME:
writeDisplayName(buf, item.getDisplayName());
break;
case REMOVE_PLAYER:
//Do nothing, all that is needed is the uuid
break;
}
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
private void writeDisplayName(ByteBuf buf, Component displayName) {
buf.writeBoolean(displayName != null);
if (displayName != null) {
ProtocolUtils.writeString(buf, ComponentSerializers.JSON.serialize(displayName));
}
}
public static class Item {
private final UUID uuid;
private String name;
private List<GameProfile.Property> properties;
private int gameMode;
private int latency;
private Component displayName;
public Item(UUID uuid) {
this.uuid = uuid;
}
public static Item from(TabListEntry entry) {
return new Item(entry.getProfile().idAsUuid())
.setName(entry.getProfile().getName())
.setProperties(entry.getProfile().getProperties())
.setLatency(entry.getLatency())
.setGameMode(entry.getGameMode())
.setDisplayName(entry.getDisplayName().orElse(null));
}
public UUID getUuid() {
return uuid;
}
public String getName() {
return name;
}
public Item setName(String name) {
this.name = name;
return this;
}
public List<GameProfile.Property> getProperties() {
return properties;
}
public Item setProperties(List<GameProfile.Property> properties) {
this.properties = properties;
return this;
}
public int getGameMode() {
return gameMode;
}
public Item setGameMode(int gamemode) {
this.gameMode = gamemode;
return this;
}
public int getLatency() {
return latency;
}
public Item setLatency(int latency) {
this.latency = latency;
return this;
}
public Component getDisplayName() {
return displayName;
}
public Item setDisplayName(Component displayName) {
this.displayName = displayName;
return this;
}
}
public enum Action {
ADD_PLAYER,
UPDATE_GAMEMODE,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME,
REMOVE_PLAYER
}
}

Datei anzeigen

@ -0,0 +1,114 @@
package com.velocitypowered.proxy.tablist;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.player.TabList;
import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.UuidUtils;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
import net.kyori.text.Component;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class VelocityTabList implements TabList {
private final MinecraftConnection connection;
private final Map<UUID, TabListEntry> entries = new ConcurrentHashMap<>();
public VelocityTabList(MinecraftConnection connection) {
this.connection = connection;
}
@Override
public void setHeaderAndFooter(Component header, Component footer) {
Preconditions.checkNotNull(header, "header");
Preconditions.checkNotNull(footer, "footer");
connection.write(HeaderAndFooter.create(header, footer));
}
@Override
public void clearHeaderAndFooter() {
connection.write(HeaderAndFooter.reset());
}
@Override
public void addEntry(TabListEntry entry) {
Preconditions.checkNotNull(entry, "entry");
Preconditions.checkArgument(entry.getTabList().equals(this), "The provided entry was not created by this tab list");
Preconditions.checkArgument(!entries.containsKey(entry.getProfile().idAsUuid()), "this TabList already contains an entry with the same uuid");
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
connection.write(new PlayerListItem(PlayerListItem.Action.ADD_PLAYER, Collections.singletonList(packetItem)));
entries.put(entry.getProfile().idAsUuid(), entry);
}
@Override
public Optional<TabListEntry> removeEntry(UUID uuid) {
TabListEntry entry = entries.remove(uuid);
if (entry != null) {
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
connection.write(new PlayerListItem(PlayerListItem.Action.REMOVE_PLAYER, Collections.singletonList(packetItem)));
}
return Optional.ofNullable(entry);
}
@Override
public Collection<TabListEntry> getEntries() {
return Collections.unmodifiableCollection(this.entries.values());
}
@Override
public TabListEntry buildEntry(GameProfile profile, Component displayName, int latency, int gameMode) {
return new VelocityTabListEntry(this, profile, displayName, latency, gameMode);
}
public void processBackendPacket(PlayerListItem packet) {
//Packets are already forwarded on, so no need to do that here
for (PlayerListItem.Item item : packet.getItems()) {
UUID uuid = item.getUuid();
if (packet.getAction() != PlayerListItem.Action.ADD_PLAYER && !entries.containsKey(uuid)) {
//Sometimes UPDATE_GAMEMODE is sent before ADD_PLAYER so don't want to warn here
continue;
}
switch (packet.getAction()) {
case ADD_PLAYER:
entries.put(item.getUuid(), TabListEntry.builder()
.tabList(this)
.profile(new GameProfile(UuidUtils.toUndashed(uuid), item.getName(), item.getProperties()))
.displayName(item.getDisplayName())
.latency(item.getLatency())
.gameMode(item.getGameMode())
.build());
break;
case REMOVE_PLAYER:
entries.remove(uuid);
break;
case UPDATE_DISPLAY_NAME:
entries.get(uuid).setDisplayName(item.getDisplayName());
break;
case UPDATE_LATENCY:
entries.get(uuid).setLatency(item.getLatency());
break;
case UPDATE_GAMEMODE:
entries.get(uuid).setGameMode(item.getGameMode());
break;
}
}
}
void updateEntry(PlayerListItem.Action action, TabListEntry entry) {
if (entries.containsKey(entry.getProfile().idAsUuid())) {
PlayerListItem.Item packetItem = PlayerListItem.Item.from(entry);
connection.write(new PlayerListItem(action, Collections.singletonList(packetItem)));
}
}
}

Datei anzeigen

@ -0,0 +1,72 @@
package com.velocitypowered.proxy.tablist;
import com.velocitypowered.api.proxy.player.TabList;
import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.protocol.packet.PlayerListItem;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Optional;
public class VelocityTabListEntry implements TabListEntry {
private final VelocityTabList tabList;
private final GameProfile profile;
private Component displayName;
private int latency;
private int gameMode;
VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, Component displayName, int latency, int gameMode) {
this.tabList = tabList;
this.profile = profile;
this.displayName = displayName;
this.latency = latency;
this.gameMode = gameMode;
}
@Override
public TabList getTabList() {
return tabList;
}
@Override
public GameProfile getProfile() {
return profile;
}
@Override
public Optional<Component> getDisplayName() {
return Optional.ofNullable(displayName);
}
@Override
public TabListEntry setDisplayName(@Nullable Component displayName) {
this.displayName = displayName;
tabList.updateEntry(PlayerListItem.Action.UPDATE_DISPLAY_NAME, this);
return this;
}
@Override
public int getLatency() {
return latency;
}
@Override
public TabListEntry setLatency(int latency) {
this.latency = latency;
tabList.updateEntry(PlayerListItem.Action.UPDATE_LATENCY, this);
return this;
}
@Override
public int getGameMode() {
return gameMode;
}
@Override
public TabListEntry setGameMode(int gameMode) {
this.gameMode = gameMode;
tabList.updateEntry(PlayerListItem.Action.UPDATE_GAMEMODE, this);
return this;
}
}