3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-06 08:10:12 +01:00

add event to modify and/or block specific updates to the tablist of a player

Inspired by https://github.com/PaperMC/Velocity/pull/874
Dieser Commit ist enthalten in:
Timongcraft 2024-09-15 13:22:46 +02:00
Ursprung 78f6cfc26c
Commit ce18534ce2
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 58C9C6C8E8E9E384
9 geänderte Dateien mit 864 neuen und 68 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,193 @@
/*
* Copyright (C) 2019-2024 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.event.annotation.AwaitingEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.player.TabListEntry;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* This event is fired, when a {@link com.velocitypowered.api.proxy.player.TabList Tablist} is updated.
* It can be used to override or cancel updates for {@link TabListEntry}s.
* Velocity will wait for this event to finish firing before forwarding it to the server.
*
* <p>Note: If the {@code actions} contain {@link Action#REMOVE_PLAYER Action.REMOVE_PLAYER}, that may be the only action.
*
* <p><b>Version-specific behavior:</b>
* <li>For versions below 1.19.3, {@code actions} may only contain one action, and if that action
* is {@link Action#ADD_PLAYER Action.ADD_PLAYER}, the values normally set by other actions
* (e.g., {@link Action#UPDATE_GAME_MODE Action.UPDATE_GAME_MODE}) may still be set.
* <li>For versions below 1.8, {@code actions} may only contain {@link Action#ADD_PLAYER Action.ADD_PLAYER}
* or {@link Action#REMOVE_PLAYER Action.REMOVE_PLAYER}. {@link Action#ADD_PLAYER Action.ADD_PLAYER} may also act as a replacement
* for actions like {@link Action#UPDATE_LATENCY Action.UPDATE_LATENCY}}.
*/
@AwaitingEvent
public class ServerUpdateTabListEvent implements ResultedEvent<ServerUpdateTabListEvent.TabListUpdateResult> {
private final Player player;
private final Set<Action> actions;
private final List<TabListEntry> entries;
private TabListUpdateResult result;
/**
* Constructs a {@link ServerUpdateTabListEvent} instance.
*
* @param player the player for whom the tab list is being updated
* @param actions the {@link Action Action}s from the server for this tab list update
* @param entries the {@link TabListEntry}s in their updated form
*/
public ServerUpdateTabListEvent(Player player, Set<Action> actions, List<TabListEntry> entries) {
this.player = Preconditions.checkNotNull(player, "player");
this.actions = Preconditions.checkNotNull(actions, "actions");
this.entries = Preconditions.checkNotNull(entries, "entries");
this.result = TabListUpdateResult.allowed();
}
public Player getPlayer() {
return player;
}
public Set<Action> getActions() {
return actions;
}
/**
* The updated {@link TabListEntry}s that will be applied to the {@link com.velocitypowered.api.proxy.player.TabList Tablist}
* of the {@code player} (or in the case of {@link Action#REMOVE_PLAYER Action.REMOVE_PLAYER} removed).
*
* @return the updated entries, normally immutable
*/
public List<TabListEntry> getEntries() {
return entries;
}
@Override
public TabListUpdateResult getResult() {
return result;
}
@Override
public void setResult(TabListUpdateResult result) {
this.result = Preconditions.checkNotNull(result, "result");
}
@Override
public String toString() {
return "ServerUpdateTabListEvent{"
+ "player=" + player
+ ", actions=" + actions
+ ", entries=" + entries
+ '}';
}
/**
* Represents an action of the {@link ServerUpdateTabListEvent}.
*/
public enum Action {
/**
* Add new players to the player list.
*/
ADD_PLAYER,
/**
* Initialize the chat session for the entries.
*/
INITIALIZE_CHAT,
/**
* Update the gamemode for the entries.
*/
UPDATE_GAME_MODE,
/**
* Update the latency for the entries.
*/
UPDATE_LISTED,
/**
* Update the latency for the entries.
*/
UPDATE_LATENCY,
/**
* Update the display name for the specific entries.
*/
UPDATE_DISPLAY_NAME,
/**
* Remove players from the player list.
*/
REMOVE_PLAYER
}
/**
* Represents the result of the {@link ServerUpdateTabListEvent}.
*/
public static final class TabListUpdateResult implements ResultedEvent.Result {
private static final TabListUpdateResult ALLOWED = new TabListUpdateResult(true);
private static final TabListUpdateResult DENIED = new TabListUpdateResult(false);
private final boolean status;
private final Set<UUID> ids;
public TabListUpdateResult(boolean status) {
this.status = status;
ids = Collections.emptySet();
}
public TabListUpdateResult(boolean status, Set<UUID> ids) {
this.status = status;
this.ids = ids;
}
@Override
public boolean isAllowed() {
return status;
}
public Set<UUID> getIds() {
return ids;
}
/**
* Allows the {@link TabListEntry}s to be updated, with or without modification.
*
* @return the allowed result
*/
public static TabListUpdateResult allowed() {
return ALLOWED;
}
/**
* Prevents the {@link TabListEntry}s from being updated.
*
* @return the denied result
*/
public static TabListUpdateResult denied() {
return DENIED;
}
/**
* Only allows specific {@link TabListEntry}s to be updated.
* The updates for the remaining entries will be dropped.
*
* <p>Note: You can get the id of an entry with {@link TabListEntry#getProfile()}{@link com.velocitypowered.api.util.GameProfile#getId() .getId()}
*
* @param allowedOnly A non-empty set of ids of the entries that should be updated
* @return a result with the specified entries to be updated
*/
public static TabListUpdateResult allowedSpecific(final Set<UUID> allowedOnly) {
Preconditions.checkNotNull(allowedOnly, "allowedOnly");
Preconditions.checkArgument(!allowedOnly.isEmpty(), "allowedOnly empty");
return new TabListUpdateResult(true, allowedOnly);
}
}
}

Datei anzeigen

@ -334,20 +334,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(LegacyPlayerListItemPacket packet) { public boolean handle(LegacyPlayerListItemPacket packet) {
serverConn.getPlayer().getTabList().processLegacy(packet); serverConn.getPlayer().getTabList().processLegacyUpdate(packet);
return false; return true;
} }
@Override @Override
public boolean handle(UpsertPlayerInfoPacket packet) { public boolean handle(UpsertPlayerInfoPacket packet) {
serverConn.getPlayer().getTabList().processUpdate(packet); serverConn.getPlayer().getTabList().processUpdate(packet);
return false; return true;
} }
@Override @Override
public boolean handle(RemovePlayerInfoPacket packet) { public boolean handle(RemovePlayerInfoPacket packet) {
serverConn.getPlayer().getTabList().processRemove(packet); serverConn.getPlayer().getTabList().processRemove(packet);
return false; return true;
} }
@Override @Override

Datei anzeigen

@ -202,7 +202,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
this.onlineMode = onlineMode; this.onlineMode = onlineMode;
if (connection.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) { if (connection.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
this.tabList = new VelocityTabList(this); this.tabList = new VelocityTabList(this, server);
} else if (connection.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) { } else if (connection.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
this.tabList = new KeyedVelocityTabList(this, server); this.tabList = new KeyedVelocityTabList(this, server);
} else { } else {

Datei anzeigen

@ -30,7 +30,7 @@ public interface InternalTabList extends TabList {
Player getPlayer(); Player getPlayer();
default void processLegacy(LegacyPlayerListItemPacket packet) { default void processLegacyUpdate(LegacyPlayerListItemPacket packet) {
} }
default void processUpdate(UpsertPlayerInfoPacket infoPacket) { default void processUpdate(UpsertPlayerInfoPacket infoPacket) {

Datei anzeigen

@ -18,6 +18,7 @@
package com.velocitypowered.proxy.tablist; package com.velocitypowered.proxy.tablist;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.player.ServerUpdateTabListEvent;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey; import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
@ -35,6 +36,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
@ -78,20 +80,27 @@ public class KeyedVelocityTabList implements InternalTabList {
} }
@Override @Override
public void addEntry(TabListEntry entry) { public void addEntry(TabListEntry entry1) {
KeyedVelocityTabListEntry entry;
if (entry1 instanceof KeyedVelocityTabListEntry) {
entry = (KeyedVelocityTabListEntry) entry1;
} else {
entry = new KeyedVelocityTabListEntry(this, entry1.getProfile(),
entry1.getDisplayNameComponent().orElse(null),
entry1.getLatency(), entry1.getGameMode(), entry1.getIdentifiedKey());
}
Preconditions.checkNotNull(entry, "entry"); Preconditions.checkNotNull(entry, "entry");
Preconditions.checkArgument(entry.getTabList().equals(this), Preconditions.checkArgument(entry.getTabList().equals(this),
"The provided entry was not created by this tab list"); "The provided entry was not created by this tab list");
Preconditions.checkArgument(!entries.containsKey(entry.getProfile().getId()), Preconditions.checkArgument(!entries.containsKey(entry.getProfile().getId()),
"this TabList already contains an entry with the same uuid"); "this TabList already contains an entry with the same uuid");
Preconditions.checkArgument(entry instanceof KeyedVelocityTabListEntry,
"Not a Velocity tab list entry");
LegacyPlayerListItemPacket.Item packetItem = LegacyPlayerListItemPacket.Item.from(entry); LegacyPlayerListItemPacket.Item packetItem = LegacyPlayerListItemPacket.Item.from(entry);
connection.write( connection.write(
new LegacyPlayerListItemPacket(LegacyPlayerListItemPacket.ADD_PLAYER, new LegacyPlayerListItemPacket(LegacyPlayerListItemPacket.ADD_PLAYER,
Collections.singletonList(packetItem))); Collections.singletonList(packetItem)));
entries.put(entry.getProfile().getId(), (KeyedVelocityTabListEntry) entry); entries.put(entry.getProfile().getId(), entry);
} }
@Override @Override
@ -166,20 +175,178 @@ public class KeyedVelocityTabList implements InternalTabList {
} }
@Override @Override
public void processLegacy(LegacyPlayerListItemPacket packet) { public void processLegacyUpdate(LegacyPlayerListItemPacket packet) {
// Packets are already forwarded on, so no need to do that here ServerUpdateTabListEvent.Action action = mapToEventAction(packet.getAction());
for (LegacyPlayerListItemPacket.Item item : packet.getItems()) { Preconditions.checkNotNull(action, "action");
UUID uuid = item.getUuid();
assert uuid != null : "1.7 tab list entry given to modern tab list handler!";
if (packet.getAction() != LegacyPlayerListItemPacket.ADD_PLAYER List<UpdateEventTabListEntry> entries = mapToEventEntries(packet.getAction(), packet.getItems());
&& !entries.containsKey(uuid)) {
proxyServer.getEventManager().fire(
new ServerUpdateTabListEvent(
player,
Set.of(action),
Collections.unmodifiableList(entries)
)
).thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
if (event.getResult().getIds().isEmpty()) {
boolean rewrite = false;
for (UpdateEventTabListEntry entry : entries) {
if (entry.isRewrite()) {
rewrite = true;
break;
}
}
if (rewrite) {
//listeners have modified entries, requires manual processing
if (action != ServerUpdateTabListEvent.Action.REMOVE_PLAYER) {
for (UpdateEventTabListEntry entry : entries) {
if (this.entries.containsKey(entry.getProfile().getId())) {
removeEntry(entry.getProfile().getId());
}
addEntry(entry);
}
} else {
for (UpdateEventTabListEntry entry : entries) {
removeEntry(entry.getProfile().getId());
}
}
} else {
//listeners haven't modified entries
for (LegacyPlayerListItemPacket.Item item : packet.getItems()) {
processLegacy(packet.getAction(), item);
}
connection.write(packet);
}
} else {
//listeners have denied entries (and may have modified others), requires manual processing
if (action != ServerUpdateTabListEvent.Action.REMOVE_PLAYER) {
for (UpdateEventTabListEntry entry : entries) {
if (event.getResult().getIds().contains(entry.getProfile().getId())) {
if (this.entries.containsKey(entry.getProfile().getId())) {
removeEntry(entry.getProfile().getId());
}
addEntry(entry);
}
}
} else {
for (UpdateEventTabListEntry entry : entries) {
if (event.getResult().getIds().contains(entry.getProfile().getId())) {
removeEntry(entry.getProfile().getId());
}
}
}
}
}
});
}
protected ServerUpdateTabListEvent.@Nullable Action mapToEventAction(int action) {
return switch (action) {
case LegacyPlayerListItemPacket.ADD_PLAYER -> ServerUpdateTabListEvent.Action.ADD_PLAYER;
case LegacyPlayerListItemPacket.REMOVE_PLAYER -> ServerUpdateTabListEvent.Action.REMOVE_PLAYER;
case LegacyPlayerListItemPacket.UPDATE_GAMEMODE -> ServerUpdateTabListEvent.Action.UPDATE_GAME_MODE;
case LegacyPlayerListItemPacket.UPDATE_LATENCY -> ServerUpdateTabListEvent.Action.UPDATE_LATENCY;
case LegacyPlayerListItemPacket.UPDATE_DISPLAY_NAME -> ServerUpdateTabListEvent.Action.UPDATE_DISPLAY_NAME;
default -> null;
};
}
private List<UpdateEventTabListEntry> mapToEventEntries(int action, List<LegacyPlayerListItemPacket.Item> packetItems) {
List<UpdateEventTabListEntry> entries = new ArrayList<>(packetItems.size());
for (LegacyPlayerListItemPacket.Item item : packetItems) {
UUID uuid = item.getUuid();
Preconditions.checkNotNull(uuid, "1.7 tab list entry given to modern tab list handler!");
if (action != LegacyPlayerListItemPacket.ADD_PLAYER
&& !this.entries.containsKey(uuid)) {
// Sometimes UPDATE_GAMEMODE is sent before ADD_PLAYER so don't want to warn here // Sometimes UPDATE_GAMEMODE is sent before ADD_PLAYER so don't want to warn here
continue; continue;
} }
switch (packet.getAction()) { UpdateEventTabListEntry currentEntry = null;
case LegacyPlayerListItemPacket.ADD_PLAYER: { KeyedVelocityTabListEntry oldCurrentEntry = this.entries.get(uuid);
if (oldCurrentEntry != null) {
currentEntry = new UpdateEventTabListEntry(
this,
oldCurrentEntry.getProfile(),
oldCurrentEntry.getDisplayNameComponent().orElse(null),
oldCurrentEntry.getLatency(),
oldCurrentEntry.getGameMode(),
oldCurrentEntry.getChatSession(),
oldCurrentEntry.isListed()
);
}
switch (action) {
case LegacyPlayerListItemPacket.ADD_PLAYER -> {
// ensure that name and properties are available
String name = item.getName();
List<GameProfile.Property> properties = item.getProperties();
if (name == null || properties == null) {
throw new IllegalStateException("Got null game profile for ADD_PLAYER");
}
currentEntry = new UpdateEventTabListEntry(
this,
new GameProfile(uuid, name, properties),
item.getDisplayName(),
item.getLatency(),
item.getGameMode(),
new RemoteChatSession(null, item.getPlayerKey()),
true
);
}
case LegacyPlayerListItemPacket.REMOVE_PLAYER -> {
//Nothing should be done here, as all entries which are not allowed are removed
// if the action is ServerUpdateTabListEvent.Action.REMOVE_PLAYER
}
case LegacyPlayerListItemPacket.UPDATE_DISPLAY_NAME -> {
if (currentEntry != null) {
currentEntry.setDisplayNameWithoutRewrite(item.getDisplayName());
}
}
case LegacyPlayerListItemPacket.UPDATE_LATENCY -> {
if (currentEntry != null) {
currentEntry.setLatencyWithoutRewrite(item.getLatency());
}
}
case LegacyPlayerListItemPacket.UPDATE_GAMEMODE -> {
if (currentEntry != null) {
currentEntry.setGameModeWithoutRewrite(item.getGameMode());
}
}
default -> {
// Nothing we can do here
}
}
if (currentEntry != null) {
entries.add(currentEntry);
}
}
return entries;
}
private void processLegacy(int action, LegacyPlayerListItemPacket.Item item) {
UUID uuid = item.getUuid();
assert uuid != null : "1.7 tab list entry given to modern tab list handler!";
if (action != LegacyPlayerListItemPacket.ADD_PLAYER
&& !entries.containsKey(uuid)) {
// Sometimes UPDATE_GAMEMODE is sent before ADD_PLAYER so don't want to warn here
return;
}
switch (action) {
case LegacyPlayerListItemPacket.ADD_PLAYER -> {
// ensure that name and properties are available // ensure that name and properties are available
String name = item.getName(); String name = item.getName();
List<GameProfile.Property> properties = item.getProperties(); List<GameProfile.Property> properties = item.getProperties();
@ -195,35 +362,28 @@ public class KeyedVelocityTabList implements InternalTabList {
.chatSession(new RemoteChatSession(null, item.getPlayerKey())) .chatSession(new RemoteChatSession(null, item.getPlayerKey()))
.gameMode(item.getGameMode()) .gameMode(item.getGameMode())
.build()); .build());
break;
} }
case LegacyPlayerListItemPacket.REMOVE_PLAYER: case LegacyPlayerListItemPacket.REMOVE_PLAYER -> entries.remove(uuid);
entries.remove(uuid); case LegacyPlayerListItemPacket.UPDATE_DISPLAY_NAME -> {
break;
case LegacyPlayerListItemPacket.UPDATE_DISPLAY_NAME: {
KeyedVelocityTabListEntry entry = entries.get(uuid); KeyedVelocityTabListEntry entry = entries.get(uuid);
if (entry != null) { if (entry != null) {
entry.setDisplayNameInternal(item.getDisplayName()); entry.setDisplayNameInternal(item.getDisplayName());
} }
break;
} }
case LegacyPlayerListItemPacket.UPDATE_LATENCY: { case LegacyPlayerListItemPacket.UPDATE_LATENCY -> {
KeyedVelocityTabListEntry entry = entries.get(uuid); KeyedVelocityTabListEntry entry = entries.get(uuid);
if (entry != null) { if (entry != null) {
entry.setLatencyInternal(item.getLatency()); entry.setLatencyInternal(item.getLatency());
} }
break;
} }
case LegacyPlayerListItemPacket.UPDATE_GAMEMODE: { case LegacyPlayerListItemPacket.UPDATE_GAMEMODE -> {
KeyedVelocityTabListEntry entry = entries.get(uuid); KeyedVelocityTabListEntry entry = entries.get(uuid);
if (entry != null) { if (entry != null) {
entry.setGameModeInternal(item.getGameMode()); entry.setGameModeInternal(item.getGameMode());
} }
break;
} }
default: default -> {
// Nothing we can do here // Nothing we can do here
break;
} }
} }
} }

