From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Helvetica Volubi Date: Sun, 16 Nov 2025 15:48:12 +0800 Subject: [PATCH] Leaves: Replay Mod API Co-authored by: violetc <58360096+s-yh-china@users.noreply.github.com> As a part of : Leaves (https://github.com/LeavesMC/Leaves) & ReplayMod (https://github.com/ReplayMod) Licensed under: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) This patch is Powered by ReplayMod (https://github.com/ReplayMod) diff --git a/net/minecraft/commands/CommandSourceStack.java b/net/minecraft/commands/CommandSourceStack.java index 587ec673a0821e91de67abaf3a559c78f4aa8016..4fc1aad19fd9321c44aae99c118f40ec67d78020 100644 --- a/net/minecraft/commands/CommandSourceStack.java +++ b/net/minecraft/commands/CommandSourceStack.java @@ -562,7 +562,7 @@ public class CommandSourceStack implements SharedSuggestionProvider, ExecutionCo @Override public Collection getOnlinePlayerNames() { - return this.entity instanceof ServerPlayer sourcePlayer && !sourcePlayer.getBukkitEntity().hasPermission("paper.bypass-visibility.tab-completion") ? this.getServer().getPlayerList().getPlayers().stream().filter(serverPlayer -> sourcePlayer.getBukkitEntity().canSee(serverPlayer.getBukkitEntity())).map(serverPlayer -> serverPlayer.getGameProfile().name()).toList() : Lists.newArrayList(this.server.getPlayerNames()); // Paper - Make CommandSourceStack respect hidden players + return this.entity instanceof ServerPlayer sourcePlayer && !(sourcePlayer instanceof org.leavesmc.leaves.replay.ServerPhotographer) && !sourcePlayer.getBukkitEntity().hasPermission("paper.bypass-visibility.tab-completion") ? this.getServer().getPlayerList().getPlayers().stream().filter(serverPlayer -> sourcePlayer.getBukkitEntity().canSee(serverPlayer.getBukkitEntity())).map(serverPlayer -> serverPlayer.getGameProfile().name()).toList() : Lists.newArrayList(this.server.getPlayerNames()); // Paper - Make CommandSourceStack respect hidden players // Leaves - only real player } @Override diff --git a/net/minecraft/commands/arguments/selector/EntitySelector.java b/net/minecraft/commands/arguments/selector/EntitySelector.java index 6c05b9276bdc267a36376f6c64592a50e4b2b36e..21d2c97b3d1e1d29c9de02b6acd58985c7c74231 100644 --- a/net/minecraft/commands/arguments/selector/EntitySelector.java +++ b/net/minecraft/commands/arguments/selector/EntitySelector.java @@ -144,13 +144,14 @@ public class EntitySelector { if (this.playerName != null) { ServerPlayer result = sender.getServer().getPlayerList().getPlayerByName(this.playerName); + result = result instanceof org.leavesmc.leaves.replay.ServerPhotographer ? null : result; // Leaves - skip photographer return result == null ? List.of() : List.of(result); } if (this.entityUUID != null) { for (ServerLevel level : sender.getServer().getAllLevels()) { Entity entity = level.getEntity(this.entityUUID); - if (entity != null) { + if (entity != null && !(entity instanceof org.leavesmc.leaves.replay.ServerPhotographer)) { if (entity.getType().isEnabled(sender.enabledFeatures())) { return List.of(entity); } @@ -164,7 +165,7 @@ public class EntitySelector { AABB absoluteAabb = this.getAbsoluteAabb(pos); if (this.currentEntity) { Predicate predicate = this.getPredicate(pos, absoluteAabb, null); - return sender.getEntity() != null && predicate.test(sender.getEntity()) ? List.of(sender.getEntity()) : List.of(); + return sender.getEntity() != null && !(sender.getEntity() instanceof org.leavesmc.leaves.replay.ServerPhotographer) && predicate.test(sender.getEntity()) ? List.of(sender.getEntity()) : List.of(); // Leaves - skip photographer } Predicate predicate = this.getPredicate(pos, absoluteAabb, sender.enabledFeatures()); @@ -176,6 +177,7 @@ public class EntitySelector { this.addEntities(result, level, absoluteAabb, predicate); } } + result.removeIf(entity -> entity instanceof org.leavesmc.leaves.replay.ServerPhotographer); // Leaves - skip photographer return this.sortAndLimit(pos, result); } @@ -210,11 +212,13 @@ public class EntitySelector { this.checkPermissions(sender); if (this.playerName != null) { ServerPlayer result = sender.getServer().getPlayerList().getPlayerByName(this.playerName); + result = result instanceof org.leavesmc.leaves.replay.ServerPhotographer ? null : result; // Leaves - skip photographer return result == null ? List.of() : List.of(result); } if (this.entityUUID != null) { ServerPlayer result = sender.getServer().getPlayerList().getPlayer(this.entityUUID); + result = result instanceof org.leavesmc.leaves.replay.ServerPhotographer ? null : result; // Leaves - skip photographer return result == null ? List.of() : List.of(result); } @@ -228,11 +232,11 @@ public class EntitySelector { int limit = this.getResultLimit(); List result; if (this.isWorldLimited()) { - result = sender.getLevel().getPlayers(predicate, limit); + result = sender.getLevel().getPlayers((player -> !(player instanceof org.leavesmc.leaves.replay.ServerPhotographer) && predicate.test(player)), limit); // Leaves - skip photographer } else { result = new ObjectArrayList<>(); - for (ServerPlayer player : sender.getServer().getPlayerList().getPlayers()) { + for (ServerPlayer player : sender.getServer().getPlayerList().realPlayers) { // Leaves - only real players if (predicate.test(player)) { result.add(player); if (result.size() >= limit) { diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java index f6ace71d151d428149e8c9615c3b91e0f213ae7c..d7a7f7e7daf45bc0912aae008f3fed35201925fe 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -1894,7 +1894,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop players = new java.util.ArrayList<>(this.playerList.getPlayers()); // Folia - region threading + List players = new java.util.ArrayList<>(this.playerList.realPlayers); // Folia - region threading // Leaves - only real player int maxPlayers = this.getMaxPlayers(); if (this.hidesOnlinePlayers()) { return new ServerStatus.Players(maxPlayers, players.size(), List.of()); @@ -2115,7 +2115,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { // Folia - region threading // Paper start - Expand PlayerGameModeChangeEvent org.bukkit.event.player.PlayerGameModeChangeEvent event = updatedPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null); // Folia - region threading @@ -2523,7 +2523,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> packets = this.getStartTrackingPackets(objective); - for (ServerPlayer player : this.server.getPlayerList().getPlayers()) { + for (ServerPlayer player : this.server.getPlayerList().realPlayers) { // Leaves - only real players if (player.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board for (Packet packet : packets) { player.connection.send(packet); @@ -271,7 +271,7 @@ public class ServerScoreboard extends Scoreboard { public void stopTrackingObjective(final Objective objective) { List> packets = this.getStopTrackingPackets(objective); - for (ServerPlayer player : this.server.getPlayerList().getPlayers()) { + for (ServerPlayer player : this.server.getPlayerList().realPlayers) { // Leaves - only real players if (player.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board for (Packet packet : packets) { player.connection.send(packet); diff --git a/net/minecraft/server/commands/ListPlayersCommand.java b/net/minecraft/server/commands/ListPlayersCommand.java index b850c908a594f839ce124c6bd01762d31cc3aa1e..b2240fd3f5ace03168fd679932675a72c09e81d7 100644 --- a/net/minecraft/server/commands/ListPlayersCommand.java +++ b/net/minecraft/server/commands/ListPlayersCommand.java @@ -33,7 +33,7 @@ public class ListPlayersCommand { private static int format(final CommandSourceStack source, final Function formatter) { PlayerList playerList = source.getServer().getPlayerList(); // CraftBukkit start - List playersTemp = playerList.getPlayers(); + List playersTemp = playerList.realPlayers; if (source.getBukkitSender() instanceof org.bukkit.entity.Player) { org.bukkit.entity.Player sender = (org.bukkit.entity.Player) source.getBukkitSender(); playersTemp = playersTemp.stream().filter((ep) -> sender.canSee(ep.getBukkitEntity())).collect(java.util.stream.Collectors.toList()); diff --git a/net/minecraft/server/commands/OpCommand.java b/net/minecraft/server/commands/OpCommand.java index 20cbc0e65fea3fc2d0033259c2f35e158ea81926..6bcd09b8d39333bd5f2c0d7c0a9a91bb52f9314d 100644 --- a/net/minecraft/server/commands/OpCommand.java +++ b/net/minecraft/server/commands/OpCommand.java @@ -25,7 +25,8 @@ public class OpCommand { (c, p) -> { PlayerList list = c.getSource().getServer().getPlayerList(); return SharedSuggestionProvider.suggest( - list.getPlayers().stream().filter(player -> !list.isOp(player.nameAndId())).map(pl -> pl.getGameProfile().name()), p + list.realPlayers // Leaves - only real player + .stream().filter(player -> !list.isOp(player.nameAndId())).map(pl -> pl.getGameProfile().name()), p ); } ) diff --git a/net/minecraft/server/commands/ParticleCommand.java b/net/minecraft/server/commands/ParticleCommand.java index ce8a8e665ca68f14158739dd7a07f932a472203c..390464ac4dcf698f27646efbdb034a873a8725af 100644 --- a/net/minecraft/server/commands/ParticleCommand.java +++ b/net/minecraft/server/commands/ParticleCommand.java @@ -36,7 +36,7 @@ public class ParticleCommand { 0.0F, 0, false, - c.getSource().getServer().getPlayerList().getPlayers() + c.getSource().getServer().getPlayerList().realPlayers // Leaves - only real player ) ) .then( @@ -50,7 +50,7 @@ public class ParticleCommand { 0.0F, 0, false, - c.getSource().getServer().getPlayerList().getPlayers() + c.getSource().getServer().getPlayerList().realPlayers // Leaves - only real player ) ) .then( @@ -68,7 +68,7 @@ public class ParticleCommand { FloatArgumentType.getFloat(c, "speed"), IntegerArgumentType.getInteger(c, "count"), false, - c.getSource().getServer().getPlayerList().getPlayers() + c.getSource().getServer().getPlayerList().realPlayers // Leaves - only real player ) ) .then( @@ -82,7 +82,7 @@ public class ParticleCommand { FloatArgumentType.getFloat(c, "speed"), IntegerArgumentType.getInteger(c, "count"), true, - c.getSource().getServer().getPlayerList().getPlayers() + c.getSource().getServer().getPlayerList().realPlayers // Leaves - only real player ) ) .then( @@ -112,7 +112,7 @@ public class ParticleCommand { FloatArgumentType.getFloat(c, "speed"), IntegerArgumentType.getInteger(c, "count"), false, - c.getSource().getServer().getPlayerList().getPlayers() + c.getSource().getServer().getPlayerList().realPlayers // Leaves - only real player ) ) .then( diff --git a/net/minecraft/server/commands/TeamMsgCommand.java b/net/minecraft/server/commands/TeamMsgCommand.java index 6a79944b040307acef3d1c66bd0dacaba514c708..4fa37a9772a10aea5eacaa1a02d9f834eda0877b 100644 --- a/net/minecraft/server/commands/TeamMsgCommand.java +++ b/net/minecraft/server/commands/TeamMsgCommand.java @@ -41,7 +41,7 @@ public class TeamMsgCommand { List receivers = source.getServer() .getPlayerList() - .getPlayers() + .realPlayers // Leaves - only real players .stream() .filter(receiver -> receiver == entity || receiver.getTeam() == team) .toList(); diff --git a/net/minecraft/server/commands/WhitelistCommand.java b/net/minecraft/server/commands/WhitelistCommand.java index 3bc559b636288d38192c64a2aba15c062898182f..99aac6df1725db48b63ce68e74f6a3a18caf7956 100644 --- a/net/minecraft/server/commands/WhitelistCommand.java +++ b/net/minecraft/server/commands/WhitelistCommand.java @@ -45,7 +45,7 @@ public class WhitelistCommand { (c, p) -> { PlayerList list = c.getSource().getServer().getPlayerList(); return SharedSuggestionProvider.suggest( - list.getPlayers() + list.realPlayers // Leaves - only real player .stream() .map(Player::nameAndId) .filter(nameAndId -> !list.getWhiteList().isWhiteListed(nameAndId)) diff --git a/net/minecraft/server/gui/PlayerListComponent.java b/net/minecraft/server/gui/PlayerListComponent.java index 37c9f983ae89c497f44a1b7967d741abf909e0a0..d7fb2505f2a86ca0d81f144624ef2e1a4b4bddbb 100644 --- a/net/minecraft/server/gui/PlayerListComponent.java +++ b/net/minecraft/server/gui/PlayerListComponent.java @@ -17,8 +17,8 @@ public class PlayerListComponent extends JList { if (this.tickCount++ % 20 == 0) { Vector players = new Vector<>(); - for (int i = 0; i < this.server.getPlayerList().getPlayers().size(); i++) { - players.add(this.server.getPlayerList().getPlayers().get(i).getGameProfile().name()); + for (int i = 0; i < this.server.getPlayerList().realPlayers.size(); i++) { // Leaves - only real players + players.add(this.server.getPlayerList().realPlayers.get(i).getGameProfile().name()); // Leaves - only real players } this.setListData(players); diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java index 41c7179cf2b530d8e4b0fc8161aec75fc9d6c4df..f75249ba1b02efec0f4fc381b02803228e3e1d7c 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -3103,7 +3103,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet if (entity instanceof ServerPlayer player) { ServerLevel.this.players.add(player); // Leaves start - skip - if (!(player instanceof org.leavesmc.leaves.bot.ServerBot)) { + if (!(player instanceof org.leavesmc.leaves.bot.ServerBot) && !(player instanceof org.leavesmc.leaves.replay.ServerPhotographer)) { // and photographer ServerLevel.this.realPlayers.add(player); } // Leaves end - skip @@ -3189,7 +3189,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet if (entity instanceof ServerPlayer player) { ServerLevel.this.players.remove(player); // Leaves start - skip - if (!(player instanceof org.leavesmc.leaves.bot.ServerBot)) { + if (!(player instanceof org.leavesmc.leaves.bot.ServerBot) && !(player instanceof org.leavesmc.leaves.replay.ServerPhotographer)) { // and photographer ServerLevel.this.realPlayers.remove(player); } // Leaves end - skip diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java index b5a22510fede9521a0d8330272876d2ceedfb1cc..049648756f9053895e773efdca33fe65b3555b10 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -129,6 +129,7 @@ public abstract class PlayerList { private int simulationDistance; private boolean allowCommandsForAllPlayers; private int sendAllPlayerInfoIn; + public final List realPlayers = new java.util.concurrent.CopyOnWriteArrayList<>(); // Leaves - replay api // CraftBukkit start private org.bukkit.craftbukkit.CraftServer cserver; @@ -191,6 +192,7 @@ public abstract class PlayerList { private boolean countConnection(Connection conn, int limit) { synchronized (this.connectionsStateLock) { + if (conn instanceof org.leavesmc.leaves.bot.ServerBotPacketListenerImpl.BotConnection) return true; int count = this.usersCountedAgainstLimit.size(); if (count >= limit) { return false; @@ -221,6 +223,173 @@ public abstract class PlayerList { abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor + // Leaves start - replay mod api + public void placeNewPhotographer(Connection connection, org.leavesmc.leaves.replay.ServerPhotographer player, ServerLevel worldserver) { + player.isRealPlayer = true; // Paper + player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed + NameAndId gameProfile = player.nameAndId(); + UserNameToIdResolver profileCache = this.server.services().nameToIdCache(); + Optional oldProfile = profileCache.get(gameProfile.id()); + String oldName = oldProfile.map(NameAndId::name).orElse(gameProfile.name()); + if (player.lastKnownName != null) { oldName = player.lastKnownName; player.lastKnownName = null; } // CraftBukkit - Better rename detection + profileCache.add(gameProfile); + ServerLevel level = player.level(); + LevelData levelData = level.getLevelData(); + CommonListenerCookie cookie = CommonListenerCookie.createInitial(player.gameProfile, false); + ServerGamePacketListenerImpl playerConnection = new ServerGamePacketListenerImpl(this.server, connection, player, cookie); + // Folia start - rewrite login process + // only after setting the connection listener to game type, add the connection to this regions list + level.getCurrentWorldData().connections.add(connection); + // Folia end - rewrite login process + if (!me.earthme.luminol.config.modules.optimizations.AsyncProtocolChangeConfig.enabled) { // Luminol - Async protocol switch // we will run async switch once these main thread logics became done + connection.setupInboundProtocol( + GameProtocols.SERVERBOUND_TEMPLATE.bind(RegistryFriendlyByteBuf.decorator(this.server.registryAccess()), playerConnection), playerConnection + ); + } // Luminol - Async protocol switch + playerConnection.suspendFlushing(); + GameRules gameRules = level.getGameRules(); + boolean immediateRespawn = gameRules.get(GameRules.IMMEDIATE_RESPAWN); + boolean reducedDebugInfo = gameRules.get(GameRules.REDUCED_DEBUG_INFO); + boolean doLimitedCrafting = gameRules.get(GameRules.LIMITED_CRAFTING); + playerConnection.send( + new ClientboundLoginPacket( + player.getId(), + levelData.isHardcore(), + this.server.levelKeys(), + this.getMaxPlayers(), + io.papermc.paper.FeatureHooks.getViewDistance(level), // Paper - view distance + io.papermc.paper.FeatureHooks.getSimulationDistance(level), // Paper - simulation distance + reducedDebugInfo, + !immediateRespawn, + doLimitedCrafting, + player.createCommonSpawnInfo(level), + this.server.usesAuthentication(), + this.server.enforceSecureProfile() + ) + ); + player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit + playerConnection.send(new ClientboundChangeDifficultyPacket(levelData.getDifficulty(), levelData.isDifficultyLocked())); + playerConnection.send(new ClientboundPlayerAbilitiesPacket(player.getAbilities())); + playerConnection.send(new ClientboundSetHeldSlotPacket(player.getInventory().getSelectedSlot())); + RecipeManager recipeManager = this.server.getRecipeManager(); + playerConnection.send( + new ClientboundUpdateRecipesPacket(recipeManager.getSynchronizedItemProperties(), recipeManager.getSynchronizedStonecutterRecipes()) + ); + this.sendPlayerPermissionLevel(player); + player.getStats().markAllDirty(); + player.getRecipeBook().sendInitialRecipeBook(player); + //this.updateEntireScoreboard(level.getScoreboard(), player); // Folia - region threading + this.server.invalidateStatus(); + + playerConnection.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot()); + ServerStatus status = this.server.getStatus(); + if (status != null && !cookie.transferred()) { + player.sendServerStatus(status); + } + + // player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below + this.players.add(player); + this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot + this.playersByUUID.put(player.getUUID(), player); + // this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player))); // CraftBukkit - replaced with loop below + // Paper start - Fire PlayerJoinEvent when Player is actually ready; correctly register player BEFORE PlayerJoinEvent, so the entity is valid and doesn't require tick delay hacks + player.suppressTrackerForLogin = true; + this.sendLevelInfo(player, level); + level.addNewPlayer(player); + this.server.getCustomBossEvents().onPlayerConnect(player); // see commented out section below serverLevel.addPlayerJoin(player); + // Paper end - Fire PlayerJoinEvent when Player is actually ready + player.initInventoryMenu(); + org.bukkit.craftbukkit.entity.CraftPlayer bukkitPlayer = player.getBukkitEntity(); + + if (!player.connection.isAcceptingMessages()) { + //return; // Folia - region threading - must still allow the player to connect, as we must add to chunk map before handling disconnect + } + + // org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol + + // Leaves start - bot support + if (fun.bm.lophine.config.modules.function.FakeplayerConfig.enable) { + org.leavesmc.leaves.bot.ServerBot bot = this.server.getBotList().getBotByName(player.getScoreboardName()); + if (bot != null) { + this.server.getBotList().removeBot(bot, org.leavesmc.leaves.event.bot.BotRemoveEvent.RemoveReason.INTERNAL, player.getBukkitEntity(), false, false); + } + this.server.getBotList().bots.forEach(bot1 -> { + bot1.sendPlayerInfo(player); + bot1.sendFakeDataIfNeed(player, true); + }); // Leaves - render bot + } + // Leaves end - bot support + + // CraftBukkit start - sendAll above replaced with this loop + ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)); // Paper - Add Listing API for Player + + final List onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - Use single player info update packet on join + for (ServerPlayer entityplayer1 : this.players) { // Folia - region threading + + 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 + continue; + } + + onlinePlayers.add(entityplayer1); // Paper - Use single player info update packet on join + } + // Paper start - Use single player info update packet on join + if (!onlinePlayers.isEmpty()) { + 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; + player.suppressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready + player.level().getChunkSource().addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now + // CraftBukkit end + + //player.refreshEntityData(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn // Paper - THIS IS NOT NEEDED ANYMORE + + this.sendLevelInfo(player, level); + + // CraftBukkit start - Only add if the player wasn't moved in the event + if (player.level() == level && !level.players().contains(player)) { + level.addNewPlayer(player); + this.server.getCustomBossEvents().onPlayerConnect(player); + } + + level = player.level(); // CraftBukkit - Update in case join event changed it + // CraftBukkit end + this.sendActivePlayerEffects(player); + // Paper - move loading pearls / parent vehicle up + player.initInventoryMenu(); + this.server.notificationManager().playerJoined(player); + playerConnection.resumeFlushing(); + // Paper start - Configurable player collision; Add to collideRule team if needed + final net.minecraft.world.scores.Scoreboard scoreboard = this.getServer().getLevel(Level.OVERWORLD).getScoreboard(); + final PlayerTeam collideRuleTeam = scoreboard.getPlayerTeam(this.collideRuleTeamName); + if (false && this.collideRuleTeamName != null && collideRuleTeam != null && player.getTeam() == null) { // Folia - region threading + scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam); + } + + if (player.isDeadOrDying()) { + net.minecraft.core.Holder plains = level.registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME) + .getOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS); + player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket( + new net.minecraft.world.level.chunk.EmptyLevelChunk(level, player.chunkPosition(), plains), + level.getLightEngine(), null, null, true) // Paper - Anti-Xray + ); + } + // Paper end - Send empty chunk + } + // Leaves end - replay mod api + public void placeNewPlayer(final Connection connection, final ServerPlayer player, final CommonListenerCookie cookie) { player.isRealPlayer = true; // Paper player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed @@ -295,6 +464,7 @@ public abstract class PlayerList { // player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below this.players.add(player); + this.realPlayers.add(player); // Leaves - replay api this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot this.playersByUUID.put(player.getUUID(), player); // this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player))); // CraftBukkit - replaced with loop below @@ -510,6 +680,7 @@ public abstract class PlayerList { } protected void save(final ServerPlayer player) { + if (player instanceof org.leavesmc.leaves.replay.ServerPhotographer) return; // Leaves - skip photographer if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit player.lastSave = System.nanoTime(); // Folia - region threading - changed to nanoTime tracking this.playerIo.save(player); @@ -524,6 +695,48 @@ public abstract class PlayerList { } } + // Leaves start - replay mod api + public void removePhotographer(org.leavesmc.leaves.replay.ServerPhotographer entityplayer) { + ServerLevel worldserver = entityplayer.level(); + + entityplayer.awardStat(Stats.LEAVE_GAME); + + if (entityplayer.containerMenu != entityplayer.inventoryMenu) { + entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); + } + + if (server.isSameThread()) entityplayer.doTick(); + + if (this.collideRuleTeamName != null) { + final net.minecraft.world.scores.Scoreboard scoreBoard = this.server.getLevel(Level.OVERWORLD).getScoreboard(); + final PlayerTeam team = scoreBoard.getPlayersTeam(this.collideRuleTeamName); + if (entityplayer.getTeam() == team && team != null) { + scoreBoard.removePlayerFromTeam(entityplayer.getScoreboardName(), team); + } + } + + worldserver.removePlayerImmediately(entityplayer, Entity.RemovalReason.UNLOADED_WITH_PLAYER); + entityplayer.retireScheduler(); + entityplayer.getBukkitEntity().packetProcessor.close(); // Folia - region threading + entityplayer.getAdvancements().clearTriggers(); + this.players.remove(entityplayer); + // Leaves - guard name removal so a photographer sharing a real player's name won't evict the real player's mapping + final String nameKey = entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT); + if (this.playersByName.get(nameKey) == entityplayer) { + this.playersByName.remove(nameKey); + } + this.server.getCustomBossEvents().onPlayerDisconnect(entityplayer); + UUID uuid = entityplayer.getUUID(); + ServerPlayer entityplayer1 = this.playersByUUID.get(uuid); + + if (entityplayer1 == entityplayer) { + this.playersByUUID.remove(uuid); + } + + this.cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity()); + } + // Leaves end - replay mod api + public net.kyori.adventure.text.@Nullable Component remove(final ServerPlayer player) { // CraftBukkit - return string // Paper - return Component // Paper start - Fix kick event leave message not being sent return this.remove(player, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? player.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(player.getDisplayName()))); @@ -597,6 +810,7 @@ public abstract class PlayerList { player.getBukkitEntity().packetProcessor.close(); // Folia - region threading player.getAdvancements().clearTriggers(); this.players.remove(player); + this.realPlayers.remove(player); // Leaves - replay api this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot if (me.earthme.luminol.config.modules.misc.UsernameCheckConfig.allowOldPlayersJoin) this.playedPlayers.remove(player.getGameProfile().name()); // Leaf - Configurable vanilla username check this.server.getCustomBossEvents().onPlayerDisconnect(player); @@ -910,15 +1124,15 @@ public abstract class PlayerList { } public String[] getPlayerNamesArray() { - List players = new java.util.ArrayList<>(this.players); // Folia - region threading - String[] names = new String[players.size() + this.server.getBotList().bots.size()]; // Leaves - fakeplayer support + List players = new java.util.ArrayList<>(this.realPlayers); // Folia - region threading // Lophine - fakeplayer support + String[] names = new String[this.realPlayers.size() + this.server.getBotList().bots.size()]; // Leaves - fakeplayer support - for (int i = 0; i < players.size(); i++) { // Folia - region threading - names[i] = players.get(i).getGameProfile().name(); // Folia - region threading + for (int i = 0; i < this.realPlayers.size(); i++) { // Folia - region threading // Leaves - only real players + names[i] = this.realPlayers.get(i).getGameProfile().name(); // Folia - region threading // Leaves - only real players } // Leaves start - fakeplayer support - for (int i = this.players.size(); i < names.length; ++i) { - names[i] = this.server.getBotList().bots.get(i - this.players.size()).gameProfile.name(); + for (int i = this.realPlayers.size(); i < names.length; ++i) { // Leaves - only real players + names[i] = this.server.getBotList().bots.get(i - this.realPlayers.size()).gameProfile.name(); // Leaves - only real players } // Leaves end - fakeplayer support diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java index d64c68651b7142869a06ce0200fb625cd45ae235..1fe317cf3251bff7bb3e0335452a5714d4d31017 100644 --- a/net/minecraft/world/entity/LivingEntity.java +++ b/net/minecraft/world/entity/LivingEntity.java @@ -276,7 +276,7 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin public int useItemRemaining; protected int fallFlyTicks; private long lastKineticHitFeedbackTime = -2147483648L; - private BlockPos lastPos; + protected BlockPos lastPos; private Optional lastClimbablePos = Optional.empty(); private @Nullable DamageSource lastDamageSource; private long lastDamageStamp = Long.MIN_VALUE; // Folia - region threading