diff --git a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch index 22d00397a5..edb5b971ee 100644 --- a/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch +++ b/paper-server/patches/sources/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java.patch @@ -1,6 +1,72 @@ --- a/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java +++ b/net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket.java -@@ -116,7 +116,15 @@ +@@ -38,7 +38,18 @@ + this.actions = EnumSet.of(action); + this.entries = List.of(new ClientboundPlayerInfoUpdatePacket.Entry(player)); + } ++ // Paper start - Add Listing API for Player ++ public ClientboundPlayerInfoUpdatePacket(EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions, List<ClientboundPlayerInfoUpdatePacket.Entry> entries) { ++ this.actions = actions; ++ this.entries = entries; ++ } + ++ public ClientboundPlayerInfoUpdatePacket(EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions, ClientboundPlayerInfoUpdatePacket.Entry entry) { ++ this.actions = actions; ++ this.entries = List.of(entry); ++ } ++ // Paper end - Add Listing API for Player ++ + public static ClientboundPlayerInfoUpdatePacket createPlayerInitializing(Collection<ServerPlayer> players) { + EnumSet<ClientboundPlayerInfoUpdatePacket.Action> enumSet = EnumSet.of( + ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, +@@ -53,6 +64,46 @@ + return new ClientboundPlayerInfoUpdatePacket(enumSet, players); + } + ++ // Paper start - Add Listing API for Player ++ public static ClientboundPlayerInfoUpdatePacket createPlayerInitializing(Collection<ServerPlayer> players, ServerPlayer forPlayer) { ++ final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> enumSet = EnumSet.of( ++ ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ++ ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER ++ ); ++ final List<ClientboundPlayerInfoUpdatePacket.Entry> entries = new java.util.ArrayList<>(players.size()); ++ final org.bukkit.craftbukkit.entity.CraftPlayer bukkitEntity = forPlayer.getBukkitEntity(); ++ for (final ServerPlayer player : players) { ++ entries.add(new ClientboundPlayerInfoUpdatePacket.Entry(player, bukkitEntity.isListed(player.getBukkitEntity()))); ++ } ++ return new ClientboundPlayerInfoUpdatePacket(enumSet, entries); ++ } ++ ++ public static ClientboundPlayerInfoUpdatePacket createSinglePlayerInitializing(ServerPlayer player, boolean listed) { ++ final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> enumSet = EnumSet.of( ++ ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER, ++ ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT, ++ ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER ++ ); ++ final List<ClientboundPlayerInfoUpdatePacket.Entry> entries = List.of(new Entry(player, listed)); ++ return new ClientboundPlayerInfoUpdatePacket(enumSet, entries); ++ } ++ ++ public static ClientboundPlayerInfoUpdatePacket updateListed(UUID playerInfoId, boolean listed) { ++ EnumSet<ClientboundPlayerInfoUpdatePacket.Action> enumSet = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED); ++ return new ClientboundPlayerInfoUpdatePacket(enumSet, new ClientboundPlayerInfoUpdatePacket.Entry(playerInfoId, listed)); ++ } ++ // Paper end - Add Listing API for Player + private ClientboundPlayerInfoUpdatePacket(RegistryFriendlyByteBuf buf) { + this.actions = buf.readEnumSet(ClientboundPlayerInfoUpdatePacket.Action.class); + this.entries = buf.readList(buf2 -> { +@@ -116,7 +167,15 @@ }), INITIALIZE_CHAT( (serialized, buf) -> serialized.chatSession = buf.readNullable(RemoteChatSession.Data::read), @@ -17,3 +83,32 @@ ), UPDATE_GAME_MODE((serialized, buf) -> serialized.gameMode = GameType.byId(buf.readVarInt()), (buf, entry) -> buf.writeVarInt(entry.gameMode().getId())), UPDATE_LISTED((serialized, buf) -> serialized.listed = buf.readBoolean(), (buf, entry) -> buf.writeBoolean(entry.listed())), +@@ -157,10 +216,15 @@ + @Nullable RemoteChatSession.Data chatSession + ) { + Entry(ServerPlayer player) { ++ // Paper start - Add Listing API for Player ++ this(player, true); ++ } ++ Entry(ServerPlayer player, boolean listed) { + this( ++ // Paper end - Add Listing API for Player + player.getUUID(), + player.getGameProfile(), +- true, ++ listed, // Paper - Add Listing API for Player + player.connection.latency(), + player.gameMode.getGameModeForPlayer(), + player.getTabListDisplayName(), +@@ -169,6 +233,11 @@ + Optionull.map(player.getChatSession(), RemoteChatSession::asData) + ); + } ++ // Paper start - Add Listing API for Player ++ Entry(UUID profileId, boolean listed) { ++ this(profileId, null, listed, 0, GameType.DEFAULT_MODE, null, true, 0, null); ++ } ++ // Paper end - Add Listing API for Player + } + + static class EntryBuilder { diff --git a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch index 7c7ef78fcf..759b6518ad 100644 --- a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch @@ -235,7 +235,7 @@ playerconnection.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot()); ServerStatus serverping = this.server.getStatus(); -@@ -222,17 +327,101 @@ +@@ -222,17 +327,109 @@ player.sendServerStatus(serverping); } @@ -279,14 +279,22 @@ + // CraftBukkit end + + // CraftBukkit start - sendAll above replaced with this loop -+ ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)); ++ ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)); // Paper - Add Listing API for Player + + final List<ServerPlayer> onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - Use single player info update packet on join + for (int i = 0; i < this.players.size(); ++i) { + ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i); + + if (entityplayer1.getBukkitEntity().canSee(bukkitPlayer)) { ++ // Paper start - Add Listing API for Player ++ if (entityplayer1.getBukkitEntity().isListed(bukkitPlayer)) { ++ // Paper end - Add Listing API for Player + entityplayer1.connection.send(packet); ++ // Paper start - Add Listing API for Player ++ } else { ++ entityplayer1.connection.send(ClientboundPlayerInfoUpdatePacket.createSinglePlayerInitializing(player, false)); ++ } ++ // Paper end - Add Listing API for Player + } + + if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { // Paper - Use single player info update packet on join; Don't include joining player @@ -297,7 +305,7 @@ + } + // Paper start - Use single player info update packet on join + if (!onlinePlayers.isEmpty()) { -+ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers)); ++ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers, player)); // Paper - Add Listing API for Player + } + // Paper end - Use single player info update packet on join + player.sentListPacket = true; @@ -342,7 +350,7 @@ } public void updateEntireScoreboard(ServerScoreboard scoreboard, ServerPlayer player) { -@@ -269,30 +458,31 @@ +@@ -269,30 +466,31 @@ } public void addWorldborderListener(ServerLevel world) { @@ -379,7 +387,7 @@ } @Override -@@ -319,14 +509,15 @@ +@@ -319,14 +517,15 @@ } protected void save(ServerPlayer player) { @@ -397,7 +405,7 @@ if (advancementdataplayer != null) { advancementdataplayer.save(); -@@ -334,95 +525,210 @@ +@@ -334,95 +533,210 @@ } @@ -596,11 +604,10 @@ } - return ichatmutablecomponent; -- } else { -- return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(profile) ? Component.translatable("multiplayer.disconnect.server_full") : null; + // return chatmessage; + event.disallow(PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure -+ } else { + } else { +- return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(profile) ? Component.translatable("multiplayer.disconnect.server_full") : null; + // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile) ? IChatBaseComponent.translatable("multiplayer.disconnect.server_full") : null; + if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile)) { + event.disallow(PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure @@ -647,7 +654,7 @@ if (entityplayer1 != null) { set.add(entityplayer1); -@@ -431,72 +737,160 @@ +@@ -431,72 +745,160 @@ Iterator iterator1 = set.iterator(); while (iterator1.hasNext()) { @@ -829,7 +836,7 @@ return entityplayer1; } -@@ -516,15 +910,32 @@ +@@ -516,15 +918,32 @@ } public void sendPlayerPermissionLevel(ServerPlayer player) { @@ -864,11 +871,10 @@ this.sendAllPlayerInfoIn = 0; } -@@ -540,6 +951,25 @@ - } +@@ -541,6 +960,25 @@ } -+ + + // CraftBukkit start - add a world/entity limited version + public void broadcastAll(Packet packet, net.minecraft.world.entity.player.Player entityhuman) { + for (int i = 0; i < this.players.size(); ++i) { @@ -887,10 +893,11 @@ + + } + // CraftBukkit end - ++ public void broadcastAll(Packet<?> packet, ResourceKey<Level> dimension) { Iterator iterator = this.players.iterator(); -@@ -554,7 +984,7 @@ + +@@ -554,7 +992,7 @@ } @@ -899,7 +906,7 @@ PlayerTeam scoreboardteam = source.getTeam(); if (scoreboardteam != null) { -@@ -573,7 +1003,7 @@ +@@ -573,7 +1011,7 @@ } } @@ -908,7 +915,7 @@ PlayerTeam scoreboardteam = source.getTeam(); if (scoreboardteam == null) { -@@ -619,7 +1049,7 @@ +@@ -619,7 +1057,7 @@ } public void deop(GameProfile profile) { @@ -917,7 +924,7 @@ ServerPlayer entityplayer = this.getPlayer(profile.getId()); if (entityplayer != null) { -@@ -629,6 +1059,11 @@ +@@ -629,6 +1067,11 @@ } private void sendPlayerPermissionLevel(ServerPlayer player, int permissionLevel) { @@ -929,7 +936,7 @@ if (player.connection != null) { byte b0; -@@ -643,35 +1078,52 @@ +@@ -643,35 +1086,52 @@ player.connection.send(new ClientboundEntityEventPacket(player, b0)); } @@ -995,7 +1002,7 @@ if (entityplayer != player && entityplayer.level().dimension() == worldKey) { double d4 = x - entityplayer.getX(); -@@ -687,10 +1139,12 @@ +@@ -687,10 +1147,12 @@ } public void saveAll() { @@ -1008,7 +1015,7 @@ } public UserWhiteList getWhiteList() { -@@ -712,15 +1166,19 @@ +@@ -712,15 +1174,19 @@ public void reloadWhiteList() {} public void sendLevelInfo(ServerPlayer player, ServerLevel world) { @@ -1032,7 +1039,7 @@ } player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.LEVEL_CHUNKS_LOAD_START, 0.0F)); -@@ -729,8 +1187,16 @@ +@@ -729,8 +1195,16 @@ public void sendAllPlayerInfo(ServerPlayer player) { player.inventoryMenu.sendAllDataToRemote(); @@ -1050,7 +1057,7 @@ } public int getPlayerCount() { -@@ -746,6 +1212,7 @@ +@@ -746,6 +1220,7 @@ } public void setUsingWhiteList(boolean whitelistEnabled) { @@ -1058,7 +1065,7 @@ this.doWhiteList = whitelistEnabled; } -@@ -786,12 +1253,36 @@ +@@ -786,12 +1261,36 @@ } public void removeAll() { @@ -1097,7 +1104,7 @@ public void broadcastSystemMessage(Component message, boolean overlay) { this.broadcastSystemMessage(message, (entityplayer) -> { return message; -@@ -819,24 +1310,43 @@ +@@ -819,24 +1318,43 @@ } public void broadcastChatMessage(PlayerChatMessage message, ServerPlayer sender, ChatType.Bound params) { @@ -1144,7 +1151,7 @@ } if (flag1 && sender != null) { -@@ -845,20 +1355,27 @@ +@@ -845,20 +1363,27 @@ } @@ -1177,7 +1184,7 @@ Path path = file2.toPath(); if (FileUtil.isPathNormalized(path) && FileUtil.isPathPortable(path) && path.startsWith(file.getPath()) && file2.isFile()) { -@@ -867,7 +1384,7 @@ +@@ -867,7 +1392,7 @@ } serverstatisticmanager = new ServerStatsCounter(this.server, file1); @@ -1186,7 +1193,7 @@ } return serverstatisticmanager; -@@ -875,13 +1392,13 @@ +@@ -875,13 +1400,13 @@ public PlayerAdvancements getPlayerAdvancements(ServerPlayer player) { UUID uuid = player.getUUID(); @@ -1202,7 +1209,7 @@ } advancementdataplayer.setPlayer(player); -@@ -932,15 +1449,39 @@ +@@ -932,15 +1457,39 @@ } public void reloadResources() { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 1f188f80e4..006d44e0a1 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -206,6 +206,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { private final ConversationTracker conversationTracker = new ConversationTracker(); private final Set<String> channels = new HashSet<String>(); private final Map<UUID, Set<WeakReference<Plugin>>> invertedVisibilityEntities = new HashMap<>(); + private final Set<UUID> unlistedEntities = new HashSet<>(); // Paper - Add Listing API for Player private static final WeakHashMap<Plugin, WeakReference<Plugin>> pluginWeakReferences = new WeakHashMap<>(); private int hash = 0; private double health = 20; @@ -2104,7 +2105,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { otherPlayer.setUUID(uuidOverride); } // Paper end - this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(otherPlayer))); + this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(otherPlayer), this.getHandle())); // Paper - Add Listing API for Player if (original != null) otherPlayer.setUUID(original); // Paper - uuid override } @@ -2208,6 +2209,41 @@ public class CraftPlayer extends CraftHumanEntity implements Player { return (entity != null) ? this.canSee(entity) : false; // If we can't find it, we can't see it } + // Paper start - Add Listing API for Player + @Override + public boolean isListed(Player other) { + return !this.unlistedEntities.contains(other.getUniqueId()); + } + + @Override + public boolean unlistPlayer(@NotNull Player other) { + Preconditions.checkNotNull(other, "hidden entity cannot be null"); + if (this.getHandle().connection == null) return false; + if (!this.canSee(other)) return false; + + if (unlistedEntities.add(other.getUniqueId())) { + this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.updateListed(other.getUniqueId(), false)); + return true; + } else { + return false; + } + } + + @Override + public boolean listPlayer(@NotNull Player other) { + Preconditions.checkNotNull(other, "hidden entity cannot be null"); + if (this.getHandle().connection == null) return false; + if (!this.canSee(other)) throw new IllegalStateException("Player cannot see other player"); + + if (this.unlistedEntities.remove(other.getUniqueId())) { + this.getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.updateListed(other.getUniqueId(), true)); + return true; + } else { + return false; + } + } + // Paper end - Add Listing API for Player + @Override public Map<String, Object> serialize() { Map<String, Object> result = new LinkedHashMap<String, Object>();