Datei anzeigen

@ -0,0 +1,145 @@
/*
* Copyright (C) 2018-2024 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.tablist;
import com.velocitypowered.api.event.player.ServerUpdateTabListEvent;
import com.velocitypowered.api.proxy.player.ChatSession;
import com.velocitypowered.api.proxy.player.TabList;
import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile;
import java.util.Optional;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents a {@link TabListEntry} of the {@link ServerUpdateTabListEvent}.
*/
public class UpdateEventTabListEntry implements TabListEntry {
private final TabList tabList;
private final GameProfile profile;
private @Nullable Component displayName;
private int latency;
private int gameMode;
private boolean listed;
private @Nullable ChatSession session;
private boolean rewrite = false;
/**
* Constructs an instance.
*/
public UpdateEventTabListEntry(TabList tabList, GameProfile profile, @Nullable Component displayName,
int latency,
int gameMode, @Nullable ChatSession session, boolean listed) {
this.tabList = tabList;
this.profile = profile;
this.displayName = displayName;
this.latency = latency;
this.gameMode = gameMode;
this.session = session;
this.listed = listed;
}
@Override
public @Nullable ChatSession getChatSession() {
return this.session;
}
@Override
public TabList getTabList() {
return this.tabList;
}
@Override
public GameProfile getProfile() {
return this.profile;
}
@Override
public Optional<Component> getDisplayNameComponent() {
return Optional.ofNullable(displayName);
}
@Override
public TabListEntry setDisplayName(@Nullable Component displayName) {
this.displayName = displayName;
rewrite = true;
return this;
}
void setDisplayNameWithoutRewrite(@Nullable Component displayName) {
this.displayName = displayName;
}
@Override
public int getLatency() {
return this.latency;
}
@Override
public TabListEntry setLatency(int latency) {
this.latency = latency;
rewrite = true;
return this;
}
void setLatencyWithoutRewrite(int latency) {
this.latency = latency;
}
@Override
public int getGameMode() {
return this.gameMode;
}
@Override
public TabListEntry setGameMode(int gameMode) {
this.gameMode = gameMode;
rewrite = true;
return this;
}
void setGameModeWithoutRewrite(int gameMode) {
this.gameMode = gameMode;
}
void setChatSessionWithoutRewrite(@Nullable ChatSession session) {
this.session = session;
}
@Override
public boolean isListed() {
return listed;
}
@Override
public TabListEntry setListed(boolean listed) {
this.listed = listed;
rewrite = true;
return this;
}
void setListedWithoutRewrite(boolean listed) {
this.listed = listed;
}
boolean isRewrite() {
return rewrite;
}
}

