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>();