Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-11-17 13:30:17 +01:00
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.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.
|
||||||
|
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(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; }
|
||||||
}
|
}
|
||||||
|
@ -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()) {
|
||||||
|
@ -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");
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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