geforkt von Mirrors/Velocity
Merge pull request #105 from Desetude/feature/tablist
Tab List Modification
Dieser Commit ist enthalten in:
Commit
49b09713f8
@ -6,6 +6,7 @@ import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
|
||||
import com.velocitypowered.api.proxy.player.PlayerSettings;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.api.proxy.player.TabList;
|
||||
import com.velocitypowered.api.util.MessagePosition;
|
||||
import com.velocitypowered.api.util.title.Title;
|
||||
import java.util.List;
|
||||
@ -88,13 +89,23 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage
|
||||
* Sets the tab list header and footer for the player.
|
||||
* @param header the header component
|
||||
* @param footer the footer component
|
||||
* @deprecated Use {@link TabList#setHeaderAndFooter(Component, Component)}.
|
||||
*/
|
||||
@Deprecated
|
||||
void setHeaderAndFooter(Component header, Component footer);
|
||||
|
||||
/**
|
||||
* Clears the tab list header and footer for the player.
|
||||
* @deprecated Use {@link TabList#clearHeaderAndFooter()}.
|
||||
*/
|
||||
@Deprecated
|
||||
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}
|
||||
|
51
api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java
Normale Datei
51
api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java
Normale Datei
@ -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);
|
||||
}
|
176
api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java
Normale Datei
176
api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java
Normale Datei
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -66,4 +66,5 @@ public interface MinecraftSessionHandler {
|
||||
default boolean handle(TabCompleteRequest packet) { return false; }
|
||||
default boolean handle(TabCompleteResponse packet) { return false; }
|
||||
default boolean handle(TitlePacket packet) { return false; }
|
||||
default boolean handle(PlayerListItem packet) { return false; }
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import com.velocitypowered.proxy.tablist.VelocityTabList;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
@ -118,6 +119,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(PlayerListItem packet) {
|
||||
serverConn.getPlayer().getTabList().processBackendPacket(packet);
|
||||
return false; //Forward packet to player
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleGeneric(MinecraftPacket packet) {
|
||||
if (!serverConn.getPlayer().isActive()) {
|
||||
|
@ -126,18 +126,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
ProtocolUtils.writeString(dataToForward, address);
|
||||
ProtocolUtils.writeUuid(dataToForward, profile.idAsUuid());
|
||||
ProtocolUtils.writeString(dataToForward, profile.getName());
|
||||
ProtocolUtils.writeVarInt(dataToForward, profile.getProperties().size());
|
||||
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);
|
||||
}
|
||||
}
|
||||
ProtocolUtils.writeProperties(dataToForward, profile.getProperties());
|
||||
|
||||
SecretKey key = new SecretKeySpec(hmacSecret, "HmacSHA256");
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
|
@ -4,6 +4,7 @@ import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.event.player.PlayerChatEvent;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.player.TabList;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
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.packet.*;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import com.velocitypowered.proxy.tablist.VelocityTabList;
|
||||
import com.velocitypowered.proxy.util.ThrowableUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
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.
|
||||
player.getConnection().setCanSendLegacyFMLResetPacket(true);
|
||||
} 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!
|
||||
//
|
||||
// In order to handle switching to another server, you will need to send three packets:
|
||||
|
@ -15,6 +15,7 @@ import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.player.PlayerSettings;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.api.proxy.player.TabList;
|
||||
import com.velocitypowered.api.util.MessagePosition;
|
||||
import com.velocitypowered.api.util.title.TextTitle;
|
||||
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.packet.*;
|
||||
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 net.kyori.text.Component;
|
||||
import net.kyori.text.TextComponent;
|
||||
@ -62,10 +67,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
private VelocityServerConnection connectedServer;
|
||||
private VelocityServerConnection connectionInFlight;
|
||||
private PlayerSettings settings;
|
||||
private final VelocityTabList tabList;
|
||||
private final VelocityServer server;
|
||||
|
||||
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, InetSocketAddress virtualHost) {
|
||||
this.server = server;
|
||||
this.tabList = new VelocityTabList(connection);
|
||||
this.profile = profile;
|
||||
this.connection = connection;
|
||||
this.virtualHost = virtualHost;
|
||||
@ -185,15 +192,18 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeaderAndFooter(@NonNull Component header, @NonNull Component footer) {
|
||||
Preconditions.checkNotNull(header, "header");
|
||||
Preconditions.checkNotNull(footer, "footer");
|
||||
connection.write(HeaderAndFooter.create(header, footer));
|
||||
public void setHeaderAndFooter(Component header, Component footer) {
|
||||
tabList.setHeaderAndFooter(header, footer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHeaderAndFooter() {
|
||||
connection.write(HeaderAndFooter.reset());
|
||||
tabList.clearHeaderAndFooter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VelocityTabList getTabList() {
|
||||
return tabList;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,10 +1,13 @@
|
||||
package com.velocitypowered.proxy.protocol;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public enum ProtocolUtils { ;
|
||||
@ -79,4 +82,35 @@ public enum ProtocolUtils { ;
|
||||
buf.writeLong(uuid.getMostSignificantBits());
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -118,6 +118,12 @@ public enum StateRegistry {
|
||||
map(0x47, MINECRAFT_1_12, true),
|
||||
map(0x48, MINECRAFT_1_12_1, 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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren