fix bot & replay api

This commit is contained in:
Helvetica Volubi
2026-07-07 00:21:03 +08:00
parent 377c81f26f
commit 012ea4f5b5
5 changed files with 111 additions and 324 deletions
@@ -260,9 +260,18 @@ index 2dae0f4249eade60ac55c9fa289dd146dc3675e1..239cad02f85f0d14d4abfa746ecdc4dd
com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
index 6cd549849bd7fb5d72431f729229d1e967cf0d98..98fa23ea62698ff615a72747db7381efb16beaf9 100644 index 6cd549849bd7fb5d72431f729229d1e967cf0d98..179e39574fe2a4f395c64c51d6149e01d641a428 100644
--- a/net/minecraft/server/level/ChunkMap.java --- a/net/minecraft/server/level/ChunkMap.java
+++ b/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java
@@ -971,7 +971,7 @@ public class ChunkMap extends SimpleRegionStorage implements ChunkHolder.PlayerP
return this.level.moonrise$getEntityLookup().hasEntity(id); // Folia - region threading
}
- protected void addEntity(final Entity entity) {
+ public void addEntity(final Entity entity) {
org.spigotmc.AsyncCatcher.catchOp("entity track"); // Spigot
// Paper start - ignore and warn about illegal addEntity calls instead of crashing server
if (!entity.valid || entity.level() != this.level || entity.moonrise$getTrackedEntity() != null) { // Folia - region threading
@@ -1388,6 +1388,13 @@ public class ChunkMap extends SimpleRegionStorage implements ChunkHolder.PlayerP @@ -1388,6 +1388,13 @@ public class ChunkMap extends SimpleRegionStorage implements ChunkHolder.PlayerP
} }
} else { } else {
@@ -311,7 +311,7 @@ index 558f465e9ec2536064716a196d52d78d873d95be..598867ffa41c8d38e0c18550071ac259
} }
// Leaves end - skip // Leaves end - skip
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index ed50484ca24ef24b1e5bef9f6369dfa4b30c359c..1a849654efcd42cdea3f85020fd8d018bc651b57 100644 index ed50484ca24ef24b1e5bef9f6369dfa4b30c359c..64a87785048e103c10f6453e97a37f8501255adc 100644
--- a/net/minecraft/server/players/PlayerList.java --- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java
@@ -129,6 +129,7 @@ public abstract class PlayerList { @@ -129,6 +129,7 @@ public abstract class PlayerList {
@@ -330,90 +330,61 @@ index ed50484ca24ef24b1e5bef9f6369dfa4b30c359c..1a849654efcd42cdea3f85020fd8d018
int count = this.usersCountedAgainstLimit.size(); int count = this.usersCountedAgainstLimit.size();
if (count >= limit) { if (count >= limit) {
return false; return false;
@@ -221,6 +223,173 @@ public abstract class PlayerList { @@ -221,6 +223,123 @@ public abstract class PlayerList {
abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor
+ // Leaves start - replay mod api + // Leaves start - replay mod api
+ public void placeNewPhotographer(Connection connection, org.leavesmc.leaves.replay.ServerPhotographer player, ServerLevel worldserver) { + public void placeNewPhotographer(Connection connection, org.leavesmc.leaves.replay.ServerPhotographer player, ServerLevel worldserver) {
+ player.isRealPlayer = true; // Paper + player.isRealPlayer = true; // Paper
+ player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed + player.loginTime = System.currentTimeMillis(); // Paper
+ NameAndId gameProfile = player.nameAndId(); +
+ UserNameToIdResolver profileCache = this.server.services().nameToIdCache(); + ServerLevel worldserver1 = worldserver;
+ Optional<NameAndId> oldProfile = profileCache.get(gameProfile.id()); +
+ String oldName = oldProfile.map(NameAndId::name).orElse(gameProfile.name()); + player.setServerLevel(worldserver1);
+ if (player.lastKnownName != null) { oldName = player.lastKnownName; player.lastKnownName = null; } // CraftBukkit - Better rename detection + player.gameMode.setLevel(player.level());
+ profileCache.add(gameProfile); +
+ ServerLevel level = player.level(); + LevelData worlddata = worldserver1.getLevelData();
+ LevelData levelData = level.getLevelData(); +
+ CommonListenerCookie cookie = CommonListenerCookie.createInitial(player.gameProfile, false); + ServerGamePacketListenerImpl playerconnection = new ServerGamePacketListenerImpl(this.server, connection, player, CommonListenerCookie.createInitial(player.gameProfile, false));
+ ServerGamePacketListenerImpl playerConnection = new ServerGamePacketListenerImpl(this.server, connection, player, cookie); + GameRules gamerules = worldserver1.getGameRules();
+ // Folia start - rewrite login process + boolean flag = gamerules.get(GameRules.IMMEDIATE_RESPAWN);
+ // only after setting the connection listener to game type, add the connection to this regions list + boolean flag1 = gamerules.get(GameRules.REDUCED_DEBUG_INFO);
+ level.getCurrentWorldData().connections.add(connection); + boolean flag2 = gamerules.get(GameRules.LIMITED_CRAFTING);
+ // 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 + playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), this.server.levelKeys(), this.getMaxPlayers(), io.papermc.paper.FeatureHooks.getViewDistance(worldserver1), io.papermc.paper.FeatureHooks.getSimulationDistance(worldserver1), flag1, !flag, flag2, player.createCommonSpawnInfo(worldserver1), this.server.usesAuthentication(), this.server.enforceSecureProfile()));
+ 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 + player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
+ playerConnection.send(new ClientboundChangeDifficultyPacket(levelData.getDifficulty(), levelData.isDifficultyLocked())); + playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
+ playerConnection.send(new ClientboundPlayerAbilitiesPacket(player.getAbilities())); + playerconnection.send(new ClientboundPlayerAbilitiesPacket(player.getAbilities()));
+ playerConnection.send(new ClientboundSetHeldSlotPacket(player.getInventory().getSelectedSlot())); + playerconnection.send(new ClientboundSetHeldSlotPacket(player.getInventory().getSelectedSlot()));
+ RecipeManager recipeManager = this.server.getRecipeManager(); + RecipeManager craftingmanager = this.server.getRecipeManager();
+ playerConnection.send( + playerconnection.send(new ClientboundUpdateRecipesPacket(craftingmanager.getSynchronizedItemProperties(), craftingmanager.getSynchronizedStonecutterRecipes()));
+ new ClientboundUpdateRecipesPacket(recipeManager.getSynchronizedItemProperties(), recipeManager.getSynchronizedStonecutterRecipes()) +
+ );
+ this.sendPlayerPermissionLevel(player); + this.sendPlayerPermissionLevel(player);
+ player.getStats().markAllDirty(); + player.getStats().markAllDirty();
+ player.getRecipeBook().sendInitialRecipeBook(player); + player.getRecipeBook().sendInitialRecipeBook(player);
+ //this.updateEntireScoreboard(level.getScoreboard(), player); // Folia - region threading + this.updateEntireScoreboard(worldserver1.getScoreboard(), player);
+ this.server.invalidateStatus(); + this.server.invalidateStatus();
+ +
+ playerConnection.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot()); + playerconnection.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot());
+ ServerStatus status = this.server.getStatus(); + ServerStatus serverping = this.server.getStatus();
+ if (status != null && !cookie.transferred()) { +
+ player.sendServerStatus(status); + if (serverping != null) {
+ player.sendServerStatus(serverping);
+ } + }
+ +
+ // player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below
+ this.players.add(player); + this.players.add(player);
+ this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot + this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot
+ this.playersByUUID.put(player.getUUID(), player); + 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; + player.suppressTrackerForLogin = true;
+ this.sendLevelInfo(player, level); + worldserver1.addNewPlayer(player);
+ level.addNewPlayer(player); + this.server.getCustomBossEvents().onPlayerConnect(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(); + org.bukkit.craftbukkit.entity.CraftPlayer bukkitPlayer = player.getBukkitEntity();
+ +
+ player.containerMenu.transferTo(player.containerMenu, bukkitPlayer);
+ if (!player.connection.isAcceptingMessages()) { + 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 + return;
+ } + }
+ +
+ // org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol + // org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol
@@ -431,80 +402,59 @@ index ed50484ca24ef24b1e5bef9f6369dfa4b30c359c..1a849654efcd42cdea3f85020fd8d018
+ } + }
+ // Leaves end - bot support + // Leaves end - bot support
+ +
+ // CraftBukkit start - sendAll above replaced with this loop + final List<ServerPlayer> onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1);
+ ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player)); // Paper - Add Listing API for Player + for (int i = 0; i < this.players.size(); ++i) {
+ ServerPlayer entityplayer1 = this.players.get(i);
+ +
+ final List<ServerPlayer> onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); // Paper - Use single player info update packet on join + if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) {
+ 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; + continue;
+ } + }
+ +
+ onlinePlayers.add(entityplayer1); // Paper - Use single player info update packet on join + // Leaves start - skip photographer
+ if (entityplayer1 instanceof org.leavesmc.leaves.replay.ServerPhotographer) {
+ continue;
+ }
+ // Leaves end - skip photographer
+
+ onlinePlayers.add(entityplayer1);
+ } + }
+ // Paper start - Use single player info update packet on join
+ if (!onlinePlayers.isEmpty()) { + if (!onlinePlayers.isEmpty()) {
+ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers, player)); // Paper - Add Listing API for Player + player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers, player));
+ } + }
+ // Paper end - Use single player info update packet on join +
+ player.sentListPacket = true; + player.sentListPacket = true;
+ player.suppressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready + player.suppressTrackerForLogin = false;
+ player.level().getChunkSource().addEntity(player); // Paper - Fire PlayerJoinEvent when Player is actually ready; track entity now + ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player);
+ // CraftBukkit end
+ +
+ //player.refreshEntityData(player); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn // Paper - THIS IS NOT NEEDED ANYMORE + this.sendLevelInfo(player, worldserver1);
+ +
+ this.sendLevelInfo(player, level); + if (player.level() == worldserver1 && !worldserver1.players().contains(player)) {
+ + worldserver1.addNewPlayer(player);
+ // 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); + this.server.getCustomBossEvents().onPlayerConnect(player);
+ } + }
+ +
+ level = player.level(); // CraftBukkit - Update in case join event changed it + worldserver1 = player.level();
+ // CraftBukkit end + java.util.Iterator<net.minecraft.world.effect.MobEffectInstance> iterator = player.getActiveEffects().iterator();
+ this.sendActivePlayerEffects(player); + while (iterator.hasNext()) {
+ // Paper - move loading pearls / parent vehicle up + MobEffectInstance mobeffect = iterator.next();
+ player.initInventoryMenu(); + playerconnection.send(new ClientboundUpdateMobEffectPacket(player.getId(), mobeffect, false));
+ 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()) { + if (player.isDeadOrDying()) {
+ net.minecraft.core.Holder<net.minecraft.world.level.biome.Biome> plains = level.registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME) + net.minecraft.core.Holder<net.minecraft.world.level.biome.Biome> plains = worldserver1.registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME)
+ .getOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS); + .getOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS);
+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket( + player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket(
+ new net.minecraft.world.level.chunk.EmptyLevelChunk(level, player.chunkPosition(), plains), + new net.minecraft.world.level.chunk.EmptyLevelChunk(worldserver1, player.chunkPosition(), plains),
+ level.getLightEngine(), null, null, true) // Paper - Anti-Xray + worldserver1.getLightEngine(), null, null, false)
+ ); + );
+ } + }
+ // Paper end - Send empty chunk
+ } + }
+ // Leaves end - replay mod api + // Leaves end - replay mod api
+ +
public void placeNewPlayer(final Connection connection, final ServerPlayer player, final CommonListenerCookie cookie) { public void placeNewPlayer(final Connection connection, final ServerPlayer player, final CommonListenerCookie cookie) {
player.isRealPlayer = true; // Paper player.isRealPlayer = true; // Paper
player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed
@@ -295,6 +464,7 @@ public abstract class PlayerList { @@ -295,6 +414,7 @@ public abstract class PlayerList {
// player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below // player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below
this.players.add(player); this.players.add(player);
@@ -512,7 +462,7 @@ index ed50484ca24ef24b1e5bef9f6369dfa4b30c359c..1a849654efcd42cdea3f85020fd8d018
this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot
this.playersByUUID.put(player.getUUID(), player); this.playersByUUID.put(player.getUUID(), player);
// this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player))); // CraftBukkit - replaced with loop below // this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(player))); // CraftBukkit - replaced with loop below
@@ -510,6 +680,7 @@ public abstract class PlayerList { @@ -510,6 +630,7 @@ public abstract class PlayerList {
} }
protected void save(final ServerPlayer player) { protected void save(final ServerPlayer player) {
@@ -520,7 +470,7 @@ index ed50484ca24ef24b1e5bef9f6369dfa4b30c359c..1a849654efcd42cdea3f85020fd8d018
if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit
player.lastSave = System.nanoTime(); // Folia - region threading - changed to nanoTime tracking player.lastSave = System.nanoTime(); // Folia - region threading - changed to nanoTime tracking
this.playerIo.save(player); this.playerIo.save(player);
@@ -524,6 +695,48 @@ public abstract class PlayerList { @@ -524,6 +645,48 @@ public abstract class PlayerList {
} }
} }
@@ -569,7 +519,7 @@ index ed50484ca24ef24b1e5bef9f6369dfa4b30c359c..1a849654efcd42cdea3f85020fd8d018
public net.kyori.adventure.text.@Nullable Component remove(final ServerPlayer player) { // CraftBukkit - return string // Paper - return Component 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 // 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()))); 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 { @@ -597,6 +760,7 @@ public abstract class PlayerList {
player.getBukkitEntity().packetProcessor.close(); // Folia - region threading player.getBukkitEntity().packetProcessor.close(); // Folia - region threading
player.getAdvancements().clearTriggers(); player.getAdvancements().clearTriggers();
this.players.remove(player); this.players.remove(player);
@@ -577,7 +527,7 @@ index ed50484ca24ef24b1e5bef9f6369dfa4b30c359c..1a849654efcd42cdea3f85020fd8d018
this.playersByName.remove(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot 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 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); this.server.getCustomBossEvents().onPlayerDisconnect(player);
@@ -910,15 +1124,15 @@ public abstract class PlayerList { @@ -910,15 +1074,15 @@ public abstract class PlayerList {
} }
public String[] getPlayerNamesArray() { public String[] getPlayerNamesArray() {
@@ -268,10 +268,10 @@ index aec23bc77fc78eea6a3137b71a6923c217ad40a7..3f3125e0f198f5530057371d631d8e32
} else { } else {
LOGGER.warn("Player {} was dropping items too fast in creative mode, ignoring.", this.player.getPlainTextName()); LOGGER.warn("Player {} was dropping items too fast in creative mode, ignoring.", this.player.getPlainTextName());
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 1a849654efcd42cdea3f85020fd8d018bc651b57..d88c54b27193dc2c4d2c8f539437bf5f6fb8466c 100644 index 64a87785048e103c10f6453e97a37f8501255adc..432a6dfff8e45eccea653b0d4cf04956edec6d98 100644
--- a/net/minecraft/server/players/PlayerList.java --- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java
@@ -308,7 +308,7 @@ public abstract class PlayerList { @@ -279,7 +279,7 @@ public abstract class PlayerList {
// org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol // org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol
// Leaves start - bot support // Leaves start - bot support
@@ -280,7 +280,7 @@ index 1a849654efcd42cdea3f85020fd8d018bc651b57..d88c54b27193dc2c4d2c8f539437bf5f
org.leavesmc.leaves.bot.ServerBot bot = this.server.getBotList().getBotByName(player.getScoreboardName()); org.leavesmc.leaves.bot.ServerBot bot = this.server.getBotList().getBotByName(player.getScoreboardName());
if (bot != null) { if (bot != null) {
this.server.getBotList().removeBot(bot, org.leavesmc.leaves.event.bot.BotRemoveEvent.RemoveReason.INTERNAL, player.getBukkitEntity(), false, false); this.server.getBotList().removeBot(bot, org.leavesmc.leaves.event.bot.BotRemoveEvent.RemoveReason.INTERNAL, player.getBukkitEntity(), false, false);
@@ -491,7 +491,7 @@ public abstract class PlayerList { @@ -441,7 +441,7 @@ public abstract class PlayerList {
org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol
// Leaves start - bot support // Leaves start - bot support
@@ -289,7 +289,7 @@ index 1a849654efcd42cdea3f85020fd8d018bc651b57..d88c54b27193dc2c4d2c8f539437bf5f
org.leavesmc.leaves.bot.ServerBot bot = this.server.getBotList().getBotByName(player.getScoreboardName()); org.leavesmc.leaves.bot.ServerBot bot = this.server.getBotList().getBotByName(player.getScoreboardName());
if (bot != null) { if (bot != null) {
this.server.getBotList().removeBot(bot, org.leavesmc.leaves.event.bot.BotRemoveEvent.RemoveReason.INTERNAL, player.getBukkitEntity(), false, false); this.server.getBotList().removeBot(bot, org.leavesmc.leaves.event.bot.BotRemoveEvent.RemoveReason.INTERNAL, player.getBukkitEntity(), false, false);
@@ -1019,7 +1019,7 @@ public abstract class PlayerList { @@ -969,7 +969,7 @@ public abstract class PlayerList {
).callEvent(); ).callEvent();
// Paper end // Paper end
// Leaves start - bot support // Leaves start - bot support
@@ -66,10 +66,10 @@ index cef9c7887332b91e2466a97a5250db81f7af0dcf..a17212c1910ce9490fe8df8c4ef507e2
public Set<ThrownEnderpearl> getEnderPearls() { public Set<ThrownEnderpearl> getEnderPearls() {
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index d88c54b27193dc2c4d2c8f539437bf5f6fb8466c..998d60b7f3f49b37b01ab6dcf7b3568faa00c276 100644 index 432a6dfff8e45eccea653b0d4cf04956edec6d98..835c13ff69592951261f917ad999d9a7689e802c 100644
--- a/net/minecraft/server/players/PlayerList.java --- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java
@@ -798,11 +798,13 @@ public abstract class PlayerList { @@ -748,11 +748,13 @@ public abstract class PlayerList {
player.unRide(); player.unRide();
for (ThrownEnderpearl enderpearl : player.getEnderPearls()) { for (ThrownEnderpearl enderpearl : player.getEnderPearls()) {
@@ -18,7 +18,6 @@
package org.leavesmc.leaves.bot; package org.leavesmc.leaves.bot;
import ca.spottedleaf.moonrise.common.util.TickThread; import ca.spottedleaf.moonrise.common.util.TickThread;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.Property;
@@ -33,33 +32,20 @@ import io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler;
import io.papermc.paper.util.MCUtil; import io.papermc.paper.util.MCUtil;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.Style;
import net.minecraft.ChatFormatting;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.Connection;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent; import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.protocol.status.ServerStatus;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.CommonListenerCookie;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.server.players.NameAndId;
import net.minecraft.server.players.UserNameToIdResolver;
import net.minecraft.util.ProblemReporter; import net.minecraft.util.ProblemReporter;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.npc.villager.AbstractVillager; import net.minecraft.world.entity.npc.villager.AbstractVillager;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.throwableitemprojectile.ThrownEnderpearl; import net.minecraft.world.entity.projectile.throwableitemprojectile.ThrownEnderpearl;
import net.minecraft.world.item.crafting.RecipeManager;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.gamerules.GameRules;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.scores.PlayerTeam;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -204,110 +190,33 @@ public class BotList {
public ServerBot placeNewBot(@NotNull ServerBot bot, ServerLevel world, Location location, ValueInput save) { public ServerBot placeNewBot(@NotNull ServerBot bot, ServerLevel world, Location location, ValueInput save) {
Optional<ValueInput> optional = Optional.ofNullable(save); Optional<ValueInput> optional = Optional.ofNullable(save);
CommonListenerCookie cookie = CommonListenerCookie.createInitial(bot.gameProfile, false); bot.isRealPlayer = true;
bot.isRealPlayer = true; // Paper bot.loginTime = System.currentTimeMillis();
bot.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed
bot.connection = new ServerBotPacketListenerImpl(this.server, bot); bot.connection = new ServerBotPacketListenerImpl(this.server, bot);
if (bot.connection.connection.getPlayer() != bot) { if (bot.connection.connection.getPlayer() != bot) {
throw new IllegalStateException("Bot connection is not bound to its bot player"); throw new IllegalStateException("Bot connection is not bound to its bot player");
} }
bot.connection.markClientLoaded();
bot.getBukkitEntity().setMetadata("NPC", new FixedMetadataValue(MinecraftInternalPlugin.INSTANCE, true)); bot.getBukkitEntity().setMetadata("NPC", new FixedMetadataValue(MinecraftInternalPlugin.INSTANCE, true));
bot.setServerLevel(world);
BotSpawnLocationEvent event = new BotSpawnLocationEvent(bot.getBukkitEntity(), location); BotSpawnLocationEvent event = new BotSpawnLocationEvent(bot.getBukkitEntity(), location);
this.server.server.getPluginManager().callEvent(event); this.server.server.getPluginManager().callEvent(event);
location = event.getSpawnLocation();
Connection connection = bot.connection.connection; bot.setServerLevel(world);
NameAndId gameProfile = bot.nameAndId(); bot.gameMode.setLevel(bot.level());
UserNameToIdResolver profileCache = this.server.services().nameToIdCache();
Optional<NameAndId> oldProfile = profileCache.get(gameProfile.id());
String oldName = oldProfile.map(NameAndId::name).orElse(gameProfile.name());
if (bot.lastKnownName != null) {
oldName = bot.lastKnownName;
bot.lastKnownName = null;
} // CraftBukkit - Better rename detection
profileCache.add(gameProfile);
final ServerLevel[] level = {bot.level()};
String address = connection.getLoggableAddress(this.server.logIPs());
LevelData levelData = level[0].getLevelData();
ServerGamePacketListenerImpl playerConnection = new ServerGamePacketListenerImpl(this.server, connection, bot, cookie);
// Folia start - rewrite login process
// only after setting the connection listener to game type, add the connection to this regions list
level[0].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[0].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(
bot.getId(),
levelData.isHardcore(),
this.server.levelKeys(),
this.server.getPlayerList().getMaxPlayers(),
io.papermc.paper.FeatureHooks.getViewDistance(level[0]), // Paper - view distance
io.papermc.paper.FeatureHooks.getSimulationDistance(level[0]), // Paper - simulation distance
reducedDebugInfo,
!immediateRespawn,
doLimitedCrafting,
bot.createCommonSpawnInfo(level[0]),
this.server.usesAuthentication(),
this.server.enforceSecureProfile()
)
);
bot.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
playerConnection.send(new ClientboundChangeDifficultyPacket(levelData.getDifficulty(), levelData.isDifficultyLocked()));
playerConnection.send(new ClientboundPlayerAbilitiesPacket(bot.getAbilities()));
playerConnection.send(new ClientboundSetHeldSlotPacket(bot.getInventory().getSelectedSlot()));
RecipeManager recipeManager = this.server.getRecipeManager();
playerConnection.send(
new ClientboundUpdateRecipesPacket(recipeManager.getSynchronizedItemProperties(), recipeManager.getSynchronizedStonecutterRecipes())
);
this.server.getPlayerList().sendPlayerPermissionLevel(bot);
bot.getStats().markAllDirty();
bot.getRecipeBook().sendInitialRecipeBook(bot);
//this.updateEntireScoreboard(level.getScoreboard(), player); // Folia - region threading
this.server.invalidateStatus();
MutableComponent component;
if (bot.getGameProfile().name().equalsIgnoreCase(oldName)) {
component = Component.translatable("multiplayer.player.joined", bot.getDisplayName());
} else {
component = Component.translatable("multiplayer.player.joined.renamed", bot.getDisplayName(), oldName);
}
// CraftBukkit start bot.setPosRaw(location.getX(), location.getY(), location.getZ());
component.withStyle(ChatFormatting.YELLOW); bot.setRot(location.getYaw(), location.getPitch());
final Component[] joinMessage = {component}; // Paper - Adventure
playerConnection.teleport(bot.getX(), bot.getY(), bot.getZ(), bot.getYRot(), bot.getXRot()); bot.connection.teleport(bot.getX(), bot.getY(), bot.getZ(), bot.getYRot(), bot.getXRot());
ServerStatus status = this.server.getStatus();
if (status != null && !cookie.transferred()) {
bot.sendServerStatus(status);
}
// player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below
this.server.getPlayerList().getPlayers().add(bot);
this.bots.add(bot); this.bots.add(bot);
this.botsByName.put(bot.getScoreboardName().toLowerCase(Locale.ROOT), bot); this.botsByName.put(bot.getScoreboardName().toLowerCase(Locale.ROOT), bot);
this.botsByUUID.put(bot.getUUID(), bot); this.botsByUUID.put(bot.getUUID(), bot);
// 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
bot.suppressTrackerForLogin = true;
this.server.getPlayerList().sendLevelInfo(bot, level[0]);
level[0].addNewPlayer(bot);
this.server.getCustomBossEvents().onPlayerConnect(bot); // see commented out section below serverLevel.addPlayerJoin(player);
// Paper end - Fire PlayerJoinEvent when Player is actually ready
bot.initInventoryMenu();
// CraftBukkit start
org.bukkit.craftbukkit.entity.CraftPlayer bukkitPlayer = bot.getBukkitEntity();
// Ensure that player inventory is populated with its viewer bot.suppressTrackerForLogin = true;
bot.containerMenu.transferTo(bot.containerMenu, bukkitPlayer);
Runnable task = () -> { Runnable task = () -> {
optional.ifPresent(nbt -> { optional.ifPresent(nbt -> {
@@ -319,100 +228,21 @@ public class BotList {
BotJoinEvent event1 = new BotJoinEvent(bot.getBukkitEntity(), PaperAdventure.asAdventure(Component.translatable("multiplayer.player.joined", bot.getDisplayName())).style(Style.style(NamedTextColor.YELLOW))); BotJoinEvent event1 = new BotJoinEvent(bot.getBukkitEntity(), PaperAdventure.asAdventure(Component.translatable("multiplayer.player.joined", bot.getDisplayName())).style(Style.style(NamedTextColor.YELLOW)));
this.server.server.getPluginManager().callEvent(event1); this.server.server.getPluginManager().callEvent(event1);
if (!bot.connection.isAcceptingMessages()) { net.kyori.adventure.text.Component joinMessage = event1.joinMessage();
//return; // Folia - region threading - must still allow the player to connect, as we must add to chunk map before handling disconnect if (joinMessage != null && !joinMessage.equals(net.kyori.adventure.text.Component.empty())) {
this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(joinMessage), false);
} }
org.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(bot); // Leaves - protocol bot.renderInfo();
bot.suppressTrackerForLogin = false;
final net.kyori.adventure.text.Component jm = event1.joinMessage(); bot.level().getChunkSource().chunkMap.addEntity(bot);
bot.renderData();
if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure
joinMessage[0] = io.papermc.paper.adventure.PaperAdventure.asVanilla(jm); // Paper - Adventure
this.server.getPlayerList().broadcastSystemMessage(joinMessage[0], false); // Paper - Adventure
}
// CraftBukkit end
// CraftBukkit start - sendAll above replaced with this loop
ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(bot)); // Paper - Add Listing API for Player
final List<ServerPlayer> onlinePlayers = Lists.newArrayListWithExpectedSize(this.server.getPlayerList().getPlayers().size() - 1); // Paper - Use single player info update packet on join
for (ServerPlayer entityplayer1 : this.server.getPlayerList().getPlayers()) { // 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(bot, false));
}
// Paper end - Add Listing API for Player
}
if (entityplayer1 == bot || !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()) {
bot.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers, bot)); // Paper - Add Listing API for Player
}
// Paper end - Use single player info update packet on join
bot.sentListPacket = true;
bot.suppressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready
bot.level().getChunkSource().addEntity(bot); // 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.server.getPlayerList().sendLevelInfo(bot, level[0]);
// CraftBukkit start - Only add if the player wasn't moved in the event
if (bot.level() == level[0] && !level[0].players().contains(bot)) {
level[0].addNewPlayer(bot);
this.server.getCustomBossEvents().onPlayerConnect(bot);
}
level[0] = bot.level(); // CraftBukkit - Update in case join event changed it
// CraftBukkit end
this.server.getPlayerList().sendActivePlayerEffects(bot);
// Paper - move loading pearls / parent vehicle up
bot.initInventoryMenu(); bot.initInventoryMenu();
this.server.notificationManager().playerJoined(bot); botsNameByWorldUuid
playerConnection.resumeFlushing(); .computeIfAbsent(bot.level().uuid.toString(), (k) -> new HashSet<>())
// Paper start - Configurable player collision; Add to collideRule team if needed .add(bot.getBukkitEntity().getName());
final net.minecraft.world.scores.Scoreboard scoreboard = this.server.getPlayerList().getServer().getLevel(Level.OVERWORLD).getScoreboard(); BotList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", bot.getName().getString(), "Local", bot.getId(), bot.level().serverLevelData.getLevelName(), bot.getX(), bot.getY(), bot.getZ());
final PlayerTeam collideRuleTeam = scoreboard.getPlayerTeam(this.server.getPlayerList().collideRuleTeamName);
if (false && this.server.getPlayerList().collideRuleTeamName != null && collideRuleTeam != null && bot.getTeam() == null) { // Folia - region threading
scoreboard.addPlayerToTeam(bot.getScoreboardName(), collideRuleTeam);
}
// Paper end - Configurable player collision
// CraftBukkit start - moved down
LOGGER.info(
"{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", // Paper - add world identifier
bot.getPlainTextName(),
address,
bot.getId(),
level[0].dimension().identifier(), // Paper - add world identifier
bot.getX(),
bot.getY(),
bot.getZ()
);
// CraftBukkit end - moved down
// Paper start - Send empty chunk, so players aren't stuck in the world loading screen with our chunk system not sending chunks when dead
if (bot.isDeadOrDying()) {
net.minecraft.core.Holder<net.minecraft.world.level.biome.Biome> plains = level[0].registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME)
.getOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS);
bot.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket(
new net.minecraft.world.level.chunk.EmptyLevelChunk(level[0], bot.chunkPosition(), plains),
level[0].getLightEngine(), (java.util.BitSet) null, (java.util.BitSet) null, true) // Paper - Anti-Xray
);
}
// Paper end - Send empty chunk
}; };
if (TickThread.isTickThreadFor(world, location.blockX() >> 4, location.blockZ() >> 4)) { if (TickThread.isTickThreadFor(world, location.blockX() >> 4, location.blockZ() >> 4)) {
task.run(); task.run();
@@ -485,13 +315,11 @@ public class BotList {
bot.unRide(); bot.unRide();
for (ThrownEnderpearl thrownEnderpearl : bot.getEnderPearls()) { for (ThrownEnderpearl thrownEnderpearl : bot.getEnderPearls()) {
// Lophine start - restore vanilla ender pearl loading on Folia if (!thrownEnderpearl.level().paperConfig().misc.legacyEnderPearlBehavior) {
thrownEnderpearl.getBukkitEntity().taskScheduler.scheduleOrExecute((Entity pearl) -> { thrownEnderpearl.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, EntityRemoveEvent.Cause.PLAYER_QUIT);
if (!pearl.isRemoved()) { } else {
pearl.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, EntityRemoveEvent.Cause.PLAYER_QUIT); thrownEnderpearl.setOwner(null);
} }
});
// Lophine end - restore vanilla ender pearl loading on Folia
} }
bot.level().getCurrentWorldData().connections.remove(bot.connection.connection); bot.level().getCurrentWorldData().connections.remove(bot.connection.connection);