Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-12-24 23:30:26 +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:
Ursprung
78f6cfc26c
Commit
ce18534ce2
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
|
@ -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,117 @@ 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
|
// 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();
|
||||||
@ -187,43 +293,97 @@ public class KeyedVelocityTabList implements InternalTabList {
|
|||||||
throw new IllegalStateException("Got null game profile for ADD_PLAYER");
|
throw new IllegalStateException("Got null game profile for ADD_PLAYER");
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.putIfAbsent(item.getUuid(), (KeyedVelocityTabListEntry) TabListEntry.builder()
|
currentEntry = new UpdateEventTabListEntry(
|
||||||
.tabList(this)
|
this,
|
||||||
.profile(new GameProfile(uuid, name, properties))
|
new GameProfile(uuid, name, properties),
|
||||||
.displayName(item.getDisplayName())
|
item.getDisplayName(),
|
||||||
.latency(item.getLatency())
|
item.getLatency(),
|
||||||
.chatSession(new RemoteChatSession(null, item.getPlayerKey()))
|
item.getGameMode(),
|
||||||
.gameMode(item.getGameMode())
|
new RemoteChatSession(null, item.getPlayerKey()),
|
||||||
.build());
|
true
|
||||||
break;
|
);
|
||||||
}
|
}
|
||||||
case LegacyPlayerListItemPacket.REMOVE_PLAYER:
|
case LegacyPlayerListItemPacket.REMOVE_PLAYER -> {
|
||||||
entries.remove(uuid);
|
//Nothing should be done here, as all entries which are not allowed are removed
|
||||||
break;
|
// if the action is ServerUpdateTabListEvent.Action.REMOVE_PLAYER
|
||||||
case LegacyPlayerListItemPacket.UPDATE_DISPLAY_NAME: {
|
}
|
||||||
KeyedVelocityTabListEntry entry = entries.get(uuid);
|
case LegacyPlayerListItemPacket.UPDATE_DISPLAY_NAME -> {
|
||||||
if (entry != null) {
|
if (currentEntry != null) {
|
||||||
entry.setDisplayNameInternal(item.getDisplayName());
|
currentEntry.setDisplayNameWithoutRewrite(item.getDisplayName());
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case LegacyPlayerListItemPacket.UPDATE_LATENCY: {
|
case LegacyPlayerListItemPacket.UPDATE_LATENCY -> {
|
||||||
KeyedVelocityTabListEntry entry = entries.get(uuid);
|
if (currentEntry != null) {
|
||||||
if (entry != null) {
|
currentEntry.setLatencyWithoutRewrite(item.getLatency());
|
||||||
entry.setLatencyInternal(item.getLatency());
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case LegacyPlayerListItemPacket.UPDATE_GAMEMODE: {
|
case LegacyPlayerListItemPacket.UPDATE_GAMEMODE -> {
|
||||||
KeyedVelocityTabListEntry entry = entries.get(uuid);
|
if (currentEntry != null) {
|
||||||
if (entry != null) {
|
currentEntry.setGameModeWithoutRewrite(item.getGameMode());
|
||||||
entry.setGameModeInternal(item.getGameMode());
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
default:
|
default -> {
|
||||||
// Nothing we can do here
|
// Nothing we can do here
|
||||||
break;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.putIfAbsent(item.getUuid(), (KeyedVelocityTabListEntry) TabListEntry.builder()
|
||||||
|
.tabList(this)
|
||||||
|
.profile(new GameProfile(uuid, name, properties))
|
||||||
|
.displayName(item.getDisplayName())
|
||||||
|
.latency(item.getLatency())
|
||||||
|
.chatSession(new RemoteChatSession(null, item.getPlayerKey()))
|
||||||
|
.gameMode(item.getGameMode())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
case LegacyPlayerListItemPacket.REMOVE_PLAYER -> entries.remove(uuid);
|
||||||
|
case LegacyPlayerListItemPacket.UPDATE_DISPLAY_NAME -> {
|
||||||
|
KeyedVelocityTabListEntry entry = entries.get(uuid);
|
||||||
|
if (entry != null) {
|
||||||
|
entry.setDisplayNameInternal(item.getDisplayName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case LegacyPlayerListItemPacket.UPDATE_LATENCY -> {
|
||||||
|
KeyedVelocityTabListEntry entry = entries.get(uuid);
|
||||||
|
if (entry != null) {
|
||||||
|
entry.setLatencyInternal(item.getLatency());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case LegacyPlayerListItemPacket.UPDATE_GAMEMODE -> {
|
||||||
|
KeyedVelocityTabListEntry entry = entries.get(uuid);
|
||||||
|
if (entry != null) {
|
||||||
|
entry.setGameModeInternal(item.getGameMode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
// Nothing we can do here
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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) {
|
||||||
for (UpsertPlayerInfoPacket.Entry entry : infoPacket.getEntries()) {
|
List<UpdateEventTabListEntry> entries = mapToEventEntries(infoPacket.getActions(), infoPacket.getEntries());
|
||||||
processUpsert(infoPacket.getActions(), entry);
|
|
||||||
}
|
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()) {
|
||||||
|
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) {
|
||||||
for (UUID uuid : infoPacket.getProfilesToRemove()) {
|
List<UpdateEventTabListEntry> entries = mapToEventEntries(infoPacket.getProfilesToRemove());
|
||||||
this.entries.remove(uuid);
|
|
||||||
}
|
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()) {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren