diff options
Diffstat (limited to 'patches/server/0416-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch')
-rw-r--r-- | patches/server/0416-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/patches/server/0416-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch b/patches/server/0416-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch new file mode 100644 index 0000000000..90f095a707 --- /dev/null +++ b/patches/server/0416-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch @@ -0,0 +1,346 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf <[email protected]> +Date: Tue, 5 May 2020 20:40:53 -0700 +Subject: [PATCH] Optimize anyPlayerCloseEnoughForSpawning to use distance maps + +Use a distance map to find the players in range quickly + +diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java +index b550f302b1bb6ef92987c8c3431b94c3ba3a091d..2add24517d38708a84e7f8ec25fbe9c62309375e 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java ++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java +@@ -83,16 +83,29 @@ public class ChunkHolder { + + // Paper start + public void onChunkAdd() { +- ++ // Paper start - optimise anyPlayerCloseEnoughForSpawning ++ long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos); ++ this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); ++ this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); ++ // Paper end - optimise anyPlayerCloseEnoughForSpawning + } + + public void onChunkRemove() { +- ++ // Paper start - optimise anyPlayerCloseEnoughForSpawning ++ this.playersInMobSpawnRange = null; ++ this.playersInChunkTickRange = null; ++ // Paper end - optimise anyPlayerCloseEnoughForSpawning + } + // Paper end + + public final io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder; // Paper - rewrite chunk system + ++ // Paper start - optimise anyPlayerCloseEnoughForSpawning ++ // cached here to avoid a map lookup ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInMobSpawnRange; ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInChunkTickRange; ++ // Paper end - optimise anyPlayerCloseEnoughForSpawning ++ + public ChunkHolder(ChunkPos pos, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.PlayerProvider playersWatchingChunkProvider, io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder) { // Paper - rewrite chunk system + this.newChunkHolder = newChunkHolder; // Paper - rewrite chunk system + this.chunkToSaveHistory = null; +diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java +index 8dce24ae70057115feff193a1307eb2437e16773..50e9d29b5a0ea6d11a44c41c49d6f52bc464e6e2 100644 +--- a/src/main/java/net/minecraft/server/level/ChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java +@@ -152,12 +152,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + // Paper start - distance maps + private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); + public final io.papermc.paper.chunk.PlayerChunkLoader playerChunkManager = new io.papermc.paper.chunk.PlayerChunkLoader(this, this.pooledLinkedPlayerHashSets); // Paper - replace chunk loader ++ // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning ++ // A note about the naming used here: ++ // Previously, mojang used a "spawn range" of 8 for controlling both ticking and ++ // mob spawn range. However, spigot makes the spawn range configurable by ++ // checking if the chunk is in the tick range (8) and the spawn range ++ // obviously this means a spawn range > 8 cannot be implemented ++ ++ // these maps are named after spigot's uses ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick ++ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap; ++ // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + + void addPlayerToDistanceMaps(ServerPlayer player) { + this.playerChunkManager.addPlayer(player); // Paper - replace chunk loader + int chunkX = MCUtil.getChunkCoordinate(player.getX()); + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); + // Note: players need to be explicitly added to distance maps before they can be updated ++ this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + // Paper start - per player mob spawning + if (this.playerMobDistanceMap != null) { + this.playerMobDistanceMap.add(player, chunkX, chunkZ, net.minecraft.server.ChunkSystem.getTickViewDistance(player)); +@@ -168,6 +180,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + void removePlayerFromDistanceMaps(ServerPlayer player) { + this.playerChunkManager.removePlayer(player); // Paper - replace chunk loader + ++ // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning ++ this.playerMobSpawnMap.remove(player); ++ this.playerChunkTickRangeMap.remove(player); ++ // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + // Paper start - per player mob spawning + if (this.playerMobDistanceMap != null) { + this.playerMobDistanceMap.remove(player); +@@ -180,6 +196,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + int chunkZ = MCUtil.getChunkCoordinate(player.getZ()); + // Note: players need to be explicitly added to distance maps before they can be updated + this.playerChunkManager.updatePlayer(player); // Paper - replace chunk loader ++ this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + // Paper start - per player mob spawning + if (this.playerMobDistanceMap != null) { + this.playerMobDistanceMap.update(player, chunkX, chunkZ, net.minecraft.server.ChunkSystem.getTickViewDistance(player)); +@@ -267,6 +284,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + this.regionManagers.add(this.dataRegionManager); + // Paper end + this.playerMobDistanceMap = this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets) : null; // Paper ++ // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning ++ this.playerChunkTickRangeMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInChunkTickRange = newState; ++ } ++ }, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInChunkTickRange = newState; ++ } ++ }); ++ this.playerMobSpawnMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInMobSpawnRange = newState; ++ } ++ }, ++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> { ++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ)); ++ if (playerChunk != null) { ++ playerChunk.playersInMobSpawnRange = newState; ++ } ++ }); ++ // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning + } + + protected ChunkGenerator generator() { +@@ -822,43 +871,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider + return this.anyPlayerCloseEnoughForSpawning(pos, false); + } + +- boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) { +- int chunkRange = level.spigotConfig.mobSpawnRange; +- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; +- chunkRange = (chunkRange > 8) ? 8 : chunkRange; +- +- final int finalChunkRange = chunkRange; // Paper for lambda below +- //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event +- double blockRange = 16384.0D; // Paper +- // Spigot end +- long i = chunkcoordintpair.toLong(); ++ // Paper start - optimise anyPlayerCloseEnoughForSpawning ++ final boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) { ++ return this.anyPlayerCloseEnoughForSpawning(this.getUpdatingChunkIfPresent(chunkcoordintpair.toLong()), chunkcoordintpair, reducedRange); ++ } + +- if (!this.distanceManager.hasPlayersNearby(i)) { ++ final boolean anyPlayerCloseEnoughForSpawning(ChunkHolder playerchunk, ChunkPos chunkcoordintpair, boolean reducedRange) { ++ // this function is so hot that removing the map lookup call can have an order of magnitude impact on its performance ++ // tested and confirmed via System.nanoTime() ++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInRange = reducedRange ? playerchunk.playersInMobSpawnRange : playerchunk.playersInChunkTickRange; ++ if (playersInRange == null) { + return false; +- } else { +- Iterator iterator = this.playerMap.getPlayers(i).iterator(); +- +- ServerPlayer entityplayer; ++ } ++ Object[] backingSet = playersInRange.getBackingSet(); + +- do { +- if (!iterator.hasNext()) { +- return false; ++ if (reducedRange) { ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object raw = backingSet[i]; ++ if (!(raw instanceof ServerPlayer player)) { ++ continue; + } +- +- entityplayer = (ServerPlayer) iterator.next(); +- // Paper start - add PlayerNaturallySpawnCreaturesEvent +- com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; +- blockRange = 16384.0D; +- if (reducedRange) { +- event = entityplayer.playerNaturallySpawnedEvent; +- if (event == null || event.isCancelled()) return false; +- blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); ++ // don't check spectator and whatnot, already handled by mob spawn map update ++ if (euclideanDistanceSquared(chunkcoordintpair, player) < player.lastEntitySpawnRadiusSquared) { ++ return true; // in range + } +- // Paper end +- } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot +- +- return true; ++ } ++ } else { ++ final double range = (DistanceManager.MOB_SPAWN_RANGE * 16) * (DistanceManager.MOB_SPAWN_RANGE * 16); ++ // before spigot, mob spawn range was actually mob spawn range + tick range, but it was split ++ for (int i = 0, len = backingSet.length; i < len; ++i) { ++ Object raw = backingSet[i]; ++ if (!(raw instanceof ServerPlayer player)) { ++ continue; ++ } ++ // don't check spectator and whatnot, already handled by mob spawn map update ++ if (euclideanDistanceSquared(chunkcoordintpair, player) < range) { ++ return true; // in range ++ } ++ } + } ++ // no players in range ++ return false; ++ // Paper end - optimise anyPlayerCloseEnoughForSpawning + } + + public List<ServerPlayer> getPlayersCloseForSpawning(ChunkPos pos) { +diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java +index d3c3db919e9b0507e8543313d9028394e5163673..52cba8f68d274cce106304aef1249a95474d3238 100644 +--- a/src/main/java/net/minecraft/server/level/DistanceManager.java ++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java +@@ -54,7 +54,7 @@ public abstract class DistanceManager { + private static final int BLOCK_TICKING_LEVEL_THRESHOLD = 33; + final Long2ObjectMap<ObjectSet<ServerPlayer>> playersPerChunk = new Long2ObjectOpenHashMap(); + // Paper - rewrite chunk system +- private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); ++ public static final int MOB_SPAWN_RANGE = 8; // private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8); // Paper - no longer used + //private final TickingTracker tickingTicketsTracker = new TickingTracker(); // Paper - no longer used + //private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); // Paper - no longer used + // Paper - rewrite chunk system +@@ -142,7 +142,7 @@ public abstract class DistanceManager { + long i = chunkcoordintpair.toLong(); + + // Paper - no longer used +- this.naturalSpawnChunkCounter.update(i, 0, true); ++ //this.naturalSpawnChunkCounter.update(i, 0, true); // Paper - no longer used + //this.playerTicketManager.update(i, 0, true); // Paper - no longer used + //this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used + } +@@ -156,7 +156,7 @@ public abstract class DistanceManager { + if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully. + if (objectset == null || objectset.isEmpty()) { // Paper + this.playersPerChunk.remove(i); +- this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); ++ // this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); // Paper - no longer used + //this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Paper - no longer used + //this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used + } +@@ -198,13 +198,17 @@ public abstract class DistanceManager { + } + + public int getNaturalSpawnChunkCount() { +- this.naturalSpawnChunkCounter.runAllUpdates(); +- return this.naturalSpawnChunkCounter.chunks.size(); ++ // Paper start - use distance map to implement ++ // note: this is the spawn chunk count ++ return this.chunkMap.playerChunkTickRangeMap.size(); ++ // Paper end - use distance map to implement + } + + public boolean hasPlayersNearby(long chunkPos) { +- this.naturalSpawnChunkCounter.runAllUpdates(); +- return this.naturalSpawnChunkCounter.chunks.containsKey(chunkPos); ++ // Paper start - use distance map to implement ++ // note: this is the is spawn chunk method ++ return this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(chunkPos) != null; ++ // Paper end - use distance map to implement + } + + public String getDebugStatus() { +diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +index 60264feb656861d5a9474fe4285ac69d8d12269e..44766ea7e5dd8f8411b52cf259187d7557cc0c23 100644 +--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java ++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java +@@ -657,6 +657,37 @@ public class ServerChunkCache extends ChunkSource { + if (flag) { + this.chunkMap.tick(); + } else { ++ // Paper start - optimize isOutisdeRange ++ ChunkMap playerChunkMap = this.chunkMap; ++ for (ServerPlayer player : this.level.players) { ++ if (!player.affectsSpawning || player.isSpectator()) { ++ playerChunkMap.playerMobSpawnMap.remove(player); ++ continue; ++ } ++ ++ int viewDistance = this.chunkMap.getEffectiveViewDistance(); ++ ++ // copied and modified from isOutisdeRange ++ int chunkRange = level.spigotConfig.mobSpawnRange; ++ chunkRange = (chunkRange > viewDistance) ? (byte)viewDistance : chunkRange; ++ chunkRange = (chunkRange > DistanceManager.MOB_SPAWN_RANGE) ? DistanceManager.MOB_SPAWN_RANGE : chunkRange; ++ ++ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(player.getBukkitEntity(), (byte)chunkRange); ++ event.callEvent(); ++ if (event.isCancelled() || event.getSpawnRadius() < 0 || playerChunkMap.playerChunkTickRangeMap.getLastViewDistance(player) == -1) { ++ playerChunkMap.playerMobSpawnMap.remove(player); ++ continue; ++ } ++ ++ int range = Math.min(event.getSpawnRadius(), 32); // limit to max view distance ++ int chunkX = net.minecraft.server.MCUtil.getChunkCoordinate(player.getX()); ++ int chunkZ = net.minecraft.server.MCUtil.getChunkCoordinate(player.getZ()); ++ ++ playerChunkMap.playerMobSpawnMap.addOrUpdate(player, chunkX, chunkZ, range); ++ player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in anyPlayerCloseEnoughForSpawning ++ player.playerNaturallySpawnedEvent = event; ++ } ++ // Paper end - optimize isOutisdeRange + LevelData worlddata = this.level.getLevelData(); + ProfilerFiller gameprofilerfiller = this.level.getProfiler(); + +@@ -700,15 +731,7 @@ public class ServerChunkCache extends ChunkSource { + boolean flag2 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit + + Collections.shuffle(list); +- // Paper start - call player naturally spawn event +- int chunkRange = level.spigotConfig.mobSpawnRange; +- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange; +- chunkRange = Math.min(chunkRange, 8); +- for (ServerPlayer entityPlayer : this.level.players()) { +- entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange); +- entityPlayer.playerNaturallySpawnedEvent.callEvent(); +- }; +- // Paper end ++ // Paper - moved natural spawn event up + Iterator iterator1 = list.iterator(); + + while (iterator1.hasNext()) { +@@ -716,9 +739,9 @@ public class ServerChunkCache extends ChunkSource { + LevelChunk chunk1 = chunkproviderserver_a.chunk; + ChunkPos chunkcoordintpair = chunk1.getPos(); + +- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair)) { ++ if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning + chunk1.incrementInhabitedTime(j); +- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot ++ if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning + NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); + } + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java +index fdc1d1d5840ee7f12e4a72656698924c51fea05c..0faa0d73907e6ef1285eba4cf7d692a5f8c3a0e3 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -263,6 +263,7 @@ public class ServerPlayer extends Player { + public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper + + public boolean isRealPlayer; // Paper ++ public double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks + public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> cachedSingleHashSet; // Paper + + public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, @Nullable ProfilePublicKey publicKey) { |