Datei anzeigen

@ -19,7 +19,9 @@ package com.velocitypowered.proxy.tablist;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.velocitypowered.api.event.player.ServerUpdateTabListEvent;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.player.ChatSession; import com.velocitypowered.api.proxy.player.ChatSession;
import com.velocitypowered.api.proxy.player.TabListEntry; import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
@ -32,11 +34,13 @@ import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession; import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -51,6 +55,7 @@ public class VelocityTabList implements InternalTabList {
private static final Logger logger = LogManager.getLogger(VelocityConsole.class); private static final Logger logger = LogManager.getLogger(VelocityConsole.class);
private final ConnectedPlayer player; private final ConnectedPlayer player;
private final MinecraftConnection connection; private final MinecraftConnection connection;
protected final ProxyServer proxyServer;
private final Map<UUID, VelocityTabListEntry> entries; private final Map<UUID, VelocityTabListEntry> entries;
/** /**
@ -58,8 +63,9 @@ public class VelocityTabList implements InternalTabList {
* *
* @param player player associated with this tab list * @param player player associated with this tab list
*/ */
public VelocityTabList(ConnectedPlayer player) { public VelocityTabList(ConnectedPlayer player, final ProxyServer proxyServer) {
this.player = player; this.player = player;
this.proxyServer = proxyServer;
this.connection = player.getConnection(); this.connection = player.getConnection();
this.entries = Maps.newConcurrentMap(); this.entries = Maps.newConcurrentMap();
} }
@ -214,9 +220,45 @@ public class VelocityTabList implements InternalTabList {
@Override @Override
public void processUpdate(UpsertPlayerInfoPacket infoPacket) { public void processUpdate(UpsertPlayerInfoPacket infoPacket) {
List<UpdateEventTabListEntry> entries = mapToEventEntries(infoPacket.getActions(), infoPacket.getEntries());
proxyServer.getEventManager().fire(new ServerUpdateTabListEvent(player,
Collections.unmodifiableSet(mapToEventActions(infoPacket.getActions())),
Collections.unmodifiableList(entries))
).thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
if (event.getResult().getIds().isEmpty()) {
boolean rewrite = false;
for (UpdateEventTabListEntry entry : entries) {
if (entry.isRewrite()) {
rewrite = true;
break;
}
}
if (rewrite) {
//listeners have modified entries, requires manual processing
for (UpdateEventTabListEntry entry : entries) {
addEntry(entry);
}
} else {
//listeners haven't modified entries
for (UpsertPlayerInfoPacket.Entry entry : infoPacket.getEntries()) { for (UpsertPlayerInfoPacket.Entry entry : infoPacket.getEntries()) {
processUpsert(infoPacket.getActions(), entry); processUpsert(infoPacket.getActions(), entry);
} }
connection.write(infoPacket);
}
} else {
//listeners have denied entries (and may have modified others), requires manual processing
for (UpdateEventTabListEntry entry : entries) {
if (event.getResult().getIds().contains(entry.getProfile().getId())) {
addEntry(entry);
}
}
}
}
});
} }
protected UpsertPlayerInfoPacket.Entry createRawEntry(VelocityTabListEntry entry) { protected UpsertPlayerInfoPacket.Entry createRawEntry(VelocityTabListEntry entry) {
@ -231,6 +273,134 @@ public class VelocityTabList implements InternalTabList {
this.connection.write(new UpsertPlayerInfoPacket(EnumSet.of(action), List.of(entry))); this.connection.write(new UpsertPlayerInfoPacket(EnumSet.of(action), List.of(entry)));
} }
private EnumSet<ServerUpdateTabListEvent.Action> mapToEventActions(EnumSet<UpsertPlayerInfoPacket.Action> packetActions) {
EnumSet<ServerUpdateTabListEvent.Action> actions = EnumSet.noneOf(ServerUpdateTabListEvent.Action.class);
for (UpsertPlayerInfoPacket.Action packetAction : packetActions) {
switch (packetAction) {
case ADD_PLAYER -> {
actions.add(ServerUpdateTabListEvent.Action.ADD_PLAYER);
}
case INITIALIZE_CHAT -> {
actions.add(ServerUpdateTabListEvent.Action.INITIALIZE_CHAT);
}
case UPDATE_GAME_MODE -> {
actions.add(ServerUpdateTabListEvent.Action.UPDATE_GAME_MODE);
}
case UPDATE_LISTED -> {
actions.add(ServerUpdateTabListEvent.Action.UPDATE_LISTED);
}
case UPDATE_LATENCY -> {
actions.add(ServerUpdateTabListEvent.Action.UPDATE_LATENCY);
}
case UPDATE_DISPLAY_NAME -> {
actions.add(ServerUpdateTabListEvent.Action.UPDATE_DISPLAY_NAME);
}
default -> {
// Nothing we can do here
}
}
}
return actions;
}
private List<UpdateEventTabListEntry> mapToEventEntries(EnumSet<UpsertPlayerInfoPacket.Action> actions,
List<UpsertPlayerInfoPacket.Entry> packetEntries) {
List<UpdateEventTabListEntry> entries = new ArrayList<>(packetEntries.size());
for (UpsertPlayerInfoPacket.Entry rawEntry : packetEntries) {
Preconditions.checkNotNull(rawEntry.getProfileId(), "Profile ID cannot be null");
UUID profileId = rawEntry.getProfileId();
UpdateEventTabListEntry currentEntry = null;
VelocityTabListEntry oldCurrentEntry = this.entries.get(profileId);
if (oldCurrentEntry != null) {
currentEntry = new UpdateEventTabListEntry(
this,
oldCurrentEntry.getProfile(),
oldCurrentEntry.getDisplayNameComponent().orElse(null),
oldCurrentEntry.getLatency(),
oldCurrentEntry.getGameMode(),
oldCurrentEntry.getChatSession(),
oldCurrentEntry.isListed()
);
}
if (actions.contains(UpsertPlayerInfoPacket.Action.ADD_PLAYER)) {
if (currentEntry == null) {
currentEntry = new UpdateEventTabListEntry(
this,
rawEntry.getProfile(),
null,
0,
-1,
null,
false
);
} else {
logger.debug("Received an add player packet for an existing entry; this does nothing.");
}
} else if (currentEntry == null) {
logger.debug(
"Received a partial player before an ADD_PLAYER action; profile could not be built. {}",
rawEntry);
continue;
} else {
currentEntry = new UpdateEventTabListEntry(
this,
currentEntry.getProfile(),
currentEntry.getDisplayNameComponent().orElse(null),
currentEntry.getLatency(),
currentEntry.getGameMode(),
currentEntry.getChatSession(),
currentEntry.isListed()
);
}
if (actions.contains(UpsertPlayerInfoPacket.Action.UPDATE_GAME_MODE)) {
currentEntry.setGameModeWithoutRewrite(rawEntry.getGameMode());
}
if (actions.contains(UpsertPlayerInfoPacket.Action.UPDATE_LATENCY)) {
currentEntry.setLatencyWithoutRewrite(rawEntry.getLatency());
}
if (actions.contains(UpsertPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME)) {
currentEntry.setDisplayNameWithoutRewrite(rawEntry.getDisplayName() != null
? rawEntry.getDisplayName().getComponent() : null);
}
if (actions.contains(UpsertPlayerInfoPacket.Action.INITIALIZE_CHAT)) {
currentEntry.setChatSessionWithoutRewrite(rawEntry.getChatSession());
}
if (actions.contains(UpsertPlayerInfoPacket.Action.UPDATE_LISTED)) {
currentEntry.setListedWithoutRewrite(rawEntry.isListed());
}
entries.add(currentEntry);
}
return entries;
}
private List<UpdateEventTabListEntry> mapToEventEntries(Collection<UUID> uuids) {
List<UpdateEventTabListEntry> entries = new ArrayList<>();
for (Map.Entry<UUID, VelocityTabListEntry> entry : this.entries.entrySet()) {
if (uuids.contains(entry.getKey())) {
entries.add(
new UpdateEventTabListEntry(
this,
entry.getValue().getProfile(),
entry.getValue().getDisplayNameComponent().orElse(null),
entry.getValue().getLatency(),
entry.getValue().getGameMode(),
entry.getValue().getChatSession(),
entry.getValue().isListed()
)
);
}
}
return entries;
}
private void processUpsert(EnumSet<UpsertPlayerInfoPacket.Action> actions, private void processUpsert(EnumSet<UpsertPlayerInfoPacket.Action> actions,
UpsertPlayerInfoPacket.Entry entry) { UpsertPlayerInfoPacket.Entry entry) {
Preconditions.checkNotNull(entry.getProfileId(), "Profile ID cannot be null"); Preconditions.checkNotNull(entry.getProfileId(), "Profile ID cannot be null");
@ -278,8 +448,34 @@ public class VelocityTabList implements InternalTabList {
@Override @Override
public void processRemove(RemovePlayerInfoPacket infoPacket) { public void processRemove(RemovePlayerInfoPacket infoPacket) {
List<UpdateEventTabListEntry> entries = mapToEventEntries(infoPacket.getProfilesToRemove());
proxyServer.getEventManager().fire(
new ServerUpdateTabListEvent(
player,
Set.of(ServerUpdateTabListEvent.Action.REMOVE_PLAYER),
Collections.unmodifiableList(entries)
)
).thenAcceptAsync(event -> { //not sure what should be used here!
if (event.getResult().isAllowed()) {
if (event.getResult().getIds().isEmpty()) {
for (UUID uuid : infoPacket.getProfilesToRemove()) { for (UUID uuid : infoPacket.getProfilesToRemove()) {
this.entries.remove(uuid); this.entries.remove(uuid);
} }
connection.write(infoPacket);
} else {
List<UUID> uuids = new ArrayList<>();
for (UUID uuid : infoPacket.getProfilesToRemove()) {
if (event.getResult().getIds().contains(uuid)) {
this.entries.remove(uuid);
uuids.add(uuid);
}
}
this.connection.write(new RemovePlayerInfoPacket(uuids));
}
}
});
} }
} }

Datei anzeigen

@ -34,7 +34,7 @@ public class VelocityTabListEntry implements TabListEntry {
private final VelocityTabList tabList; private final VelocityTabList tabList;
private final GameProfile profile; private final GameProfile profile;
private Component displayName; private @Nullable Component displayName;
private int latency; private int latency;
private int gameMode; private int gameMode;
private boolean listed; private boolean listed;
@ -43,7 +43,7 @@ public class VelocityTabListEntry implements TabListEntry {
/** /**
* Constructs the instance. * Constructs the instance.
*/ */
public VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, Component displayName, public VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, @Nullable Component displayName,
int latency, int latency,
int gameMode, @Nullable ChatSession session, boolean listed) { int gameMode, @Nullable ChatSession session, boolean listed) {
this.tabList = tabList; this.tabList = tabList;

Datei anzeigen

@ -17,16 +17,18 @@
package com.velocitypowered.proxy.tablist; package com.velocitypowered.proxy.tablist;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.event.player.ServerUpdateTabListEvent;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.player.TabListEntry; import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket; import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket;
import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket.Item;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
@ -82,11 +84,111 @@ public class VelocityTabListLegacy extends KeyedVelocityTabList {
} }
@Override @Override
public void processLegacy(LegacyPlayerListItemPacket packet) { public void processLegacyUpdate(LegacyPlayerListItemPacket packet) {
Item item = packet.getItems().get(0); // Only one item per packet in 1.7 ServerUpdateTabListEvent.Action action = mapToEventAction(packet.getAction());
Preconditions.checkNotNull(action, "action");
switch (packet.getAction()) { UpdateEventTabListEntry entry = mapToEventEntry(packet.getAction(), packet.getItems().get(0)); // Only one item per packet in 1.7
case LegacyPlayerListItemPacket.ADD_PLAYER:
proxyServer.getEventManager().fire(
new ServerUpdateTabListEvent(
player,
Set.of(action),
Collections.singletonList(entry)
)
).thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
if (event.getResult().getIds().isEmpty()) {
if (entry.isRewrite()) {
//listeners have modified the entry, requires manual processing
if (action != ServerUpdateTabListEvent.Action.REMOVE_PLAYER) {
if (this.entries.containsKey(entry.getProfile().getId())) {
removeEntry(entry.getProfile().getId());
}
addEntry(entry);
} else {
removeEntry(entry.getProfile().getId());
}
} else {
//listeners haven't modified the entry
processLegacy(packet.getAction(), packet.getItems().get(0));
connection.write(packet);
}
} else {
//listeners have denied entries (and may have modified others), requires manual processing
// (doesn't make much sense as there can only be one entry)
if (action != ServerUpdateTabListEvent.Action.REMOVE_PLAYER) {
if (event.getResult().getIds().contains(entry.getProfile().getId())) {
if (this.entries.containsKey(entry.getProfile().getId())) {
removeEntry(entry.getProfile().getId());
}
addEntry(entry);
}
} else {
if (event.getResult().getIds().contains(entry.getProfile().getId())) {
removeEntry(entry.getProfile().getId());
}
}
}
}
});
}
private UpdateEventTabListEntry mapToEventEntry(int action, LegacyPlayerListItemPacket.Item packetItem) {
UpdateEventTabListEntry currentEntry = null;
switch (action) {
case LegacyPlayerListItemPacket.ADD_PLAYER -> {
if (nameMapping.containsKey(packetItem.getName())) { // ADD_PLAYER also used for updating ping
KeyedVelocityTabListEntry oldCurrentEntry = this.entries.get(nameMapping.get(packetItem.getName()));
if (oldCurrentEntry != null) {
currentEntry = new UpdateEventTabListEntry(
this,
oldCurrentEntry.getProfile(),
oldCurrentEntry.getDisplayNameComponent().orElse(null),
oldCurrentEntry.getLatency(),
oldCurrentEntry.getGameMode(),
oldCurrentEntry.getChatSession(),
oldCurrentEntry.isListed()
);
}
if (currentEntry != null) {
currentEntry.setLatencyWithoutRewrite(packetItem.getLatency());
}
} else {
UUID uuid = UUID.randomUUID(); // Use a fake uuid to preserve function of custom entries
nameMapping.put(packetItem.getName(), uuid);
currentEntry = new UpdateEventTabListEntry(
this,
new GameProfile(uuid, packetItem.getName(), ImmutableList.of()),
null,
packetItem.getLatency(),
0,
null,
true
);
}
}
case LegacyPlayerListItemPacket.REMOVE_PLAYER -> {
//Nothing should be done here as all entries which are not allowed are removed if the action is ServerUpdateTabListEvent.Action.REMOVE_PLAYER
}
default -> {
// For 1.7 there is only add and remove
}
}
return currentEntry;
}
private void processLegacy(int action, LegacyPlayerListItemPacket.Item item) {
switch (action) {
case LegacyPlayerListItemPacket.ADD_PLAYER -> {
if (nameMapping.containsKey(item.getName())) { // ADD_PLAYER also used for updating ping if (nameMapping.containsKey(item.getName())) { // ADD_PLAYER also used for updating ping
KeyedVelocityTabListEntry entry = entries.get(nameMapping.get(item.getName())); KeyedVelocityTabListEntry entry = entries.get(nameMapping.get(item.getName()));
if (entry != null) { if (entry != null) {
@ -101,16 +203,16 @@ public class VelocityTabListLegacy extends KeyedVelocityTabList {
.latency(item.getLatency()) .latency(item.getLatency())
.build()); .build());
} }
break; }
case LegacyPlayerListItemPacket.REMOVE_PLAYER: case LegacyPlayerListItemPacket.REMOVE_PLAYER -> {
UUID removedUuid = nameMapping.remove(item.getName()); UUID removedUuid = nameMapping.remove(item.getName());
if (removedUuid != null) { if (removedUuid != null) {
entries.remove(removedUuid); entries.remove(removedUuid);
} }
break; }
default: default -> {
// For 1.7 there is only add and remove // For 1.7 there is only add and remove
break; }
} }
} }