3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-12-23 23:00:35 +01:00

Initial tablist implementation

Dieser Commit ist enthalten in:
Desetude 2018-09-30 20:54:50 +01:00
Ursprung 732caa2d40
Commit 61bd178591
13 geänderte Dateien mit 565 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.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,14 +89,20 @@ 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();
TabList getTabList();
/**
* Disconnects the player with the specified reason. Once this method is called, further calls to other {@link Player}
* methods will become undefined.

Datei anzeigen

@ -0,0 +1,37 @@
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}.
* TODO: Desetude
*/
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();
void addEntry(TabListEntry entry);
Optional<TabListEntry> removeEntry(UUID uuid);
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,75 @@
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.Nullable;
import java.util.Optional;
/**
* TODO: Desetude
*/
public interface TabListEntry {
TabList getTabList();
GameProfile getProfile();
Optional<Component> getDisplayName();
TabListEntry setDisplayName(@Nullable Component displayName);
int getLatency();
TabListEntry setLatency(int latency);
int getGameMode();
TabListEntry setGameMode(int gameMode);
static Builder builder() {
return new Builder();
}
class Builder {
private TabList tabList;
private GameProfile profile;
private Component displayName;
private int latency = 0;
private int gameMode = 0;
private Builder() {}
public Builder tabList(TabList tabList) {
this.tabList = tabList;
return this;
}
public Builder profile(GameProfile profile) {
this.profile = profile;
return this;
}
public Builder displayName(@Nullable Component displayName) {
this.displayName = displayName;
return this;
}
public Builder latency(int latency) {
this.latency = latency;
return this;
}
public Builder gameMode(int gameMode) {
this.gameMode = gameMode;
return this;
}
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(TabCompleteResponse 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.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()) {

Datei anzeigen

@ -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");

Datei anzeigen

@ -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 com.velocitypowered.proxy.util.ThrowableUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.text.TextComponent;

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.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

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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 {

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,113 @@
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;
public class VelocityTabList implements TabList {
private final MinecraftConnection connection;
private final Map<UUID, TabListEntry> entries = new HashMap<>();
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;
}
}