From db6b5b45ff13428a65ca43622a1a1f5b060dd41f Mon Sep 17 00:00:00 2001
From: Alex <40795980+AlexProgrammerDE@users.noreply.github.com>
Date: Thu, 28 Dec 2023 00:22:44 +0100
Subject: [PATCH] Fix long loading screen when refreshing skins (#10026)

Send `ClientboundGameEventPacket.LEVEL_CHUNKS_LOAD_START` when refreshing players after
player profile changes.
---
 ...-CauldronLevelChange-on-initial-fill.patch | 40 -------------------
 ...tem-locations-dropped-from-campfires.patch | 14 +++----
 .../server/Player.setPlayerProfile-API.patch  | 34 ++++++++++++----
 3 files changed, 32 insertions(+), 56 deletions(-)

diff --git a/patches/server/Fire-CauldronLevelChange-on-initial-fill.patch b/patches/server/Fire-CauldronLevelChange-on-initial-fill.patch
index 803e7b70cd..e631e1e3ca 100644
--- a/patches/server/Fire-CauldronLevelChange-on-initial-fill.patch
+++ b/patches/server/Fire-CauldronLevelChange-on-initial-fill.patch
@@ -6,46 +6,6 @@ Subject: [PATCH] Fire CauldronLevelChange on initial fill
 Also don't fire level events or game events if stalactite
 drip is cancelled
 
-diff --git a/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java b/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java
-+++ b/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java
-@@ -0,0 +0,0 @@ public interface CauldronInteraction {
-             } else {
-                 if (!world.isClientSide) {
-                     // CraftBukkit start
--                    if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, Blocks.WATER_CAULDRON.defaultBlockState(), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY)) {
-+                    if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, Blocks.WATER_CAULDRON.defaultBlockState(), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper
-                         return InteractionResult.SUCCESS;
-                     }
-                     // CraftBukkit end
-@@ -0,0 +0,0 @@ public interface CauldronInteraction {
-             if ((Integer) iblockdata.getValue(LayeredCauldronBlock.LEVEL) != 3 && PotionUtils.getPotion(itemstack) == Potions.WATER) {
-                 if (!world.isClientSide) {
-                     // CraftBukkit start
--                    if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, iblockdata.cycle(LayeredCauldronBlock.LEVEL), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY)) {
-+                    if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, iblockdata.cycle(LayeredCauldronBlock.LEVEL), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper
-                         return InteractionResult.SUCCESS;
-                     }
-                     // CraftBukkit end
-@@ -0,0 +0,0 @@ public interface CauldronInteraction {
-         } else {
-             if (!world.isClientSide) {
-                 // CraftBukkit start
--                if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.CAULDRON.defaultBlockState(), player, CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL)) {
-+                if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.CAULDRON.defaultBlockState(), player, CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL, false)) { // Paper
-                     return InteractionResult.SUCCESS;
-                 }
-                 // CraftBukkit end
-@@ -0,0 +0,0 @@ public interface CauldronInteraction {
-     static InteractionResult emptyBucket(Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, BlockState state, SoundEvent soundEvent) {
-         if (!world.isClientSide) {
-             // CraftBukkit start
--            if (!LayeredCauldronBlock.changeLevel(state, world, pos, state, player, CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY)) {
-+            if (!LayeredCauldronBlock.changeLevel(state, world, pos, state, player, CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY, false)) { // Paper
-                 return InteractionResult.SUCCESS;
-             }
-             // CraftBukkit end
 diff --git a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java
diff --git a/patches/server/Fix-item-locations-dropped-from-campfires.patch b/patches/server/Fix-item-locations-dropped-from-campfires.patch
index 72b68d5023..e2c2851318 100644
--- a/patches/server/Fix-item-locations-dropped-from-campfires.patch
+++ b/patches/server/Fix-item-locations-dropped-from-campfires.patch
@@ -4,7 +4,6 @@ Date: Sat, 3 Oct 2020 20:32:25 -0500
 Subject: [PATCH] Fix item locations dropped from campfires
 
 Fixes #4259 by not flooring the blockposition among other weirdness
-Vanilla Issue: MC-267622
 
 diff --git a/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CampfireBlockEntity.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
@@ -15,14 +14,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                          itemstack1 = CraftItemStack.asNMSCopy(result);
                          // CraftBukkit end
 -                        Containers.dropItemStack(world, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), itemstack1);
-+                        // Paper start
-+                        double deviation = 0.05F * RandomSource.GAUSSIAN_SPREAD_FACTOR;
-+                        while (!itemstack1.isEmpty()) {
-+                            net.minecraft.world.entity.item.ItemEntity droppedItem = new net.minecraft.world.entity.item.ItemEntity(world, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, itemstack1.split(world.random.nextInt(21) + 10));
-+                            droppedItem.setDeltaMovement(world.random.triangle(0.0D, deviation), world.random.triangle(0.2D, deviation), world.random.triangle(0.0D, deviation));
-+                            world.addFreshEntity(droppedItem);
-+                        }
-+                        // Paper end
++                    // Paper start
++                    net.minecraft.world.entity.item.ItemEntity droppedItem = new net.minecraft.world.entity.item.ItemEntity(world, pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, itemstack1.split(world.random.nextInt(21) + 10));
++                    droppedItem.setDeltaMovement(world.random.nextGaussian() * 0.05D, world.random.nextGaussian() * 0.05D + 0.2D, world.random.nextGaussian() * 0.05D);
++                    world.addFreshEntity(droppedItem);
++                    // Paper end
                          campfire.items.set(i, ItemStack.EMPTY);
                          world.sendBlockUpdated(pos, state, state, 3);
                          world.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state));
diff --git a/patches/server/Player.setPlayerProfile-API.patch b/patches/server/Player.setPlayerProfile-API.patch
index 09f3f82586..486c7781ff 100644
--- a/patches/server/Player.setPlayerProfile-API.patch
+++ b/patches/server/Player.setPlayerProfile-API.patch
@@ -39,6 +39,28 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
                          gameprofile = com.destroystokyo.paper.profile.CraftPlayerProfile.asAuthlibCopy(profile);
                          playerName = gameprofile.getName();
                          uniqueId = gameprofile.getId();
+diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -0,0 +0,0 @@ public abstract class PlayerList {
+     }
+ 
+     public void sendPlayerPermissionLevel(ServerPlayer player) {
++    // Paper start - avoid recalculating permissions if possible
++        this.sendPlayerPermissionLevel(player, true);
++    }
++
++    public void sendPlayerPermissionLevel(ServerPlayer player, boolean recalculatePermissions) {
++    // Paper end - avoid recalculating permissions if possible
+         GameProfile gameprofile = player.getGameProfile();
+         int i = this.server.getProfilePermissions(gameprofile);
+ 
+-        this.sendPlayerPermissionLevel(player, i);
++        this.sendPlayerPermissionLevel(player, i, recalculatePermissions); // Paper - avoid recalculating permissions if possible
+     }
+ 
+     public void tick() {
 diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
 --- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
@@ -179,21 +201,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
 +
 +        //Respawn the player then update their position and selected slot
 +        ServerLevel worldserver = handle.serverLevel();
-+        connection.send(new net.minecraft.network.protocol.game.ClientboundRespawnPacket(new net.minecraft.network.protocol.game.CommonPlayerSpawnInfo(worldserver.dimensionTypeId(), worldserver.dimension(), net.minecraft.world.level.biome.BiomeManager.obfuscateSeed(worldserver.getSeed()), handle.gameMode.getGameModeForPlayer(), handle.gameMode.getPreviousGameModeForPlayer(), worldserver.isDebug(), worldserver.isFlat(), handle.getLastDeathLocation(), handle.getPortalCooldown()), net.minecraft.network.protocol.game.ClientboundRespawnPacket.KEEP_ALL_DATA));
++        connection.send(new net.minecraft.network.protocol.game.ClientboundRespawnPacket(handle.createCommonSpawnInfo(worldserver), net.minecraft.network.protocol.game.ClientboundRespawnPacket.KEEP_ALL_DATA));
 +        handle.onUpdateAbilities();
 +        connection.internalTeleport(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch(), java.util.Collections.emptySet());
-+        net.minecraft.server.MinecraftServer.getServer().getPlayerList().sendAllPlayerInfo(handle);
++        net.minecraft.server.players.PlayerList playerList = handle.server.getPlayerList();
++        playerList.sendPlayerPermissionLevel(handle, false);
++        playerList.sendLevelInfo(handle, worldserver);
++        playerList.sendAllPlayerInfo(handle);
 +
 +        // Resend their XP and effects because the respawn packet resets it
 +        connection.send(new net.minecraft.network.protocol.game.ClientboundSetExperiencePacket(handle.experienceProgress, handle.totalExperience, handle.experienceLevel));
 +        for (net.minecraft.world.effect.MobEffectInstance mobEffect : handle.getActiveEffects()) {
 +            connection.send(new net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket(handle.getId(), mobEffect));
 +        }
-+
-+        if (this.isOp()) {
-+            this.setOp(false);
-+            this.setOp(true);
-+        }
 +    }
 +    // Paper end