diff --git a/lophine-server/minecraft-patches/features/0031-Leaves-Fakeplayer.patch b/lophine-server/minecraft-patches/features/0031-Leaves-Fakeplayer.patch index 84a6446..0207796 100644 --- a/lophine-server/minecraft-patches/features/0031-Leaves-Fakeplayer.patch +++ b/lophine-server/minecraft-patches/features/0031-Leaves-Fakeplayer.patch @@ -260,9 +260,18 @@ index 2dae0f4249eade60ac55c9fa289dd146dc3675e1..239cad02f85f0d14d4abfa746ecdc4dd 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 -index 6cd549849bd7fb5d72431f729229d1e967cf0d98..98fa23ea62698ff615a72747db7381efb16beaf9 100644 +index 6cd549849bd7fb5d72431f729229d1e967cf0d98..179e39574fe2a4f395c64c51d6149e01d641a428 100644 --- a/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 } } else { diff --git a/lophine-server/minecraft-patches/features/0032-Leaves-Replay-Mod-API.patch b/lophine-server/minecraft-patches/features/0032-Leaves-Replay-Mod-API.patch index b4c62a3..3ed3387 100644 --- a/lophine-server/minecraft-patches/features/0032-Leaves-Replay-Mod-API.patch +++ b/lophine-server/minecraft-patches/features/0032-Leaves-Replay-Mod-API.patch @@ -311,7 +311,7 @@ index 558f465e9ec2536064716a196d52d78d873d95be..598867ffa41c8d38e0c18550071ac259 } // Leaves end - skip 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 +++ b/net/minecraft/server/players/PlayerList.java @@ -129,6 +129,7 @@ public abstract class PlayerList { @@ -330,90 +330,61 @@ index ed50484ca24ef24b1e5bef9f6369dfa4b30c359c..1a849654efcd42cdea3f85020fd8d018 int count = this.usersCountedAgainstLimit.size(); if (count >= limit) { 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 + // 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.loginTime = System.currentTimeMillis(); // Paper ++ ++ ServerLevel worldserver1 = worldserver; ++ ++ player.setServerLevel(worldserver1); ++ player.gameMode.setLevel(player.level()); ++ ++ LevelData worlddata = worldserver1.getLevelData(); ++ ++ ServerGamePacketListenerImpl playerconnection = new ServerGamePacketListenerImpl(this.server, connection, player, CommonListenerCookie.createInitial(player.gameProfile, false)); ++ GameRules gamerules = worldserver1.getGameRules(); ++ boolean flag = gamerules.get(GameRules.IMMEDIATE_RESPAWN); ++ boolean flag1 = gamerules.get(GameRules.REDUCED_DEBUG_INFO); ++ boolean flag2 = gamerules.get(GameRules.LIMITED_CRAFTING); ++ ++ 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())); + 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()) -+ ); ++ playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); ++ playerconnection.send(new ClientboundPlayerAbilitiesPacket(player.getAbilities())); ++ playerconnection.send(new ClientboundSetHeldSlotPacket(player.getInventory().getSelectedSlot())); ++ RecipeManager craftingmanager = this.server.getRecipeManager(); ++ playerconnection.send(new ClientboundUpdateRecipesPacket(craftingmanager.getSynchronizedItemProperties(), craftingmanager.getSynchronizedStonecutterRecipes())); ++ + this.sendPlayerPermissionLevel(player); + player.getStats().markAllDirty(); + player.getRecipeBook().sendInitialRecipeBook(player); -+ //this.updateEntireScoreboard(level.getScoreboard(), player); // Folia - region threading ++ this.updateEntireScoreboard(worldserver1.getScoreboard(), player); + 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); ++ playerconnection.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot()); ++ ServerStatus serverping = this.server.getStatus(); ++ ++ if (serverping != null) { ++ player.sendServerStatus(serverping); + } + -+ // 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(); ++ worldserver1.addNewPlayer(player); ++ this.server.getCustomBossEvents().onPlayerConnect(player); + org.bukkit.craftbukkit.entity.CraftPlayer bukkitPlayer = player.getBukkitEntity(); + ++ player.containerMenu.transferTo(player.containerMenu, bukkitPlayer); + 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 @@ -431,80 +402,59 @@ index ed50484ca24ef24b1e5bef9f6369dfa4b30c359c..1a849654efcd42cdea3f85020fd8d018 + } + // 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); ++ for (int i = 0; i < this.players.size(); ++i) { ++ ServerPlayer entityplayer1 = this.players.get(i); + -+ 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 ++ if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { + 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()) { -+ 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.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.suppressTrackerForLogin = false; ++ ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); + -+ //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); -+ -+ // CraftBukkit start - Only add if the player wasn't moved in the event -+ if (player.level() == level && !level.players().contains(player)) { -+ level.addNewPlayer(player); ++ if (player.level() == worldserver1 && !worldserver1.players().contains(player)) { ++ worldserver1.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); ++ worldserver1 = player.level(); ++ java.util.Iterator iterator = player.getActiveEffects().iterator(); ++ while (iterator.hasNext()) { ++ MobEffectInstance mobeffect = iterator.next(); ++ playerconnection.send(new ClientboundUpdateMobEffectPacket(player.getId(), mobeffect, false)); + } + + if (player.isDeadOrDying()) { -+ net.minecraft.core.Holder plains = level.registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME) ++ net.minecraft.core.Holder plains = worldserver1.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 ++ new net.minecraft.world.level.chunk.EmptyLevelChunk(worldserver1, player.chunkPosition(), plains), ++ worldserver1.getLightEngine(), null, null, false) + ); + } -+ // 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 { +@@ -295,6 +414,7 @@ public abstract class PlayerList { // player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below this.players.add(player); @@ -512,7 +462,7 @@ index ed50484ca24ef24b1e5bef9f6369dfa4b30c359c..1a849654efcd42cdea3f85020fd8d018 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 { +@@ -510,6 +630,7 @@ public abstract class PlayerList { } protected void save(final ServerPlayer player) { @@ -520,7 +470,7 @@ index ed50484ca24ef24b1e5bef9f6369dfa4b30c359c..1a849654efcd42cdea3f85020fd8d018 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 { +@@ -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 // 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 { +@@ -597,6 +760,7 @@ public abstract class PlayerList { player.getBukkitEntity().packetProcessor.close(); // Folia - region threading player.getAdvancements().clearTriggers(); this.players.remove(player); @@ -577,7 +527,7 @@ index ed50484ca24ef24b1e5bef9f6369dfa4b30c359c..1a849654efcd42cdea3f85020fd8d018 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 { +@@ -910,15 +1074,15 @@ public abstract class PlayerList { } public String[] getPlayerNamesArray() { diff --git a/lophine-server/minecraft-patches/features/0045-Carpet-features.patch b/lophine-server/minecraft-patches/features/0045-Carpet-features.patch index 9b977b6..c3d8c46 100644 --- a/lophine-server/minecraft-patches/features/0045-Carpet-features.patch +++ b/lophine-server/minecraft-patches/features/0045-Carpet-features.patch @@ -268,10 +268,10 @@ index aec23bc77fc78eea6a3137b71a6923c217ad40a7..3f3125e0f198f5530057371d631d8e32 } else { 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 -index 1a849654efcd42cdea3f85020fd8d018bc651b57..d88c54b27193dc2c4d2c8f539437bf5f6fb8466c 100644 +index 64a87785048e103c10f6453e97a37f8501255adc..432a6dfff8e45eccea653b0d4cf04956edec6d98 100644 --- a/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 // Leaves start - bot support @@ -280,7 +280,7 @@ index 1a849654efcd42cdea3f85020fd8d018bc651b57..d88c54b27193dc2c4d2c8f539437bf5f 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); -@@ -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 // Leaves start - bot support @@ -289,7 +289,7 @@ index 1a849654efcd42cdea3f85020fd8d018bc651b57..d88c54b27193dc2c4d2c8f539437bf5f 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); -@@ -1019,7 +1019,7 @@ public abstract class PlayerList { +@@ -969,7 +969,7 @@ public abstract class PlayerList { ).callEvent(); // Paper end // Leaves start - bot support diff --git a/lophine-server/minecraft-patches/features/0049-Restore-vanilla-ender-pearl-loading.patch b/lophine-server/minecraft-patches/features/0049-Restore-vanilla-ender-pearl-loading.patch index 8c70fdf..9ce9665 100644 --- a/lophine-server/minecraft-patches/features/0049-Restore-vanilla-ender-pearl-loading.patch +++ b/lophine-server/minecraft-patches/features/0049-Restore-vanilla-ender-pearl-loading.patch @@ -66,10 +66,10 @@ index cef9c7887332b91e2466a97a5250db81f7af0dcf..a17212c1910ce9490fe8df8c4ef507e2 public Set getEnderPearls() { 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 +++ 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(); for (ThrownEnderpearl enderpearl : player.getEnderPearls()) { diff --git a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotList.java b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotList.java index 2e00c11..b7e851f 100644 --- a/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotList.java +++ b/lophine-server/src/main/java/org/leavesmc/leaves/bot/BotList.java @@ -18,7 +18,6 @@ package org.leavesmc.leaves.bot; import ca.spottedleaf.moonrise.common.util.TickThread; -import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; @@ -33,33 +32,20 @@ import io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler; import io.papermc.paper.util.MCUtil; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.Style; -import net.minecraft.ChatFormatting; 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.MutableComponent; -import net.minecraft.network.protocol.game.*; -import net.minecraft.network.protocol.status.ServerStatus; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; 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.world.entity.Entity; import net.minecraft.world.entity.npc.villager.AbstractVillager; import net.minecraft.world.entity.player.Player; 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.gamerules.GameRules; -import net.minecraft.world.level.storage.LevelData; import net.minecraft.world.level.storage.ValueInput; -import net.minecraft.world.scores.PlayerTeam; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.command.CommandSender; @@ -204,110 +190,33 @@ public class BotList { public ServerBot placeNewBot(@NotNull ServerBot bot, ServerLevel world, Location location, ValueInput save) { Optional optional = Optional.ofNullable(save); - CommonListenerCookie cookie = CommonListenerCookie.createInitial(bot.gameProfile, false); - bot.isRealPlayer = true; // Paper - bot.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed + bot.isRealPlayer = true; + bot.loginTime = System.currentTimeMillis(); bot.connection = new ServerBotPacketListenerImpl(this.server, bot); if (bot.connection.connection.getPlayer() != bot) { 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.setServerLevel(world); BotSpawnLocationEvent event = new BotSpawnLocationEvent(bot.getBukkitEntity(), location); this.server.server.getPluginManager().callEvent(event); + location = event.getSpawnLocation(); - Connection connection = bot.connection.connection; - NameAndId gameProfile = bot.nameAndId(); - UserNameToIdResolver profileCache = this.server.services().nameToIdCache(); - Optional 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); - } + bot.setServerLevel(world); + bot.gameMode.setLevel(bot.level()); - // CraftBukkit start - component.withStyle(ChatFormatting.YELLOW); - final Component[] joinMessage = {component}; // Paper - Adventure - playerConnection.teleport(bot.getX(), bot.getY(), bot.getZ(), bot.getYRot(), bot.getXRot()); - ServerStatus status = this.server.getStatus(); - if (status != null && !cookie.transferred()) { - bot.sendServerStatus(status); - } + bot.setPosRaw(location.getX(), location.getY(), location.getZ()); + bot.setRot(location.getYaw(), location.getPitch()); + + bot.connection.teleport(bot.getX(), bot.getY(), bot.getZ(), bot.getYRot(), bot.getXRot()); - // player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below - this.server.getPlayerList().getPlayers().add(bot); this.bots.add(bot); this.botsByName.put(bot.getScoreboardName().toLowerCase(Locale.ROOT), 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.containerMenu.transferTo(bot.containerMenu, bukkitPlayer); + bot.suppressTrackerForLogin = true; Runnable task = () -> { 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))); this.server.server.getPluginManager().callEvent(event1); - if (!bot.connection.isAcceptingMessages()) { - //return; // Folia - region threading - must still allow the player to connect, as we must add to chunk map before handling disconnect + net.kyori.adventure.text.Component joinMessage = event1.joinMessage(); + 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(); - - 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 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.level().getChunkSource().chunkMap.addEntity(bot); + bot.renderData(); bot.initInventoryMenu(); - this.server.notificationManager().playerJoined(bot); - playerConnection.resumeFlushing(); - // Paper start - Configurable player collision; Add to collideRule team if needed - final net.minecraft.world.scores.Scoreboard scoreboard = this.server.getPlayerList().getServer().getLevel(Level.OVERWORLD).getScoreboard(); - 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 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 + botsNameByWorldUuid + .computeIfAbsent(bot.level().uuid.toString(), (k) -> new HashSet<>()) + .add(bot.getBukkitEntity().getName()); + 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()); }; if (TickThread.isTickThreadFor(world, location.blockX() >> 4, location.blockZ() >> 4)) { task.run(); @@ -485,13 +315,11 @@ public class BotList { bot.unRide(); for (ThrownEnderpearl thrownEnderpearl : bot.getEnderPearls()) { - // Lophine start - restore vanilla ender pearl loading on Folia - thrownEnderpearl.getBukkitEntity().taskScheduler.scheduleOrExecute((Entity pearl) -> { - if (!pearl.isRemoved()) { - pearl.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, EntityRemoveEvent.Cause.PLAYER_QUIT); - } - }); - // Lophine end - restore vanilla ender pearl loading on Folia + if (!thrownEnderpearl.level().paperConfig().misc.legacyEnderPearlBehavior) { + thrownEnderpearl.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER, EntityRemoveEvent.Cause.PLAYER_QUIT); + } else { + thrownEnderpearl.setOwner(null); + } } bot.level().getCurrentWorldData().connections.remove(bot.connection.connection);