From 35afd218f53cf9970dd8a1f5cc18e364492c0762 Mon Sep 17 00:00:00 2001 From: Bjarne Koll Date: Sun, 15 Dec 2024 05:32:25 +0100 Subject: [PATCH] net/minecraft/server/level --- .../server/level/ChunkHolder.java.patch | 155 +- .../server/level/ChunkMap.java.patch | 375 ++++ .../server/level/DistanceManager.java.patch | 128 ++ .../server/level/ServerChunkCache.java.patch | 171 +- .../server/level/ServerEntity.java.patch | 134 ++ .../server/level/ServerLevel.java.patch | 1217 +++++++++++ .../server/level/ServerPlayer.java.patch | 1684 +++++++++++++++ .../level/ServerPlayerGameMode.java.patch | 299 ++- .../server/level/TicketType.java.patch | 16 +- .../server/level/WorldGenRegion.java.patch | 75 +- .../server/level/ChunkMap.java.patch | 433 ---- .../server/level/DistanceManager.java.patch | 152 -- .../server/level/ServerEntity.java.patch | 179 -- .../server/level/ServerLevel.java.patch | 1261 ----------- .../server/level/ServerPlayer.java.patch | 1891 ----------------- .../paper/configuration/Configurations.java | 2 +- .../configuration/PaperConfigurations.java | 2 +- .../org/bukkit/craftbukkit/CraftServer.java | 2 +- .../org/bukkit/craftbukkit/CraftWorld.java | 7 +- 19 files changed, 3854 insertions(+), 4329 deletions(-) rename paper-server/patches/{unapplied => sources}/net/minecraft/server/level/ChunkHolder.java.patch (55%) create mode 100644 paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch create mode 100644 paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch rename paper-server/patches/{unapplied => sources}/net/minecraft/server/level/ServerChunkCache.java.patch (55%) create mode 100644 paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch create mode 100644 paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch create mode 100644 paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch rename paper-server/patches/{unapplied => sources}/net/minecraft/server/level/ServerPlayerGameMode.java.patch (63%) rename paper-server/patches/{unapplied => sources}/net/minecraft/server/level/TicketType.java.patch (63%) rename paper-server/patches/{unapplied => sources}/net/minecraft/server/level/WorldGenRegion.java.patch (58%) delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/level/ChunkMap.java.patch delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/level/DistanceManager.java.patch delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/level/ServerEntity.java.patch delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/level/ServerLevel.java.patch delete mode 100644 paper-server/patches/unapplied/net/minecraft/server/level/ServerPlayer.java.patch diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/ChunkHolder.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch similarity index 55% rename from paper-server/patches/unapplied/net/minecraft/server/level/ChunkHolder.java.patch rename to paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch index 4516416d44..9cb5de711a 100644 --- a/paper-server/patches/unapplied/net/minecraft/server/level/ChunkHolder.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkHolder.java.patch @@ -1,42 +1,20 @@ --- a/net/minecraft/server/level/ChunkHolder.java +++ b/net/minecraft/server/level/ChunkHolder.java -@@ -28,14 +28,18 @@ - import net.minecraft.world.level.chunk.status.ChunkStatus; - import net.minecraft.world.level.lighting.LevelLightEngine; - -+// CraftBukkit start -+import net.minecraft.server.MinecraftServer; -+// CraftBukkit end -+ - public class ChunkHolder extends GenerationChunkHolder { - +@@ -33,9 +_,9 @@ public static final ChunkResult UNLOADED_LEVEL_CHUNK = ChunkResult.error("Unloaded level chunk"); - private static final CompletableFuture> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(ChunkHolder.UNLOADED_LEVEL_CHUNK); + private static final CompletableFuture> UNLOADED_LEVEL_CHUNK_FUTURE = CompletableFuture.completedFuture(UNLOADED_LEVEL_CHUNK); private final LevelHeightAccessor levelHeightAccessor; -- private volatile CompletableFuture> fullChunkFuture; -- private volatile CompletableFuture> tickingChunkFuture; -- private volatile CompletableFuture> entityTickingChunkFuture; +- private volatile CompletableFuture> fullChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE; +- private volatile CompletableFuture> tickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE; +- private volatile CompletableFuture> entityTickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE; + private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage + private volatile CompletableFuture> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage + private volatile CompletableFuture> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage public int oldTicketLevel; private int ticketLevel; private int queueLevel; -@@ -58,9 +62,9 @@ - this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; - this.blockChangedLightSectionFilter = new BitSet(); - this.skyChangedLightSectionFilter = new BitSet(); -- this.pendingFullStateConfirmation = CompletableFuture.completedFuture((Object) null); -- this.sendSync = CompletableFuture.completedFuture((Object) null); -- this.saveSync = CompletableFuture.completedFuture((Object) null); -+ this.pendingFullStateConfirmation = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error -+ this.sendSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error -+ this.saveSync = CompletableFuture.completedFuture(null); // CraftBukkit - decompile error - this.levelHeightAccessor = world; - this.lightEngine = lightingProvider; - this.onLevelChange = levelUpdateListener; -@@ -72,6 +76,18 @@ - this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()]; +@@ -71,6 +_,18 @@ + this.changedBlocksPerSection = new ShortSet[levelHeightAccessor.getSectionsCount()]; } + // CraftBukkit start @@ -54,64 +32,31 @@ public CompletableFuture> getTickingChunkFuture() { return this.tickingChunkFuture; } -@@ -85,8 +101,8 @@ - } - - @Nullable -- public LevelChunk getTickingChunk() { -- return (LevelChunk) ((ChunkResult) this.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).orElse((Object) null); -+ public final LevelChunk getTickingChunk() { // Paper - final for inline -+ return (LevelChunk) ((ChunkResult) this.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).orElse(null); // CraftBukkit - decompile error - } - - @Nullable -@@ -138,6 +154,7 @@ +@@ -129,6 +_,7 @@ + } else { boolean flag = this.hasChangedSections; - int i = this.levelHeightAccessor.getSectionIndex(pos.getY()); - -+ if (i < 0 || i >= this.changedBlocksPerSection.length) return false; // CraftBukkit - SPIGOT-6086, SPIGOT-6296 - if (this.changedBlocksPerSection[i] == null) { + int sectionIndex = this.levelHeightAccessor.getSectionIndex(pos.getY()); ++ if (sectionIndex < 0 || sectionIndex >= this.changedBlocksPerSection.length) return false; // CraftBukkit - SPIGOT-6086, SPIGOT-6296 + if (this.changedBlocksPerSection[sectionIndex] == null) { this.hasChangedSections = true; - this.changedBlocksPerSection[i] = new ShortOpenHashSet(); -@@ -224,8 +241,11 @@ - ClientboundSectionBlocksUpdatePacket packetplayoutmultiblockchange = new ClientboundSectionBlocksUpdatePacket(sectionposition, shortset, chunksection); - - this.broadcast(list, packetplayoutmultiblockchange); -+ // CraftBukkit start -+ List finalList = list; - packetplayoutmultiblockchange.runUpdates((blockposition1, iblockdata1) -> { -- this.broadcastBlockEntityIfNeeded(list, world, blockposition1, iblockdata1); -+ this.broadcastBlockEntityIfNeeded(finalList, world, blockposition1, iblockdata1); -+ // CraftBukkit end - }); - } - } -@@ -291,7 +311,7 @@ - this.pendingFullStateConfirmation = completablefuture1; - chunkFuture.thenAccept((chunkresult) -> { - chunkresult.ifSuccess((chunk) -> { -- completablefuture1.complete((Object) null); -+ completablefuture1.complete(null); // CraftBukkit - decompile error - }); - }); - } -@@ -301,6 +321,38 @@ - chunkLoadingManager.onFullChunkStatusChange(this.pos, target); + this.changedBlocksPerSection[sectionIndex] = new ShortOpenHashSet(); +@@ -274,6 +_,38 @@ + chunkMap.onFullChunkStatusChange(this.pos, fullChunkStatus); } + // CraftBukkit start + // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. + // SPIGOT-7780: Moved out of updateFutures to call all chunk unload events before calling updateHighestAllowedStatus for all chunks -+ protected void callEventIfUnloading(ChunkMap playerchunkmap) { ++ protected void callEventIfUnloading(ChunkMap chunkMap) { + FullChunkStatus oldFullChunkStatus = ChunkLevel.fullStatus(this.oldTicketLevel); + FullChunkStatus newFullChunkStatus = ChunkLevel.fullStatus(this.ticketLevel); + boolean oldIsFull = oldFullChunkStatus.isOrAfter(FullChunkStatus.FULL); + boolean newIsFull = newFullChunkStatus.isOrAfter(FullChunkStatus.FULL); + if (oldIsFull && !newIsFull) { + this.getFullChunkFuture().thenAccept((either) -> { -+ LevelChunk chunk = (LevelChunk) either.orElse(null); ++ LevelChunk chunk = either.orElse(null); + if (chunk != null) { -+ playerchunkmap.callbackExecutor.execute(() -> { ++ chunkMap.callbackExecutor.execute(() -> { + // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick + // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag. + // These actions may however happen deferred, so we manually set the needsSaving flag already here. @@ -121,26 +66,26 @@ + } + }).exceptionally((throwable) -> { + // ensure exceptions are printed, by default this is not the case -+ MinecraftServer.LOGGER.error("Failed to schedule unload callback for chunk " + ChunkHolder.this.pos, throwable); ++ net.minecraft.server.MinecraftServer.LOGGER.error("Failed to schedule unload callback for chunk " + ChunkHolder.this.pos, throwable); + return null; + }); + + // Run callback right away if the future was already done -+ playerchunkmap.callbackExecutor.run(); ++ chunkMap.callbackExecutor.run(); + } + } + // CraftBukkit end + - protected void updateFutures(ChunkMap chunkLoadingManager, Executor executor) { - FullChunkStatus fullchunkstatus = ChunkLevel.fullStatus(this.oldTicketLevel); - FullChunkStatus fullchunkstatus1 = ChunkLevel.fullStatus(this.ticketLevel); -@@ -309,12 +361,28 @@ - - this.wasAccessibleSinceLastSave |= flag1; - if (!flag && flag1) { + protected void updateFutures(ChunkMap chunkMap, Executor executor) { + FullChunkStatus fullChunkStatus = ChunkLevel.fullStatus(this.oldTicketLevel); + FullChunkStatus fullChunkStatus1 = ChunkLevel.fullStatus(this.ticketLevel); +@@ -281,12 +_,28 @@ + boolean isOrAfter1 = fullChunkStatus1.isOrAfter(FullChunkStatus.FULL); + this.wasAccessibleSinceLastSave |= isOrAfter1; + if (!isOrAfter && isOrAfter1) { + int expectCreateCount = ++this.fullChunkCreateCount; // Paper - this.fullChunkFuture = chunkLoadingManager.prepareAccessibleChunk(this); - this.scheduleFullChunkPromotion(chunkLoadingManager, this.fullChunkFuture, executor, FullChunkStatus.FULL); + this.fullChunkFuture = chunkMap.prepareAccessibleChunk(this); + this.scheduleFullChunkPromotion(chunkMap, this.fullChunkFuture, executor, FullChunkStatus.FULL); + // Paper start - cache ticking ready status + this.fullChunkFuture.thenAccept(chunkResult -> { + chunkResult.ifSuccess(chunk -> { @@ -154,19 +99,19 @@ this.addSaveDependency(this.fullChunkFuture); } - if (flag && !flag1) { + if (isOrAfter && !isOrAfter1) { + // Paper start + if (this.isFullChunkReady) { + ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotBorder(this.fullChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper + } + // Paper end - this.fullChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); - this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; + this.fullChunkFuture.complete(UNLOADED_LEVEL_CHUNK); + this.fullChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE; } -@@ -325,11 +393,25 @@ - if (!flag2 && flag3) { - this.tickingChunkFuture = chunkLoadingManager.prepareTickingChunk(this); - this.scheduleFullChunkPromotion(chunkLoadingManager, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING); +@@ -296,11 +_,25 @@ + if (!isOrAfter2 && isOrAfter3) { + this.tickingChunkFuture = chunkMap.prepareTickingChunk(this); + this.scheduleFullChunkPromotion(chunkMap, this.tickingChunkFuture, executor, FullChunkStatus.BLOCK_TICKING); + // Paper start - cache ticking ready status + this.tickingChunkFuture.thenAccept(chunkResult -> { + chunkResult.ifSuccess(chunk -> { @@ -179,21 +124,21 @@ this.addSaveDependency(this.tickingChunkFuture); } - if (flag2 && !flag3) { -- this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); + if (isOrAfter2 && !isOrAfter3) { +- this.tickingChunkFuture.complete(UNLOADED_LEVEL_CHUNK); + // Paper start + if (this.isTickingReady) { + ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotTicking(this.tickingChunkFuture.join().orElseThrow(IllegalStateException::new), this); // Paper + } + // Paper end + this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage - this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; + this.tickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE; } -@@ -343,11 +425,24 @@ +@@ -313,11 +_,24 @@ - this.entityTickingChunkFuture = chunkLoadingManager.prepareEntityTickingChunk(this); - this.scheduleFullChunkPromotion(chunkLoadingManager, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING); + this.entityTickingChunkFuture = chunkMap.prepareEntityTickingChunk(this); + this.scheduleFullChunkPromotion(chunkMap, this.entityTickingChunkFuture, executor, FullChunkStatus.ENTITY_TICKING); + // Paper start - cache ticking ready status + this.entityTickingChunkFuture.thenAccept(chunkResult -> { + chunkResult.ifSuccess(chunk -> { @@ -205,39 +150,39 @@ this.addSaveDependency(this.entityTickingChunkFuture); } - if (flag4 && !flag5) { -- this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); + if (isOrAfter4 && !isOrAfter5) { +- this.entityTickingChunkFuture.complete(UNLOADED_LEVEL_CHUNK); + // Paper start + if (this.isEntityTickingReady) { + ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkNotEntityTicking(this.entityTickingChunkFuture.join().orElseThrow(IllegalStateException::new), this); + } + // Paper end + this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage - this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; + this.entityTickingChunkFuture = UNLOADED_LEVEL_CHUNK_FUTURE; } -@@ -357,6 +452,26 @@ +@@ -327,6 +_,26 @@ this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel); this.oldTicketLevel = this.ticketLevel; + // CraftBukkit start + // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. -+ if (!fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) { ++ if (!fullChunkStatus.isOrAfter(FullChunkStatus.FULL) && fullChunkStatus1.isOrAfter(FullChunkStatus.FULL)) { + this.getFullChunkFuture().thenAccept((either) -> { + LevelChunk chunk = (LevelChunk) either.orElse(null); + if (chunk != null) { -+ chunkLoadingManager.callbackExecutor.execute(() -> { ++ chunkMap.callbackExecutor.execute(() -> { + chunk.loadCallback(); + }); + } + }).exceptionally((throwable) -> { + // ensure exceptions are printed, by default this is not the case -+ MinecraftServer.LOGGER.error("Failed to schedule load callback for chunk " + ChunkHolder.this.pos, throwable); ++ net.minecraft.server.MinecraftServer.LOGGER.error("Failed to schedule load callback for chunk " + ChunkHolder.this.pos, throwable); + return null; + }); + + // Run callback right away if the future was already done -+ chunkLoadingManager.callbackExecutor.run(); ++ chunkMap.callbackExecutor.run(); + } + // CraftBukkit end } diff --git a/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch new file mode 100644 index 0000000000..559aabf756 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/level/ChunkMap.java.patch @@ -0,0 +1,375 @@ +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -145,6 +_,33 @@ + public int serverViewDistance; + private final WorldGenContext worldGenContext; + ++ // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() ++ public final CallbackExecutor callbackExecutor = new CallbackExecutor(); ++ public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable { ++ ++ private final java.util.Queue queue = new java.util.ArrayDeque<>(); ++ ++ @Override ++ public void execute(Runnable runnable) { ++ this.queue.add(runnable); ++ } ++ ++ @Override ++ public void run() { ++ Runnable task; ++ while ((task = this.queue.poll()) != null) { ++ task.run(); ++ } ++ } ++ }; ++ // CraftBukkit end ++ ++ // Paper start ++ public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) { ++ return this.pendingUnloads.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); ++ } ++ // Paper end ++ + public ChunkMap( + ServerLevel level, + LevelStorageSource.LevelStorageAccess levelStorageAccess, +@@ -171,13 +_,19 @@ + this.level = level; + RegistryAccess registryAccess = level.registryAccess(); + long seed = level.getSeed(); +- if (generator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) { ++ // CraftBukkit start - SPIGOT-7051: It's a rigged game! Use delegate for random state creation, otherwise it is not so random. ++ ChunkGenerator randomGenerator = generator; ++ if (randomGenerator instanceof org.bukkit.craftbukkit.generator.CustomChunkGenerator customChunkGenerator) { ++ randomGenerator = customChunkGenerator.getDelegate(); ++ } ++ if (randomGenerator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) { ++ // CraftBukkit end + this.randomState = RandomState.create(noiseBasedChunkGenerator.generatorSettings().value(), registryAccess.lookupOrThrow(Registries.NOISE), seed); + } else { + this.randomState = RandomState.create(NoiseGeneratorSettings.dummy(), registryAccess.lookupOrThrow(Registries.NOISE), seed); + } + +- this.chunkGeneratorState = generator.createState(registryAccess.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, seed); ++ this.chunkGeneratorState = generator.createState(registryAccess.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, seed, level.spigotConfig); // Spigot + this.mainThreadExecutor = mainThreadExecutor; + ConsecutiveExecutor consecutiveExecutor = new ConsecutiveExecutor(dispatcher, "worldgen"); + this.progressListener = progressListener; +@@ -207,6 +_,12 @@ + this.chunksToEagerlySave.add(chunkPos.toLong()); + } + ++ // Paper start ++ public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) { ++ return -1; ++ } ++ // Paper end ++ + protected ChunkGenerator generator() { + return this.worldGenContext.generator(); + } +@@ -354,9 +_,9 @@ + } + ); + stringBuilder.append("Updating:").append(System.lineSeparator()); +- this.updatingChunkMap.values().forEach(consumer); ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.getUpdatingChunkHolders(this.level).forEach(consumer); // Paper + stringBuilder.append("Visible:").append(System.lineSeparator()); +- this.visibleChunkMap.values().forEach(consumer); ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).forEach(consumer); // Paper + CrashReport crashReport = CrashReport.forThrowable(exception, "Chunk loading"); + CrashReportCategory crashReportCategory = crashReport.addCategory("Chunk loading"); + crashReportCategory.setDetail("Details", details); +@@ -392,6 +_,9 @@ + holder.setTicketLevel(newLevel); + } else { + holder = new ChunkHolder(new ChunkPos(chunkPos), newLevel, this.level, this.lightEngine, this::onLevelChange, this); ++ // Paper start ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderCreate(this.level, holder); ++ // Paper end + } + + this.updatingChunkMap.put(chunkPos, holder); +@@ -420,8 +_,8 @@ + + protected void saveAllChunks(boolean flush) { + if (flush) { +- List list = this.visibleChunkMap +- .values() ++ List list = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level) // Paper - moonrise ++ //.values() // Paper - moonrise + .stream() + .filter(ChunkHolder::wasAccessibleSinceLastSave) + .peek(ChunkHolder::refreshAccessibility) +@@ -447,7 +_,7 @@ + this.nextChunkSaveTime.clear(); + long millis = Util.getMillis(); + +- for (ChunkHolder chunkHolder : this.visibleChunkMap.values()) { ++ for (ChunkHolder chunkHolder : ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)) { // Paper + this.saveChunkIfNeeded(chunkHolder, millis); + } + } +@@ -468,6 +_,7 @@ + public boolean hasWork() { + return this.lightEngine.hasLightWork() + || !this.pendingUnloads.isEmpty() ++ || ca.spottedleaf.moonrise.common.util.ChunkSystem.hasAnyChunkHolders(this.level) // Paper - moonrise + || !this.updatingChunkMap.isEmpty() + || this.poiManager.hasWork() + || !this.toDrop.isEmpty() +@@ -526,7 +_,11 @@ + this.scheduleUnload(chunkPos, chunkHolder); + } else { + ChunkAccess latestChunk = chunkHolder.getLatestChunk(); +- if (this.pendingUnloads.remove(chunkPos, chunkHolder) && latestChunk != null) { ++ // Paper start ++ boolean removed; ++ if ((removed = this.pendingUnloads.remove(chunkPos, chunkHolder)) && latestChunk != null) { ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunkHolder); ++ // Paper end + if (latestChunk instanceof LevelChunk levelChunk) { + levelChunk.setLoaded(false); + } +@@ -540,7 +_,9 @@ + this.lightEngine.tryScheduleUpdate(); + this.progressListener.onStatusChange(latestChunk.getPos(), null); + this.nextChunkSaveTime.remove(latestChunk.getPos().toLong()); +- } ++ } else if (removed) { // Paper start ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunkHolder); ++ } // Paper end + } + }, this.unloadQueue::add).whenComplete((_void, error) -> { + if (error != null) { +@@ -818,7 +_,7 @@ + } + } + +- protected void setServerViewDistance(int viewDistance) { ++ public void setServerViewDistance(int viewDistance) { // Paper - publi + int i = Mth.clamp(viewDistance, 2, 32); + if (i != this.serverViewDistance) { + this.serverViewDistance = i; +@@ -856,7 +_,7 @@ + } + + public int size() { +- return this.visibleChunkMap.size(); ++ return ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolderCount(this.level); // Paper + } + + public net.minecraft.server.level.DistanceManager getDistanceManager() { +@@ -864,7 +_,7 @@ + } + + protected Iterable getChunks() { +- return Iterables.unmodifiableIterable(this.visibleChunkMap.values()); ++ return Iterables.unmodifiableIterable(ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)); // Paper + } + + void dumpChunks(Writer writer) throws IOException { +@@ -888,10 +_,10 @@ + .build(writer); + TickingTracker tickingTracker = this.distanceManager.tickingTracker(); + +- for (Entry entry : this.visibleChunkMap.long2ObjectEntrySet()) { +- long longKey = entry.getLongKey(); ++ for (ChunkHolder entry : ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)) { // Paper - Moonrise ++ long longKey = entry.pos.toLong(); // Paper - Moonrise + ChunkPos chunkPos = new ChunkPos(longKey); +- ChunkHolder chunkHolder = entry.getValue(); ++ ChunkHolder chunkHolder = entry; // Paper - Moonrise + Optional optional = Optional.ofNullable(chunkHolder.getLatestChunk()); + Optional optional1 = optional.flatMap(chunk -> chunk instanceof LevelChunk ? Optional.of((LevelChunk)chunk) : Optional.empty()); + csvOutput.writeRow( +@@ -931,11 +_,13 @@ + } + + private CompletableFuture> readChunk(ChunkPos pos) { +- return this.read(pos).thenApplyAsync(optional -> optional.map(this::upgradeChunkTag), Util.backgroundExecutor().forName("upgradeChunk")); ++ return this.read(pos).thenApplyAsync(optional -> optional.map(tag -> upgradeChunkTag(tag, pos)), Util.backgroundExecutor().forName("upgradeChunk")); // CraftBukkit + } + +- private CompoundTag upgradeChunkTag(CompoundTag tag) { +- return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, tag, this.generator().getTypeNameForDataFixer()); ++ // CraftBukkit start ++ private CompoundTag upgradeChunkTag(CompoundTag tag, ChunkPos pos) { ++ return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, tag, this.generator().getTypeNameForDataFixer(), pos, this.level); ++ // CraftBukkit end + } + + void forEachSpawnCandidateChunk(Consumer action) { +@@ -951,12 +_,34 @@ + } + + public boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkPos) { +- return this.distanceManager.hasPlayersNearby(chunkPos.toLong()) && this.anyPlayerCloseEnoughForSpawningInternal(chunkPos); ++ // Spigot start ++ return this.anyPlayerCloseEnoughForSpawning(chunkPos, false); ++ } ++ ++ boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkPos, boolean reducedRange) { ++ return this.distanceManager.hasPlayersNearby(chunkPos.toLong()) && this.anyPlayerCloseEnoughForSpawningInternal(chunkPos, reducedRange); ++ // Spigot end + } + + private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkPos) { ++ // Spigot start ++ return this.anyPlayerCloseEnoughForSpawningInternal(chunkPos, false); ++ } ++ ++ private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkPos, boolean reducedRange) { ++ double blockRange; // Paper - use from event ++ // Spigot end + for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) { +- if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos)) { ++ // Paper start - PlayerNaturallySpawnCreaturesEvent ++ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; ++ blockRange = 16384.0D; ++ if (reducedRange) { ++ event = serverPlayer.playerNaturallySpawnedEvent; ++ if (event == null || event.isCancelled()) continue; ++ blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); ++ } ++ // Paper end - PlayerNaturallySpawnCreaturesEvent ++ if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos, blockRange)) { + return true; + } + } +@@ -972,7 +_,7 @@ + Builder builder = ImmutableList.builder(); + + for (ServerPlayer serverPlayer : this.playerMap.getAllPlayers()) { +- if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos)) { ++ if (this.playerIsCloseEnoughForSpawning(serverPlayer, chunkPos, 16384.0D)) { // Spigot + builder.add(serverPlayer); + } + } +@@ -981,12 +_,12 @@ + } + } + +- private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos chunkPos) { ++ private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos chunkPos, double range) { // Spigot + if (player.isSpectator()) { + return false; + } else { + double d = euclideanDistanceSquared(chunkPos, player); +- return d < 16384.0; ++ return d < range; // Spigot + } + } + +@@ -1100,9 +_,19 @@ + } + + public void addEntity(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 || this.entityMap.containsKey(entity.getId())) { ++ LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName() ++ + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable()); ++ return; ++ } ++ // Paper end - ignore and warn about illegal addEntity calls instead of crashing server ++ if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delayadding to tracker until after list packets + if (!(entity instanceof EnderDragonPart)) { + EntityType type = entity.getType(); + int i = type.clientTrackingRange() * 16; ++ i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // Spigot + if (i != 0) { + int updateInterval = type.updateInterval(); + if (this.entityMap.containsKey(entity.getId())) { +@@ -1126,6 +_,7 @@ + } + + protected void removeEntity(Entity entity) { ++ org.spigotmc.AsyncCatcher.catchOp("entity untrack"); // Spigot + if (entity instanceof ServerPlayer serverPlayer) { + this.updatePlayerStatus(serverPlayer, false); + +@@ -1230,7 +_,7 @@ + }); + } + +- class DistanceManager extends net.minecraft.server.level.DistanceManager { ++ public class DistanceManager extends net.minecraft.server.level.DistanceManager { // Paper - public + protected DistanceManager(final Executor dispatcher, final Executor mainThreadExecutor) { + super(dispatcher, mainThreadExecutor); + } +@@ -1258,10 +_,10 @@ + final Entity entity; + private final int range; + SectionPos lastSectionPos; +- public final Set seenBy = Sets.newIdentityHashSet(); ++ public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl + + public TrackedEntity(final Entity entity, final int range, final int updateInterval, final boolean trackDelta) { +- this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, updateInterval, trackDelta, this::broadcast); ++ this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, updateInterval, trackDelta, this::broadcast, this.seenBy); // CraftBukkit + this.entity = entity; + this.range = range; + this.lastSectionPos = SectionPos.of(entity); +@@ -1297,24 +_,47 @@ + } + + public void removePlayer(ServerPlayer player) { ++ org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot + if (this.seenBy.remove(player.connection)) { + this.serverEntity.removePairing(player); + } + } + + public void updatePlayer(ServerPlayer player) { ++ org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot + if (player != this.entity) { +- Vec3 vec3 = player.position().subtract(this.entity.position()); ++ // Paper start - remove allocation of Vec3D here ++ // Vec3 vec3d = player.position().subtract(this.entity.position()); ++ double vec3d_dx = player.getX() - this.entity.getX(); ++ double vec3d_dz = player.getZ() - this.entity.getZ(); ++ // Paper end - remove allocation of Vec3D here + int playerViewDistance = ChunkMap.this.getPlayerViewDistance(player); + double d = Math.min(this.getEffectiveRange(), playerViewDistance * 16); +- double d1 = vec3.x * vec3.x + vec3.z * vec3.z; ++ double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper + double d2 = d * d; +- boolean flag = d1 <= d2 +- && this.entity.broadcastToPlayer(player) +- && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); ++ // Paper start - Configurable entity tracking range by Y ++ boolean flag = d1 <= d2; ++ if (flag && level.paperConfig().entities.trackingRangeY.enabled) { ++ double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1); ++ if (rangeY != -1) { ++ double vec3d_dy = player.getY() - this.entity.getY(); ++ flag = vec3d_dy * vec3d_dy <= rangeY * rangeY; ++ } ++ } ++ flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); ++ // Paper end - Configurable entity tracking range by Y ++ // CraftBukkit start - respect vanish API ++ if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits ++ flag = false; ++ } ++ // CraftBukkit end + if (flag) { + if (this.seenBy.add(player.connection)) { ++ // Paper start - entity tracking events ++ if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) { + this.serverEntity.addPairing(player); ++ } ++ // Paper end - entity tracking events + } + } else if (this.seenBy.remove(player.connection)) { + this.serverEntity.removePairing(player); +@@ -1331,6 +_,7 @@ + + for (Entity entity : this.entity.getIndirectPassengers()) { + int i1 = entity.getType().clientTrackingRange() * 16; ++ i1 = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i1); // Paper + if (i1 > i) { + i = i1; + } diff --git a/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch b/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch new file mode 100644 index 0000000000..0626bbca7a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/level/DistanceManager.java.patch @@ -0,0 +1,128 @@ +--- a/net/minecraft/server/level/DistanceManager.java ++++ b/net/minecraft/server/level/DistanceManager.java +@@ -107,6 +_,12 @@ + } + + if (!this.chunksToUpdateFutures.isEmpty()) { ++ // CraftBukkit start - SPIGOT-7780: Call chunk unload events before updateHighestAllowedStatus ++ for (final ChunkHolder chunkHolder : this.chunksToUpdateFutures) { ++ chunkHolder.callEventIfUnloading(chunkMap); ++ } ++ // CraftBukkit end - SPIGOT-7780: Call chunk unload events before updateHighestAllowedStatus ++ + for (ChunkHolder chunkHolder : this.chunksToUpdateFutures) { + chunkHolder.updateHighestAllowedStatus(chunkMap); + } +@@ -143,7 +_,7 @@ + } + } + +- void addTicket(long chunkPos, Ticket ticket) { ++ boolean addTicket(long chunkPos, Ticket ticket) { // CraftBukkit - void -> boolean + SortedArraySet> tickets = this.getTickets(chunkPos); + int ticketLevelAt = getTicketLevelAt(tickets); + Ticket ticket1 = tickets.addOrGet(ticket); +@@ -151,11 +_,14 @@ + if (ticket.getTicketLevel() < ticketLevelAt) { + this.ticketTracker.update(chunkPos, ticket.getTicketLevel(), true); + } ++ return ticket == ticket1; // CraftBukkit + } + +- void removeTicket(long chunkPos, Ticket ticket) { ++ boolean removeTicket(long chunkPos, Ticket ticket) { // CraftBukkit - void -> boolean + SortedArraySet> tickets = this.getTickets(chunkPos); ++ boolean removed = false; // CraftBukkit + if (tickets.remove(ticket)) { ++ removed = true; // CraftBukkit + } + + if (tickets.isEmpty()) { +@@ -163,6 +_,7 @@ + } + + this.ticketTracker.update(chunkPos, getTicketLevelAt(tickets), false); ++ return removed; // CraftBukkit + } + + public void addTicket(TicketType type, ChunkPos pos, int level, T value) { +@@ -175,17 +_,29 @@ + } + + public void addRegionTicket(TicketType type, ChunkPos pos, int distance, T value) { ++ // CraftBukkit start ++ this.addRegionTicketAtDistance(type, pos, distance, value); ++ } ++ public boolean addRegionTicketAtDistance(TicketType type, ChunkPos pos, int distance, T value) { ++ // CraftBukkit end + Ticket ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value); + long packedChunkPos = pos.toLong(); +- this.addTicket(packedChunkPos, ticket); ++ final boolean addded = this.addTicket(packedChunkPos, ticket); // CraftBukkit + this.tickingTicketsTracker.addTicket(packedChunkPos, ticket); ++ return addded; // CraftBukkit + } + + public void removeRegionTicket(TicketType type, ChunkPos pos, int distance, T value) { ++ // CraftBukkit start ++ removeRegionTicketAtDistance(type, pos, distance, value); ++ } ++ public boolean removeRegionTicketAtDistance(TicketType type, ChunkPos pos, int distance, T value) { ++ // CraftBukkit end + Ticket ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value); + long packedChunkPos = pos.toLong(); +- this.removeTicket(packedChunkPos, ticket); ++ final boolean removed = this.removeTicket(packedChunkPos, ticket); // CraftBukkit + this.tickingTicketsTracker.removeTicket(packedChunkPos, ticket); ++ return removed; // CraftBukkit + } + + private SortedArraySet> getTickets(long chunkPos) { +@@ -217,8 +_,9 @@ + ChunkPos chunkPos = sectionPos.chunk(); + long packedChunkPos = chunkPos.toLong(); + ObjectSet set = this.playersPerChunk.get(packedChunkPos); +- set.remove(player); +- if (set.isEmpty()) { ++ if (set == null) return; // CraftBukkit - SPIGOT-6208 ++ if (set != null) set.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully ++ if (set == null || set.isEmpty()) { // Paper + this.playersPerChunk.remove(packedChunkPos); + this.naturalSpawnChunkCounter.update(packedChunkPos, Integer.MAX_VALUE, false); + this.playerTicketManager.update(packedChunkPos, Integer.MAX_VALUE, false); +@@ -299,7 +_,7 @@ + } + + public void removeTicketsOnClosing() { +- ImmutableSet> set = ImmutableSet.of(TicketType.UNKNOWN); ++ ImmutableSet> set = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve + ObjectIterator>>> objectIterator = this.tickets.long2ObjectEntrySet().fastIterator(); + + while (objectIterator.hasNext()) { +@@ -329,6 +_,26 @@ + public boolean hasTickets() { + return !this.tickets.isEmpty(); + } ++ ++ // CraftBukkit start ++ public void removeAllTicketsFor(TicketType ticketType, int ticketLevel, T ticketIdentifier) { ++ Ticket target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier); ++ ++ for (java.util.Iterator>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) { ++ Entry>> entry = iterator.next(); ++ SortedArraySet> tickets = entry.getValue(); ++ if (tickets.remove(target)) { ++ // copied from removeTicket ++ this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false); ++ ++ // can't use entry after it's removed ++ if (tickets.isEmpty()) { ++ iterator.remove(); ++ } ++ } ++ } ++ } ++ // CraftBukkit end + + class ChunkTicketTracker extends ChunkTracker { + private static final int MAX_LEVEL = ChunkLevel.MAX_LEVEL + 1; diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/ServerChunkCache.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch similarity index 55% rename from paper-server/patches/unapplied/net/minecraft/server/level/ServerChunkCache.java.patch rename to paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch index f7949f17c6..5f558ccc4a 100644 --- a/paper-server/patches/unapplied/net/minecraft/server/level/ServerChunkCache.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerChunkCache.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java -@@ -74,6 +74,13 @@ +@@ -73,6 +_,13 @@ @Nullable @VisibleForDebug private NaturalSpawner.SpawnState lastSpawnState; @@ -12,9 +12,9 @@ + long chunkFutureAwaitCounter; + // Paper end - public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory) { - this.level = world; -@@ -95,6 +102,64 @@ + public ServerChunkCache( + ServerLevel level, +@@ -121,6 +_,64 @@ this.clearCache(); } @@ -79,53 +79,50 @@ @Override public ThreadedLevelLightEngine getLightEngine() { return this.lightEngine; -@@ -138,7 +203,7 @@ - if (k == this.lastChunkPos[l] && leastStatus == this.lastChunkStatus[l]) { - ChunkAccess ichunkaccess = this.lastChunk[l]; - -- if (ichunkaccess != null || !create) { -+ if (ichunkaccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime - return ichunkaccess; +@@ -160,7 +_,7 @@ + for (int i = 0; i < 4; i++) { + if (packedChunkPos == this.lastChunkPos[i] && chunkStatus == this.lastChunkStatus[i]) { + ChunkAccess chunkAccess = this.lastChunk[i]; +- if (chunkAccess != null || !requireChunk) { ++ if (chunkAccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime + return chunkAccess; } } -@@ -150,8 +215,9 @@ - - Objects.requireNonNull(completablefuture); - chunkproviderserver_b.managedBlock(completablefuture::isDone); +@@ -169,6 +_,7 @@ + profilerFiller.incrementCounter("getChunkCacheMiss"); + CompletableFuture> chunkFutureMainThread = this.getChunkFutureMainThread(x, z, chunkStatus, requireChunk); + this.mainThreadProcessor.managedBlock(chunkFutureMainThread::isDone); + // com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x, z); // Paper - Add debug for sync chunk loads - ChunkResult chunkresult = (ChunkResult) completablefuture.join(); -- ChunkAccess ichunkaccess1 = (ChunkAccess) chunkresult.orElse((Object) null); -+ ChunkAccess ichunkaccess1 = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error - - if (ichunkaccess1 == null && create) { - throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("Chunk not there when requested: " + chunkresult.getError())); -@@ -231,7 +297,15 @@ - int l = ChunkLevel.byStatus(leastStatus); - ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k); - -- if (create) { + ChunkResult chunkResult = chunkFutureMainThread.join(); + ChunkAccess chunkAccess1 = chunkResult.orElse(null); + if (chunkAccess1 == null && requireChunk) { +@@ -240,7 +_,15 @@ + long packedChunkPos = chunkPos.toLong(); + int i = ChunkLevel.byStatus(chunkStatus); + ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(packedChunkPos); +- if (requireChunk) { + // CraftBukkit start - don't add new ticket for currently unloading chunk + boolean currentlyUnloading = false; -+ if (playerchunk != null) { -+ FullChunkStatus oldChunkState = ChunkLevel.fullStatus(playerchunk.oldTicketLevel); -+ FullChunkStatus currentChunkState = ChunkLevel.fullStatus(playerchunk.getTicketLevel()); ++ if (visibleChunkIfPresent != null) { ++ FullChunkStatus oldChunkState = ChunkLevel.fullStatus(visibleChunkIfPresent.oldTicketLevel); ++ FullChunkStatus currentChunkState = ChunkLevel.fullStatus(visibleChunkIfPresent.getTicketLevel()); + currentlyUnloading = (oldChunkState.isOrAfter(FullChunkStatus.FULL) && !currentChunkState.isOrAfter(FullChunkStatus.FULL)); + } -+ if (create && !currentlyUnloading) { -+ // CraftBukkit end - this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); - if (this.chunkAbsent(playerchunk, l)) { - ProfilerFiller gameprofilerfiller = Profiler.get(); -@@ -250,7 +324,7 @@ ++ if (requireChunk && !currentlyUnloading) { ++ // CraftBukkit end + this.distanceManager.addTicket(TicketType.UNKNOWN, chunkPos, i, chunkPos); + if (this.chunkAbsent(visibleChunkIfPresent, i)) { + ProfilerFiller profilerFiller = Profiler.get(); +@@ -260,7 +_,7 @@ } - private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) { -- return holder == null || holder.getTicketLevel() > maxLevel; -+ return holder == null || holder.oldTicketLevel > maxLevel; // CraftBukkit using oldTicketLevel for isLoaded checks + private boolean chunkAbsent(@Nullable ChunkHolder chunkHolder, int status) { +- return chunkHolder == null || chunkHolder.getTicketLevel() > status; ++ return chunkHolder == null || chunkHolder.oldTicketLevel > status; // CraftBukkit using oldTicketLevel for isLoaded checks } @Override -@@ -279,7 +353,7 @@ +@@ -287,7 +_,7 @@ return this.mainThreadProcessor.pollTask(); } @@ -133,13 +130,12 @@ + public boolean runDistanceManagerUpdates() { // Paper - public boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); boolean flag1 = this.chunkMap.promoteChunkMap(); - -@@ -309,18 +383,40 @@ + this.chunkMap.runGenerationTasks(); +@@ -315,17 +_,38 @@ @Override public void close() throws IOException { - this.save(true); -+ // CraftBukkit start + this.close(true); + } + @@ -168,26 +164,24 @@ + // CraftBukkit end + @Override - public void tick(BooleanSupplier shouldKeepTicking, boolean tickChunks) { - ProfilerFiller gameprofilerfiller = Profiler.get(); - - gameprofilerfiller.push("purge"); + public void tick(BooleanSupplier hasTimeLeft, boolean tickChunks) { + ProfilerFiller profilerFiller = Profiler.get(); + profilerFiller.push("purge"); - if (this.level.tickRateManager().runsNormally() || !tickChunks) { + if (this.level.tickRateManager().runsNormally() || !tickChunks || this.level.spigotConfig.unloadFrozenChunks) { // Spigot this.distanceManager.purgeStaleTickets(); } -@@ -401,14 +497,22 @@ - - this.lastSpawnState = spawnercreature_d; +@@ -400,11 +_,19 @@ + ); + this.lastSpawnState = spawnState; profiler.popPush("spawnAndTick"); -- boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING); -+ boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit - int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); - List list1; - - if (flag && (this.spawnEnemies || this.spawnFriendlies)) { -- boolean flag1 = this.level.getLevelData().getGameTime() % 400L == 0L; +- boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING); ++ boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit; + int _int = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); + List filteredSpawningCategories; + if (_boolean && (this.spawnEnemies || this.spawnFriendlies)) { +- boolean flag = this.level.getLevelData().getGameTime() % 400L == 0L; + // Paper start - PlayerNaturallySpawnCreaturesEvent + for (ServerPlayer entityPlayer : this.level.players()) { + int chunkRange = Math.min(level.spigotConfig.mobSpawnRange, entityPlayer.getBukkitEntity().getViewDistance()); @@ -196,60 +190,51 @@ + entityPlayer.playerNaturallySpawnedEvent.callEvent(); + } + // Paper end - PlayerNaturallySpawnCreaturesEvent -+ boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit - -- list1 = NaturalSpawner.getFilteredSpawningCategories(spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1); -+ list1 = NaturalSpawner.getFilteredSpawningCategories(spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1, this.level); // CraftBukkit ++ boolean flag = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit + filteredSpawningCategories = NaturalSpawner.getFilteredSpawningCategories(spawnState, this.spawnFriendlies, this.spawnEnemies, flag); } else { - list1 = List.of(); - } -@@ -420,7 +524,7 @@ - ChunkPos chunkcoordintpair = chunk.getPos(); - - chunk.incrementInhabitedTime(timeDelta); -- if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair)) { -+ if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot - NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, list1); + filteredSpawningCategories = List.of(); +@@ -413,7 +_,7 @@ + for (LevelChunk levelChunk : chunks) { + ChunkPos pos = levelChunk.getPos(); + levelChunk.incrementInhabitedTime(timeInhabited); +- if (!filteredSpawningCategories.isEmpty() && this.level.getWorldBorder().isWithinBounds(pos)) { ++ if (!filteredSpawningCategories.isEmpty() && this.level.getWorldBorder().isWithinBounds(pos) && this.chunkMap.anyPlayerCloseEnoughForSpawning(pos, true)) { // Spigot + NaturalSpawner.spawnForChunk(this.level, levelChunk, spawnState, filteredSpawningCategories); } -@@ -541,10 +645,16 @@ +@@ -526,8 +_,13 @@ @Override - public void setSpawnSettings(boolean spawnMonsters) { -- this.spawnEnemies = spawnMonsters; + public void setSpawnSettings(boolean spawnSettings) { +- this.spawnEnemies = spawnSettings; - this.spawnFriendlies = this.spawnFriendlies; + // CraftBukkit start -+ this.setSpawnSettings(spawnMonsters, this.spawnFriendlies); - } - -+ public void setSpawnSettings(boolean flag, boolean spawnFriendlies) { -+ this.spawnEnemies = flag; ++ this.setSpawnSettings(spawnSettings, this.spawnFriendlies); ++ } ++ public void setSpawnSettings(boolean spawnEnemies, boolean spawnFriendlies) { ++ this.spawnEnemies = spawnEnemies; + this.spawnFriendlies = spawnFriendlies; + // CraftBukkit end -+ } -+ - public String getChunkDebugData(ChunkPos pos) { - return this.chunkMap.getChunkDebugData(pos); } -@@ -618,14 +728,20 @@ - } + + public String getChunkDebugData(ChunkPos chunkPos) { +@@ -603,12 +_,18 @@ @Override -- protected boolean pollTask() { -+ // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task -+ public boolean pollTask() { -+ try { + public boolean pollTask() { ++ try { // CraftBukkit - process pending Chunk loadCallback() and unloadCallback() after each run task if (ServerChunkCache.this.runDistanceManagerUpdates()) { return true; } else { ServerChunkCache.this.lightEngine.tryScheduleUpdate(); return super.pollTask(); } -+ } finally { -+ ServerChunkCache.this.chunkMap.callbackExecutor.run(); ++ // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task ++ } finally { ++ ServerChunkCache.this.chunkMap.callbackExecutor.run(); ++ } ++ // CraftBukkit end - process pending Chunk loadCallback() and unloadCallback() after each run task } -+ // CraftBukkit end -+ } } - - private static record ChunkAndHolder(LevelChunk chunk, ChunkHolder holder) { + } diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch new file mode 100644 index 0000000000..5f30bfd43d --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerEntity.java.patch @@ -0,0 +1,134 @@ +--- a/net/minecraft/server/level/ServerEntity.java ++++ b/net/minecraft/server/level/ServerEntity.java +@@ -65,13 +_,17 @@ + private Vec3 lastSentMovement; + private int tickCount; + private int teleportDelay; +- private List lastPassengers = Collections.emptyList(); ++ private List lastPassengers = com.google.common.collect.ImmutableList.of(); // Paper - optimize passenger checks + private boolean wasRiding; + private boolean wasOnGround; + @Nullable + private List> trackedDataValues; + +- public ServerEntity(ServerLevel level, Entity entity, int updateInterval, boolean trackDelta, Consumer> broadcast) { ++ // CraftBukkit start ++ private final Set trackedPlayers; ++ public ServerEntity(ServerLevel level, Entity entity, int updateInterval, boolean trackDelta, Consumer> broadcast, final Set trackedPlayers) { ++ this.trackedPlayers = trackedPlayers; ++ // CraftBukkit end + this.level = level; + this.broadcast = broadcast; + this.entity = entity; +@@ -89,7 +_,7 @@ + public void sendChanges() { + List passengers = this.entity.getPassengers(); + if (!passengers.equals(this.lastPassengers)) { +- this.broadcast.accept(new ClientboundSetPassengersPacket(this.entity)); ++ this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit + removedPassengers(passengers, this.lastPassengers) + .forEach( + removedPassenger -> { +@@ -102,10 +_,10 @@ + this.lastPassengers = passengers; + } + +- if (this.entity instanceof ItemFrame itemFrame && this.tickCount % 10 == 0) { ++ if (!this.trackedPlayers.isEmpty() && this.entity instanceof ItemFrame itemFrame /*&& this.tickCount % 10 == 0*/) { // CraftBukkit - moved tickCount below // Paper - Perf: Only tick item frames if players can see it + ItemStack item = itemFrame.getItem(); +- if (item.getItem() instanceof MapItem) { +- MapId mapId = item.get(DataComponents.MAP_ID); ++ if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && item.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable ++ MapId mapId = itemFrame.cachedMapId; // Paper - Perf: Cache map ids on item frames + MapItemSavedData savedData = MapItem.getSavedData(mapId, this.level); + if (savedData != null) { + for (ServerPlayer serverPlayer : this.level.players()) { +@@ -141,7 +_,13 @@ + } else { + this.teleportDelay++; + Vec3 vec3 = this.entity.trackingPosition(); +- boolean flag1 = this.positionCodec.delta(vec3).lengthSqr() >= 7.6293945E-6F; ++ // Paper start - reduce allocation of Vec3D here ++ Vec3 base = this.positionCodec.base; ++ double vec3_dx = vec3.x - base.x; ++ double vec3_dy = vec3.y - base.y; ++ double vec3_dz = vec3.z - base.z; ++ boolean flag1 = (vec3_dx * vec3_dx + vec3_dy * vec3_dy + vec3_dz * vec3_dz) >= 7.62939453125E-6D; ++ // Paper end - reduce allocation of Vec3D here + Packet packet = null; + boolean flag2 = flag1 || this.tickCount % 60 == 0; + boolean flag3 = false; +@@ -219,6 +_,25 @@ + + this.tickCount++; + if (this.entity.hurtMarked) { ++ // CraftBukkit start - Create PlayerVelocity event ++ boolean cancelled = false; ++ ++ if (this.entity instanceof ServerPlayer) { ++ org.bukkit.entity.Player player = (org.bukkit.entity.Player) this.entity.getBukkitEntity(); ++ org.bukkit.util.Vector velocity = player.getVelocity(); ++ ++ org.bukkit.event.player.PlayerVelocityEvent event = new org.bukkit.event.player.PlayerVelocityEvent(player, velocity.clone()); ++ if (!event.callEvent()) { ++ cancelled = true; ++ } else if (!velocity.equals(event.getVelocity())) { ++ player.setVelocity(event.getVelocity()); ++ } ++ } ++ ++ if (cancelled) { ++ return; ++ } ++ // CraftBukkit end + this.entity.hurtMarked = false; + this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity)); + } +@@ -273,7 +_,10 @@ + + public void sendPairingData(ServerPlayer player, Consumer> consumer) { + if (this.entity.isRemoved()) { +- LOGGER.warn("Fetching packet for removed entity {}", this.entity); ++ // CraftBukkit start - Remove useless error spam, just return ++ // LOGGER.warn("Fetching packet for removed entity {}", this.entity); ++ return; ++ // CraftBukkit end + } + + Packet addEntityPacket = this.entity.getAddEntityPacket(this); +@@ -285,6 +_,12 @@ + boolean flag = this.trackDelta; + if (this.entity instanceof LivingEntity) { + Collection syncableAttributes = ((LivingEntity)this.entity).getAttributes().getSyncableAttributes(); ++ ++ // CraftBukkit start - If sending own attributes send scaled health instead of current maximum health ++ if (this.entity.getId() == player.getId()) { ++ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(syncableAttributes, false); ++ } ++ // CraftBukkit end + if (!syncableAttributes.isEmpty()) { + consumer.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), syncableAttributes)); + } +@@ -309,8 +_,9 @@ + } + + if (!list.isEmpty()) { +- consumer.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list)); ++ consumer.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list, true)); // Paper - data sanitization + } ++ ((LivingEntity) this.entity).detectEquipmentUpdatesPublic(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending + } + + if (!this.entity.getPassengers().isEmpty()) { +@@ -357,6 +_,11 @@ + if (this.entity instanceof LivingEntity) { + Set attributesToSync = ((LivingEntity)this.entity).getAttributes().getAttributesToSync(); + if (!attributesToSync.isEmpty()) { ++ // CraftBukkit start - Send scaled max health ++ if (this.entity instanceof ServerPlayer) { ++ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(attributesToSync, false); ++ } ++ // CraftBukkit end + this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), attributesToSync)); + } + diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch new file mode 100644 index 0000000000..20b894a4b0 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerLevel.java.patch @@ -0,0 +1,1217 @@ +--- a/net/minecraft/server/level/ServerLevel.java ++++ b/net/minecraft/server/level/ServerLevel.java +@@ -54,7 +_,6 @@ + import net.minecraft.network.protocol.game.ClientboundDamageEventPacket; + import net.minecraft.network.protocol.game.ClientboundEntityEventPacket; + import net.minecraft.network.protocol.game.ClientboundExplodePacket; +-import net.minecraft.network.protocol.game.ClientboundGameEventPacket; + import net.minecraft.network.protocol.game.ClientboundLevelEventPacket; + import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket; + import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket; +@@ -120,6 +_,7 @@ + import net.minecraft.world.level.StructureManager; + import net.minecraft.world.level.WorldGenLevel; + import net.minecraft.world.level.biome.Biome; ++import net.minecraft.world.level.biome.BiomeSource; + import net.minecraft.world.level.block.Block; + import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.SnowLayerBlock; +@@ -145,7 +_,9 @@ + import net.minecraft.world.level.gameevent.DynamicGameEventListener; + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.gameevent.GameEventDispatcher; ++import net.minecraft.world.level.levelgen.FlatLevelSource; + import net.minecraft.world.level.levelgen.Heightmap; ++import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; + import net.minecraft.world.level.levelgen.structure.BoundingBox; + import net.minecraft.world.level.levelgen.structure.Structure; + import net.minecraft.world.level.levelgen.structure.StructureCheck; +@@ -161,7 +_,7 @@ + import net.minecraft.world.level.saveddata.maps.MapItemSavedData; + import net.minecraft.world.level.storage.DimensionDataStorage; + import net.minecraft.world.level.storage.LevelStorageSource; +-import net.minecraft.world.level.storage.ServerLevelData; ++import net.minecraft.world.level.storage.PrimaryLevelData; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; + import net.minecraft.world.phys.shapes.BooleanOp; +@@ -182,7 +_,7 @@ + final List players = Lists.newArrayList(); + public final ServerChunkCache chunkSource; + private final MinecraftServer server; +- public final ServerLevelData serverLevelData; ++ public final PrimaryLevelData serverLevelData; // CraftBukkit - type + private int lastSpawnChunkRadius; + final EntityTickList entityTickList = new EntityTickList(); + public final PersistentEntitySectionManager entityManager; +@@ -209,11 +_,132 @@ + private final boolean tickTime; + private final RandomSequences randomSequences; + ++ // CraftBukkit start ++ public final LevelStorageSource.LevelStorageAccess levelStorageAccess; ++ public final UUID uuid; ++ public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent ++ public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent ++ ++ public LevelChunk getChunkIfLoaded(int x, int z) { ++ return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately ++ } ++ ++ @Override ++ public ResourceKey getTypeKey() { ++ return this.levelStorageAccess.dimensionType; ++ } ++ ++ // Paper start ++ public final boolean areChunksLoadedForMove(AABB axisalignedbb) { ++ // copied code from collision methods, so that we can guarantee that they wont load chunks (we don't override ++ // ICollisionAccess methods for VoxelShapes) ++ // be more strict too, add a block (dumb plugins in move events?) ++ int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; ++ int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; ++ ++ int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; ++ int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; ++ ++ int minChunkX = minBlockX >> 4; ++ int maxChunkX = maxBlockX >> 4; ++ ++ int minChunkZ = minBlockZ >> 4; ++ int maxChunkZ = maxBlockZ >> 4; ++ ++ ServerChunkCache chunkProvider = this.getChunkSource(); ++ ++ for (int cx = minChunkX; cx <= maxChunkX; ++cx) { ++ for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { ++ if (chunkProvider.getChunkAtIfLoadedImmediately(cx, cz) == null) { ++ return false; ++ } ++ } ++ } ++ ++ return true; ++ } ++ ++ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.util.Priority priority, ++ java.util.function.Consumer> onLoad) { ++ if (Thread.currentThread() != this.thread) { ++ this.getChunkSource().mainThreadProcessor.execute(() -> { ++ this.loadChunksForMoveAsync(axisalignedbb, priority, onLoad); ++ }); ++ return; ++ } ++ int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; ++ int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; ++ ++ int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; ++ int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; ++ ++ int minChunkX = minBlockX >> 4; ++ int minChunkZ = minBlockZ >> 4; ++ ++ int maxChunkX = maxBlockX >> 4; ++ int maxChunkZ = maxBlockZ >> 4; ++ ++ this.loadChunks(minChunkX, minChunkZ, maxChunkX, maxChunkZ, priority, onLoad); ++ } ++ ++ public final void loadChunks(int minChunkX, int minChunkZ, int maxChunkX, int maxChunkZ, ++ ca.spottedleaf.concurrentutil.util.Priority priority, ++ java.util.function.Consumer> onLoad) { ++ List ret = new java.util.ArrayList<>(); ++ it.unimi.dsi.fastutil.ints.IntArrayList ticketLevels = new it.unimi.dsi.fastutil.ints.IntArrayList(); ++ ServerChunkCache chunkProvider = this.getChunkSource(); ++ ++ int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1); ++ int[] loadedChunks = new int[1]; ++ ++ Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++); ++ ++ java.util.function.Consumer consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> { ++ if (chunk != null) { ++ int ticketLevel = Math.max(33, chunkProvider.chunkMap.getUpdatingChunkIfPresent(chunk.getPos().toLong()).getTicketLevel()); ++ ret.add(chunk); ++ ticketLevels.add(ticketLevel); ++ chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier); ++ } ++ if (++loadedChunks[0] == requiredChunks) { ++ try { ++ onLoad.accept(java.util.Collections.unmodifiableList(ret)); ++ } finally { ++ for (int i = 0, len = ret.size(); i < len; ++i) { ++ ChunkPos chunkPos = ret.get(i).getPos(); ++ int ticketLevel = ticketLevels.getInt(i); ++ ++ chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); ++ chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier); ++ } ++ } ++ } ++ }; ++ ++ for (int cx = minChunkX; cx <= maxChunkX; ++cx) { ++ for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { ++ ca.spottedleaf.moonrise.common.util.ChunkSystem.scheduleChunkLoad( ++ this, cx, cz, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true, priority, consumer ++ ); ++ } ++ } ++ } ++ // Paper end ++ ++ // Paper start - optimise getPlayerByUUID ++ @Nullable ++ @Override ++ public Player getPlayerByUUID(UUID uuid) { ++ final Player player = this.getServer().getPlayerList().getPlayer(uuid); ++ return player != null && player.level() == this ? player : null; ++ } ++ // Paper end - optimise getPlayerByUUID ++ + public ServerLevel( + MinecraftServer server, + Executor dispatcher, + LevelStorageSource.LevelStorageAccess levelStorageAccess, +- ServerLevelData serverLevelData, ++ PrimaryLevelData serverLevelData, // CraftBukkit + ResourceKey dimension, + LevelStem levelStem, + ChunkProgressListener progressListener, +@@ -221,14 +_,38 @@ + long biomeZoomSeed, + List customSpawners, + boolean tickTime, +- @Nullable RandomSequences randomSequences ++ @Nullable RandomSequences randomSequences, ++ org.bukkit.World.Environment env, // CraftBukkit ++ org.bukkit.generator.ChunkGenerator gen, // CraftBukkit ++ org.bukkit.generator.BiomeProvider biomeProvider // CraftBukkit + ) { +- super(serverLevelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates()); ++ // CraftBukkit start ++ super(serverLevelData, dimension, server.registryAccess(), levelStem.type(), false, isDebug, biomeZoomSeed, server.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> server.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(levelStorageAccess.levelDirectory.path(), serverLevelData.getLevelName(), dimension.location(), spigotConfig, server.registryAccess(), serverLevelData.getGameRules()))); // Paper - create paper world configs ++ this.pvpMode = server.isPvpAllowed(); ++ this.levelStorageAccess = levelStorageAccess; ++ this.uuid = org.bukkit.craftbukkit.util.WorldUUID.getUUID(levelStorageAccess.levelDirectory.path().toFile()); ++ // CraftBukkit end + this.tickTime = tickTime; + this.server = server; + this.customSpawners = customSpawners; + this.serverLevelData = serverLevelData; + ChunkGenerator chunkGenerator = levelStem.generator(); ++ // CraftBukkit start ++ this.serverLevelData.setWorld(this); ++ ++ if (biomeProvider != null) { ++ BiomeSource worldChunkManager = new org.bukkit.craftbukkit.generator.CustomWorldChunkManager(this.getWorld(), biomeProvider, this.server.registryAccess().lookupOrThrow(Registries.BIOME), chunkGenerator.getBiomeSource()); // Paper - add vanillaBiomeProvider ++ if (chunkGenerator instanceof NoiseBasedChunkGenerator cga) { ++ chunkGenerator = new NoiseBasedChunkGenerator(worldChunkManager, cga.settings); ++ } else if (chunkGenerator instanceof FlatLevelSource cpf) { ++ chunkGenerator = new FlatLevelSource(cpf.settings(), worldChunkManager); ++ } ++ } ++ ++ if (gen != null) { ++ chunkGenerator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, chunkGenerator, gen); ++ } ++ // CraftBukkit end + boolean flag = server.forceSynchronousWrites(); + DataFixer fixerUpper = server.getFixerUpper(); + EntityPersistentStorage entityPersistentStorage = new EntityStorage( +@@ -250,8 +_,8 @@ + server.getStructureManager(), + dispatcher, + chunkGenerator, +- server.getPlayerList().getViewDistance(), +- server.getPlayerList().getSimulationDistance(), ++ this.spigotConfig.viewDistance, // Spigot ++ this.spigotConfig.simulationDistance, // Spigot + flag, + progressListener, + this.entityManager::updateChunkStatus, +@@ -272,7 +_,7 @@ + this.chunkSource.chunkScanner(), + this.registryAccess(), + server.getStructureManager(), +- dimension, ++ getTypeKey(), // Paper - Fix missing CB diff + chunkGenerator, + this.chunkSource.randomState(), + this, +@@ -281,7 +_,7 @@ + fixerUpper + ); + this.structureManager = new StructureManager(this, server.getWorldData().worldGenOptions(), this.structureCheck); +- if (this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) { ++ if (this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END) || env == org.bukkit.World.Environment.THE_END) { // CraftBukkit - Allow to create EnderDragonBattle in default and custom END + this.dragonFight = new EndDragonFight(this, seed, server.getWorldData().endDragonFightData()); + } else { + this.dragonFight = null; +@@ -292,7 +_,15 @@ + this.randomSequences = Objects.requireNonNullElseGet( + randomSequences, () -> this.getDataStorage().computeIfAbsent(RandomSequences.factory(seed), "random_sequences") + ); +- } ++ this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit ++ } ++ ++ // Paper start ++ @Override ++ public boolean hasChunk(int chunkX, int chunkZ) { ++ return this.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) != null; ++ } ++ // Paper end + + @Deprecated + @VisibleForTesting +@@ -304,8 +_,8 @@ + this.serverLevelData.setClearWeatherTime(clearTime); + this.serverLevelData.setRainTime(weatherTime); + this.serverLevelData.setThunderTime(weatherTime); +- this.serverLevelData.setRaining(isRaining); +- this.serverLevelData.setThundering(isThundering); ++ this.serverLevelData.setRaining(isRaining, org.bukkit.event.weather.WeatherChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents ++ this.serverLevelData.setThundering(isThundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents + } + + @Override +@@ -332,12 +_,25 @@ + + int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); + if (this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) { ++ // Paper start - create time skip event - move up calculations ++ final long newDayTime = this.levelData.getDayTime() + 24000L; ++ org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent( ++ this.getWorld(), ++ org.bukkit.event.world.TimeSkipEvent.SkipReason.NIGHT_SKIP, ++ (newDayTime - newDayTime % 24000L) - this.getDayTime() ++ ); ++ // Paper end - create time skip event - move up calculations + if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { +- long l = this.levelData.getDayTime() + 24000L; +- this.setDayTime(l - l % 24000L); ++ // Paper start - call time skip event if gamerule is enabled ++ // long l = this.levelData.getDayTime() + 24000L; // Paper - diff on change to above - newDayTime ++ // this.setDayTime(l - l % 24000L); // Paper - diff on change to above - event param ++ if (event.callEvent()) { ++ this.setDayTime(this.getDayTime() + event.getSkipAmount()); ++ } ++ // Paper end - call time skip event if gamerule is enabled + } + +- this.wakeUpAllPlayers(); ++ if (!event.isCancelled()) this.wakeUpAllPlayers(); // Paper - only wake up players if time skip event is not cancelled + if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE) && this.isRaining()) { + this.resetWeatherCycle(); + } +@@ -352,9 +_,9 @@ + if (!this.isDebug() && runsNormally) { + long l = this.getGameTime(); + profilerFiller.push("blockTicks"); +- this.blockTicks.tick(l, 65536, this::tickBlock); ++ this.blockTicks.tick(l, paperConfig().environment.maxBlockTicks, this::tickBlock); // Paper - configurable max block ticks + profilerFiller.popPush("fluidTicks"); +- this.fluidTicks.tick(l, 65536, this::tickFluid); ++ this.fluidTicks.tick(l, paperConfig().environment.maxFluidTicks, this::tickFluid); // Paper - configurable max fluid ticks + profilerFiller.pop(); + } + +@@ -372,7 +_,7 @@ + + this.handlingTick = false; + profilerFiller.pop(); +- boolean flag = !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); ++ boolean flag = !paperConfig().unsupportedSettings.disableWorldTickingWhenEmpty || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players // Paper - restore this + if (flag) { + this.resetEmptyTime(); + } +@@ -385,6 +_,7 @@ + profilerFiller.pop(); + } + ++ org.spigotmc.ActivationRange.activateEntities(this); // Spigot + this.entityTickList + .forEach( + entity -> { +@@ -461,12 +_,12 @@ + int minBlockZ = pos.getMinBlockZ(); + ProfilerFiller profilerFiller = Profiler.get(); + profilerFiller.push("thunder"); +- if (isRaining && this.isThundering() && this.random.nextInt(100000) == 0) { ++ if (!this.paperConfig().environment.disableThunder && isRaining && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder + BlockPos blockPos = this.findLightningTargetAround(this.getBlockRandomPos(minBlockX, 0, minBlockZ, 15)); + if (this.isRainingAt(blockPos)) { + DifficultyInstance currentDifficultyAt = this.getCurrentDifficultyAt(blockPos); + boolean flag = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) +- && this.random.nextDouble() < currentDifficultyAt.getEffectiveDifficulty() * 0.01 ++ && this.random.nextDouble() < currentDifficultyAt.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) // Paper - Configurable spawn chances for skeleton horses + && !this.getBlockState(blockPos.below()).is(Blocks.LIGHTNING_ROD); + if (flag) { + SkeletonHorse skeletonHorse = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT); +@@ -474,7 +_,7 @@ + skeletonHorse.setTrap(true); + skeletonHorse.setAge(0); + skeletonHorse.setPos(blockPos.getX(), blockPos.getY(), blockPos.getZ()); +- this.addFreshEntity(skeletonHorse); ++ this.addFreshEntity(skeletonHorse, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit + } + } + +@@ -482,18 +_,20 @@ + if (lightningBolt != null) { + lightningBolt.moveTo(Vec3.atBottomCenterOf(blockPos)); + lightningBolt.setVisualOnly(flag); +- this.addFreshEntity(lightningBolt); ++ this.strikeLightning(lightningBolt, org.bukkit.event.weather.LightningStrikeEvent.Cause.WEATHER); // CraftBukkit + } + } + } + + profilerFiller.popPush("iceandsnow"); + ++ if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow + for (int i = 0; i < randomTickSpeed; i++) { + if (this.random.nextInt(48) == 0) { + this.tickPrecipitation(this.getBlockRandomPos(minBlockX, 0, minBlockZ, 15)); + } + } ++ } // Paper - Option to disable ice and snow + + profilerFiller.popPush("tickBlocks"); + if (randomTickSpeed > 0) { +@@ -535,7 +_,7 @@ + BlockPos blockPos1 = heightmapPos.below(); + Biome biome = this.getBiome(heightmapPos).value(); + if (biome.shouldFreeze(this, blockPos1)) { +- this.setBlockAndUpdate(blockPos1, Blocks.ICE.defaultBlockState()); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockPos1, Blocks.ICE.defaultBlockState(), null); // CraftBukkit + } + + if (this.isRaining()) { +@@ -547,10 +_,10 @@ + if (layersValue < Math.min(_int, 8)) { + BlockState blockState1 = blockState.setValue(SnowLayerBlock.LAYERS, Integer.valueOf(layersValue + 1)); + Block.pushEntitiesUp(blockState, blockState1, this, heightmapPos); +- this.setBlockAndUpdate(heightmapPos, blockState1); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, heightmapPos, blockState1, null); // CraftBukkit + } + } else { +- this.setBlockAndUpdate(heightmapPos, Blocks.SNOW.defaultBlockState()); ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, heightmapPos, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit + } + } + +@@ -575,6 +_,11 @@ + } + + protected BlockPos findLightningTargetAround(BlockPos pos) { ++ // Paper start - Add methods to find targets for lightning strikes ++ return this.findLightningTargetAround(pos, false); ++ } ++ public BlockPos findLightningTargetAround(BlockPos pos, boolean returnNullWhenNoTarget) { ++ // Paper end - Add methods to find targets for lightning strikes + BlockPos heightmapPos = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos); + Optional optional = this.findLightningRod(heightmapPos); + if (optional.isPresent()) { +@@ -582,11 +_,12 @@ + } else { + AABB aabb = AABB.encapsulatingFullBlocks(heightmapPos, heightmapPos.atY(this.getMaxY() + 1)).inflate(3.0); + List entitiesOfClass = this.getEntitiesOfClass( +- LivingEntity.class, aabb, entity -> entity != null && entity.isAlive() && this.canSeeSky(entity.blockPosition()) ++ LivingEntity.class, aabb, entity -> entity != null && entity.isAlive() && this.canSeeSky(entity.blockPosition()) && !entity.isSpectator() // Paper - Fix lightning being able to hit spectators (MC-262422) + ); + if (!entitiesOfClass.isEmpty()) { + return entitiesOfClass.get(this.random.nextInt(entitiesOfClass.size())).blockPosition(); + } else { ++ if (returnNullWhenNoTarget) return null; // Paper - Add methods to find targets for lightning strikes + if (heightmapPos.getY() == this.getMinY() - 1) { + heightmapPos = heightmapPos.above(2); + } +@@ -673,8 +_,8 @@ + this.serverLevelData.setThunderTime(thunderTime); + this.serverLevelData.setRainTime(rainTime); + this.serverLevelData.setClearWeatherTime(clearWeatherTime); +- this.serverLevelData.setThundering(isThundering); +- this.serverLevelData.setRaining(isRaining1); ++ this.serverLevelData.setThundering(isThundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents ++ this.serverLevelData.setRaining(isRaining1, org.bukkit.event.weather.WeatherChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents + } + + this.oThunderLevel = this.thunderLevel; +@@ -695,6 +_,7 @@ + this.rainLevel = Mth.clamp(this.rainLevel, 0.0F, 1.0F); + } + ++ /* CraftBukkit start + if (this.oRainLevel != this.rainLevel) { + this.server + .getPlayerList() +@@ -717,14 +_,47 @@ + this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.rainLevel)); + this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel)); + } ++ */ ++ for (int idx = 0; idx < this.players.size(); ++idx) { ++ if (((ServerPlayer) this.players.get(idx)).level() == this) { ++ ((ServerPlayer) this.players.get(idx)).tickWeather(); ++ } ++ } ++ ++ if (isRaining != this.isRaining()) { ++ // Only send weather packets to those affected ++ for (int idx = 0; idx < this.players.size(); ++idx) { ++ if (((ServerPlayer) this.players.get(idx)).level() == this) { ++ ((ServerPlayer) this.players.get(idx)).setPlayerWeather((!isRaining ? org.bukkit.WeatherType.DOWNFALL : org.bukkit.WeatherType.CLEAR), false); ++ } ++ } ++ } ++ for (int idx = 0; idx < this.players.size(); ++idx) { ++ if (((ServerPlayer) this.players.get(idx)).level() == this) { ++ ((ServerPlayer) this.players.get(idx)).updateWeather(this.oRainLevel, this.rainLevel, this.oThunderLevel, this.thunderLevel); ++ } ++ } ++ // CraftBukkit end + } + + @VisibleForTesting + public void resetWeatherCycle() { +- this.serverLevelData.setRainTime(0); +- this.serverLevelData.setRaining(false); +- this.serverLevelData.setThunderTime(0); +- this.serverLevelData.setThundering(false); ++ // CraftBukkit start ++ this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents ++ // If we stop due to everyone sleeping we should reset the weather duration to some other random value. ++ // Not that everyone ever manages to get the whole server to sleep at the same time.... ++ if (!this.serverLevelData.isRaining()) { ++ this.serverLevelData.setRainTime(0); ++ } ++ // CraftBukkit end ++ this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents ++ // CraftBukkit start ++ // If we stop due to everyone sleeping we should reset the weather duration to some other random value. ++ // Not that everyone ever manages to get the whole server to sleep at the same time.... ++ if (!this.serverLevelData.isThundering()) { ++ this.serverLevelData.setThunderTime(0); ++ } ++ // CraftBukkit end + } + + public void resetEmptyTime() { +@@ -747,12 +_,20 @@ + } + + public void tickNonPassenger(Entity entity) { ++ // Spigot start ++ if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { ++ entity.tickCount++; ++ entity.inactiveTick(); ++ return; ++ } ++ // Spigot end + entity.setOldPosAndRot(); + ProfilerFiller profilerFiller = Profiler.get(); + entity.tickCount++; + profilerFiller.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString()); + profilerFiller.incrementCounter("tickNonPassenger"); + entity.tick(); ++ entity.postTick(); // CraftBukkit + profilerFiller.pop(); + + for (Entity entity1 : entity.getPassengers()) { +@@ -770,6 +_,7 @@ + profilerFiller.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(passengerEntity.getType()).toString()); + profilerFiller.incrementCounter("tickPassenger"); + passengerEntity.rideTick(); ++ passengerEntity.postTick(); // CraftBukkit + profilerFiller.pop(); + + for (Entity entity : passengerEntity.getPassengers()) { +@@ -786,6 +_,7 @@ + public void save(@Nullable ProgressListener progress, boolean flush, boolean skipSave) { + ServerChunkCache chunkSource = this.getChunkSource(); + if (!skipSave) { ++ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld())); // CraftBukkit + if (progress != null) { + progress.progressStartNoAbort(Component.translatable("menu.savingLevel")); + } +@@ -802,11 +_,19 @@ + this.entityManager.autoSave(); + } + } ++ ++ // CraftBukkit start - moved from MinecraftServer.saveChunks ++ ServerLevel worldserver1 = this; ++ ++ this.serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings()); ++ this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save(this.registryAccess())); ++ this.levelStorageAccess.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData()); ++ // CraftBukkit end + } + + private void saveLevelData(boolean join) { + if (this.dragonFight != null) { +- this.server.getWorldData().setEndDragonFightData(this.dragonFight.saveData()); ++ this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit + } + + DimensionDataStorage dataStorage = this.getChunkSource().getDataStorage(); +@@ -871,18 +_,40 @@ + + @Override + public boolean addFreshEntity(Entity entity) { +- return this.addEntity(entity); ++ // CraftBukkit start ++ return this.addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); ++ } ++ ++ @Override ++ public boolean addFreshEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ return this.addEntity(entity, reason); ++ // CraftBukkit end + } + + public boolean addWithUUID(Entity entity) { +- return this.addEntity(entity); ++ // CraftBukkit start ++ return this.addWithUUID(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); ++ } ++ ++ public boolean addWithUUID(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ return this.addEntity(entity, reason); ++ // CraftBukkit end + } + + public void addDuringTeleport(Entity entity) { ++ // CraftBukkit start ++ // SPIGOT-6415: Don't call spawn event for entities which travel trough worlds, ++ // since it is only an implementation detail, that a new entity is created when ++ // they are traveling between worlds. ++ this.addDuringTeleport(entity, null); ++ } ++ ++ public void addDuringTeleport(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ // CraftBukkit end + if (entity instanceof ServerPlayer serverPlayer) { + this.addPlayer(serverPlayer); + } else { +- this.addEntity(entity); ++ this.addEntity(entity, reason); // CraftBukkit + } + } + +@@ -905,40 +_,119 @@ + this.entityManager.addNewEntity(player); + } + +- private boolean addEntity(Entity entity) { ++ // CraftBukkit start ++ private boolean addEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) { ++ org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot ++ entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process ++ // Paper start - extra debug info ++ if (entity.valid) { ++ MinecraftServer.LOGGER.error("Attempted Double World add on {}", entity, new Throwable()); ++ return true; ++ } ++ // Paper end - extra debug info ++ if (entity.spawnReason == null) entity.spawnReason = spawnReason; // Paper - Entity#getEntitySpawnReason + if (entity.isRemoved()) { +- LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityType.getKey(entity.getType())); ++ // LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityType.getKey(entity.getType())); // CraftBukkit - remove warning + return false; + } else { ++ if (entity instanceof net.minecraft.world.entity.item.ItemEntity itemEntity && itemEntity.getItem().isEmpty()) return false; // Paper - Prevent empty items from being added ++ // Paper start - capture all item additions to the world ++ if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { ++ captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); ++ return true; ++ } ++ // Paper end - capture all item additions to the world ++ // SPIGOT-6415: Don't call spawn event when reason is null. For example when an entity teleports to a new world. ++ if (spawnReason != null && !org.bukkit.craftbukkit.event.CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) { ++ return false; ++ } ++ // CraftBukkit end ++ + return this.entityManager.addNewEntity(entity); + } + } + + public boolean tryAddFreshEntityWithPassengers(Entity entity) { ++ // CraftBukkit start ++ return this.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); ++ } ++ ++ public boolean tryAddFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { ++ // CraftBukkit end + if (entity.getSelfAndPassengers().map(Entity::getUUID).anyMatch(this.entityManager::isLoaded)) { + return false; + } else { +- this.addFreshEntityWithPassengers(entity); ++ this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit + return true; + } + } + + public void unload(LevelChunk chunk) { ++ // Spigot Start ++ for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) { ++ if (tileentity instanceof net.minecraft.world.Container) { ++ // Paper start - this area looks like it can load chunks, change the behavior ++ // chests for example can apply physics to the world ++ // so instead we just change the active container and call the event ++ for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) { ++ ((org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason ++ } ++ // Paper end - this area looks like it can load chunks, change the behavior ++ } ++ } ++ // Spigot End + chunk.clearAllBlockEntities(); + chunk.unregisterTickContainerFromLevel(this); + } + + public void removePlayerImmediately(ServerPlayer player, Entity.RemovalReason reason) { +- player.remove(reason); +- } ++ player.remove(reason, null); // CraftBukkit - add Bukkit remove cause ++ } ++ ++ // CraftBukkit start ++ public boolean strikeLightning(Entity entitylightning) { ++ return this.strikeLightning(entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause.UNKNOWN); ++ } ++ ++ public boolean strikeLightning(Entity entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause cause) { ++ org.bukkit.event.weather.LightningStrikeEvent lightning = org.bukkit.craftbukkit.event.CraftEventFactory.callLightningStrikeEvent((org.bukkit.entity.LightningStrike) entitylightning.getBukkitEntity(), cause); ++ ++ if (lightning.isCancelled()) { ++ return false; ++ } ++ ++ return this.addFreshEntity(entitylightning); ++ } ++ // CraftBukkit end + + @Override + public void destroyBlockProgress(int breakerId, BlockPos pos, int progress) { ++ // CraftBukkit start ++ Player breakerPlayer = null; ++ Entity entity = this.getEntity(breakerId); ++ if (entity instanceof Player) breakerPlayer = (Player) entity; ++ // CraftBukkit end ++ ++ // Paper start - Add BlockBreakProgressUpdateEvent ++ // If a plugin is using this method to send destroy packets for a client-side only entity id, no block progress occurred on the server. ++ // Hence, do not call the event. ++ if (entity != null) { ++ float progressFloat = Mth.clamp(progress, 0, 10) / 10.0f; ++ org.bukkit.craftbukkit.block.CraftBlock bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(this, pos); ++ new io.papermc.paper.event.block.BlockBreakProgressUpdateEvent(bukkitBlock, progressFloat, entity.getBukkitEntity()) ++ .callEvent(); ++ } ++ // Paper end - Add BlockBreakProgressUpdateEvent + for (ServerPlayer serverPlayer : this.server.getPlayerList().getPlayers()) { + if (serverPlayer != null && serverPlayer.level() == this && serverPlayer.getId() != breakerId) { + double d = pos.getX() - serverPlayer.getX(); + double d1 = pos.getY() - serverPlayer.getY(); + double d2 = pos.getZ() - serverPlayer.getZ(); ++ // CraftBukkit start ++ if (breakerPlayer != null && !serverPlayer.getBukkitEntity().canSee(breakerPlayer.getBukkitEntity())) { ++ continue; ++ } ++ // CraftBukkit end + if (d * d + d1 * d1 + d2 * d2 < 1024.0) { + serverPlayer.connection.send(new ClientboundBlockDestructionPacket(breakerId, pos, progress)); + } +@@ -1000,7 +_,7 @@ + public void levelEvent(@Nullable Player player, int type, BlockPos pos, int data) { + this.server + .getPlayerList() +- .broadcast(player, pos.getX(), pos.getY(), pos.getZ(), 64.0, this.dimension(), new ClientboundLevelEventPacket(type, pos, data, false)); ++ .broadcast(player, pos.getX(), pos.getY(), pos.getZ(), 64.0, this.dimension(), new ClientboundLevelEventPacket(type, pos, data, false)); // Paper - diff on change (the 64.0 distance is used as defaults for sound ranges in spigot config for ender dragon, end portal and wither) + } + + public int getLogicalHeight() { +@@ -1009,6 +_,11 @@ + + @Override + public void gameEvent(Holder gameEvent, Vec3 pos, GameEvent.Context context) { ++ // Paper start - Prevent GameEvents being fired from unloaded chunks ++ if (this.getChunkIfLoadedImmediately((Mth.floor(pos.x) >> 4), (Mth.floor(pos.z) >> 4)) == null) { ++ return; ++ } ++ // Paper end - Prevent GameEvents being fired from unloaded chunks + this.gameEventDispatcher.post(gameEvent, pos, context); + } + +@@ -1021,17 +_,28 @@ + + this.getChunkSource().blockChanged(pos); + this.pathTypesByPosCache.invalidate(pos); ++ if (this.paperConfig().misc.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates + VoxelShape collisionShape = oldState.getCollisionShape(this, pos); + VoxelShape collisionShape1 = newState.getCollisionShape(this, pos); + if (Shapes.joinIsNotEmpty(collisionShape, collisionShape1, BooleanOp.NOT_SAME)) { + List list = new ObjectArrayList<>(); + ++ try { // Paper - catch CME see below why + for (Mob mob : this.navigatingMobs) { + PathNavigation navigation = mob.getNavigation(); + if (navigation.shouldRecomputePath(pos)) { + list.add(navigation); + } + } ++ // Paper start - catch CME see below why ++ } catch (final java.util.ConcurrentModificationException concurrentModificationException) { ++ // This can happen because the pathfinder update below may trigger a chunk load, which in turn may cause more navigators to register ++ // In this case we just run the update again across all the iterators as the chunk will then be loaded ++ // As this is a relative edge case it is much faster than copying navigators (on either read or write) ++ this.sendBlockUpdated(pos, oldState, newState, flags); ++ return; ++ } ++ // Paper end - catch CME see below why + + try { + this.isUpdatingNavigations = true; +@@ -1043,15 +_,18 @@ + this.isUpdatingNavigations = false; + } + } ++ } // Paper - option to disable pathfinding updates + } + + @Override + public void updateNeighborsAt(BlockPos pos, Block block) { ++ if (captureBlockStates) { return; } // Paper - Cancel all physics during placement + this.updateNeighborsAt(pos, block, ExperimentalRedstoneUtils.initialOrientation(this, null, null)); + } + + @Override + public void updateNeighborsAt(BlockPos pos, Block block, @Nullable Orientation orientation) { ++ if (captureBlockStates) { return; } // Paper - Cancel all physics during placement + this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, block, null, orientation); + } + +@@ -1100,6 +_,42 @@ + ParticleOptions largeExplosionParticles, + Holder explosionSound + ) { ++ // CraftBukkit start ++ this.explode0(source, damageSource, damageCalculator, x, y, z, radius, fire, explosionInteraction, smallExplosionParticles, largeExplosionParticles, explosionSound); ++ } ++ ++ public ServerExplosion explode0( ++ @Nullable Entity source, ++ @Nullable DamageSource damageSource, ++ @Nullable ExplosionDamageCalculator damageCalculator, ++ double x, ++ double y, ++ double z, ++ float radius, ++ boolean fire, ++ Level.ExplosionInteraction explosionInteraction, ++ ParticleOptions smallExplosionParticles, ++ ParticleOptions largeExplosionParticles, ++ Holder explosionSound ++ ) { ++ return this.explode0(source, damageSource, damageCalculator, x, y, z, radius, fire, explosionInteraction, smallExplosionParticles, largeExplosionParticles, explosionSound, null); ++ } ++ public ServerExplosion explode0( ++ @Nullable Entity source, ++ @Nullable DamageSource damageSource, ++ @Nullable ExplosionDamageCalculator damageCalculator, ++ double x, ++ double y, ++ double z, ++ float radius, ++ boolean fire, ++ Level.ExplosionInteraction explosionInteraction, ++ ParticleOptions smallExplosionParticles, ++ ParticleOptions largeExplosionParticles, ++ Holder explosionSound, ++ java.util.function.Consumer configurator ++ ) { ++ // CraftBukkit end + Explosion.BlockInteraction blockInteraction = switch (explosionInteraction) { + case NONE -> Explosion.BlockInteraction.KEEP; + case BLOCK -> this.getDestroyType(GameRules.RULE_BLOCK_EXPLOSION_DROP_DECAY); +@@ -1108,10 +_,17 @@ + : Explosion.BlockInteraction.KEEP; + case TNT -> this.getDestroyType(GameRules.RULE_TNT_EXPLOSION_DROP_DECAY); + case TRIGGER -> Explosion.BlockInteraction.TRIGGER_BLOCK; ++ case STANDARD -> Explosion.BlockInteraction.DESTROY; // CraftBukkit - handle custom explosion type + }; + Vec3 vec3 = new Vec3(x, y, z); + ServerExplosion serverExplosion = new ServerExplosion(this, source, damageSource, damageCalculator, vec3, radius, fire, blockInteraction); ++ if (configurator != null) configurator.accept(serverExplosion);// Paper - Allow explosions to damage source + serverExplosion.explode(); ++ // CraftBukkit start ++ if (serverExplosion.wasCanceled) { ++ return serverExplosion; ++ } ++ // CraftBukkit end + ParticleOptions particleOptions = serverExplosion.isSmall() ? smallExplosionParticles : largeExplosionParticles; + + for (ServerPlayer serverPlayer : this.players) { +@@ -1120,6 +_,8 @@ + serverPlayer.connection.send(new ClientboundExplodePacket(vec3, optional, particleOptions, explosionSound)); + } + } ++ ++ return serverExplosion; // CraftBukkit + } + + private Explosion.BlockInteraction getDestroyType(GameRules.Key decayGameRule) { +@@ -1190,7 +_,7 @@ + public int sendParticles( + T type, double posX, double posY, double posZ, int particleCount, double xOffset, double yOffset, double zOffset, double speed + ) { +- return this.sendParticles(type, false, false, posX, posY, posZ, particleCount, xOffset, yOffset, zOffset, speed); ++ return this.sendParticlesSource(null, type, false, false, posX, posY, posZ, particleCount, xOffset, yOffset, zOffset, speed); // CraftBukkit - visibility api support + } + + public int sendParticles( +@@ -1206,13 +_,49 @@ + double zOffset, + double speed + ) { ++ // CraftBukkit start - visibility api support ++ return this.sendParticlesSource(null, type, overrideLimiter, alwaysShow, posX, posY, posZ, particleCount, xOffset, yOffset, zOffset, speed); ++ } ++ public int sendParticlesSource( ++ @javax.annotation.Nullable ServerPlayer sender, ++ T type, ++ boolean overrideLimiter, ++ boolean alwaysShow, ++ double posX, ++ double posY, ++ double posZ, ++ int particleCount, ++ double xOffset, ++ double yOffset, ++ double zOffset, ++ double speed ++ ) { ++ return sendParticlesSource(this.players, sender, type, overrideLimiter, alwaysShow, posX, posY, posZ, particleCount, xOffset, yOffset, zOffset, speed); ++ } ++ public int sendParticlesSource( ++ List receivers, ++ @javax.annotation.Nullable ServerPlayer sender, ++ T type, ++ boolean overrideLimiter, ++ boolean alwaysShow, ++ double posX, ++ double posY, ++ double posZ, ++ int particleCount, ++ double xOffset, ++ double yOffset, ++ double zOffset, ++ double speed ++ ) { ++ // CraftBukkit end - visibility api support + ClientboundLevelParticlesPacket clientboundLevelParticlesPacket = new ClientboundLevelParticlesPacket( + type, overrideLimiter, alwaysShow, posX, posY, posZ, (float)xOffset, (float)yOffset, (float)zOffset, (float)speed, particleCount + ); + int i = 0; + +- for (int i1 = 0; i1 < this.players.size(); i1++) { +- ServerPlayer serverPlayer = this.players.get(i1); ++ for (int i1 = 0; i1 < receivers.size(); i1++) { // Paper - particle API ++ ServerPlayer serverPlayer = receivers.get(i1); // Paper - particle API ++ if (sender != null && !serverPlayer.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; // CraftBukkit + if (this.sendParticles(serverPlayer, overrideLimiter, posX, posY, posZ, clientboundLevelParticlesPacket)) { + i++; + } +@@ -1280,7 +_,7 @@ + + @Nullable + public BlockPos findNearestMapStructure(TagKey structureTag, BlockPos pos, int radius, boolean skipExistingChunks) { +- if (!this.server.getWorldData().worldGenOptions().generateStructures()) { ++ if (!this.serverLevelData.worldGenOptions().generateStructures()) { // CraftBukkit + return null; + } else { + Optional> optional = this.registryAccess().lookupOrThrow(Registries.STRUCTURE).get(structureTag); +@@ -1327,11 +_,38 @@ + @Nullable + @Override + public MapItemSavedData getMapData(MapId mapId) { +- return this.getServer().overworld().getDataStorage().get(MapItemSavedData.factory(), mapId.key()); ++ // Paper start - Call missing map initialize event and set id ++ final DimensionDataStorage storage = this.getServer().overworld().getDataStorage(); ++ ++ final Optional cacheEntry = storage.cache.get(mapId.key()); ++ if (cacheEntry == null) { // Cache did not contain, try to load and may init ++ final MapItemSavedData worldmap = storage.get(MapItemSavedData.factory(), mapId.key()); // get populates the cache ++ if (worldmap != null) { // map was read, init it and return ++ worldmap.id = mapId; ++ new org.bukkit.event.server.MapInitializeEvent(worldmap.mapView).callEvent(); ++ return worldmap; ++ } ++ ++ return null; // Map does not exist, reading failed. ++ } ++ ++ // Cache entry exists, update it with the id ref and return. ++ if (cacheEntry.orElse(null) instanceof final MapItemSavedData mapItemSavedData) { ++ mapItemSavedData.id = mapId; ++ return mapItemSavedData; ++ } ++ ++ return null; ++ // Paper end - Call missing map initialize event and set id + } + + @Override + public void setMapData(MapId mapId, MapItemSavedData mapData) { ++ // CraftBukkit start ++ mapData.id = mapId; ++ org.bukkit.event.server.MapInitializeEvent event = new org.bukkit.event.server.MapInitializeEvent(mapData.mapView); ++ event.callEvent(); ++ // CraftBukkit end + this.getServer().overworld().getDataStorage().set(mapId.key(), mapData); + } + +@@ -1344,17 +_,27 @@ + BlockPos spawnPos = this.levelData.getSpawnPos(); + float spawnAngle = this.levelData.getSpawnAngle(); + if (!spawnPos.equals(pos) || spawnAngle != angle) { ++ org.bukkit.Location prevSpawnLoc = this.getWorld().getSpawnLocation(); // Paper - Call SpawnChangeEvent + this.levelData.setSpawn(pos, angle); ++ new org.bukkit.event.world.SpawnChangeEvent(this.getWorld(), prevSpawnLoc).callEvent(); // Paper - Call SpawnChangeEvent + this.getServer().getPlayerList().broadcastAll(new ClientboundSetDefaultSpawnPositionPacket(pos, angle)); + } + + if (this.lastSpawnChunkRadius > 1) { +- this.getChunkSource().removeRegionTicket(TicketType.START, new ChunkPos(spawnPos), this.lastSpawnChunkRadius, Unit.INSTANCE); ++ // Paper start - allow disabling gamerule limits ++ for (ChunkPos chunkPos : io.papermc.paper.util.MCUtil.getSpiralOutChunks(spawnPos, this.lastSpawnChunkRadius - 2)) { ++ this.getChunkSource().removeTicketAtLevel(TicketType.START, chunkPos, net.minecraft.server.level.ChunkLevel.ENTITY_TICKING_LEVEL, Unit.INSTANCE); ++ } ++ // Paper end - allow disabling gamerule limits + } + + int i = this.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS) + 1; + if (i > 1) { +- this.getChunkSource().addRegionTicket(TicketType.START, new ChunkPos(pos), i, Unit.INSTANCE); ++ // Paper start - allow disabling gamerule limits ++ for (ChunkPos chunkPos : io.papermc.paper.util.MCUtil.getSpiralOutChunks(pos, i - 2)) { ++ this.getChunkSource().addTicketAtLevel(TicketType.START, chunkPos, net.minecraft.server.level.ChunkLevel.ENTITY_TICKING_LEVEL, Unit.INSTANCE); ++ } ++ // Paper end - allow disabling gamerule limits + } + + this.lastSpawnChunkRadius = i; +@@ -1403,6 +_,11 @@ + DebugPackets.sendPoiRemovedPacket(this, blockPos); + })); + optional1.ifPresent(poiType -> this.getServer().execute(() -> { ++ // Paper start - Remove stale POIs ++ if (optional.isEmpty() && this.getPoiManager().exists(blockPos, ignored -> true)) { ++ this.getPoiManager().remove(blockPos); ++ } ++ // Paper end - Remove stale POIs + this.getPoiManager().add(blockPos, (Holder)poiType); + DebugPackets.sendPoiAddedPacket(this, blockPos); + })); +@@ -1543,6 +_,11 @@ + @Override + public void blockUpdated(BlockPos pos, Block block) { + if (!this.isDebug()) { ++ // CraftBukkit start ++ if (this.populating) { ++ return; ++ } ++ // CraftBukkit end + this.updateNeighborsAt(pos, block); + } + } +@@ -1562,12 +_,12 @@ + } + + public boolean isFlat() { +- return this.server.getWorldData().isFlatWorld(); ++ return this.serverLevelData.isFlatWorld(); // CraftBukkit + } + + @Override + public long getSeed() { +- return this.server.getWorldData().worldGenOptions().seed(); ++ return this.serverLevelData.worldGenOptions().seed(); // CraftBukkit + } + + @Nullable +@@ -1618,6 +_,7 @@ + + @Override + public LevelEntityGetter getEntities() { ++ org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot + return this.entityManager.getEntityGetter(); + } + +@@ -1699,6 +_,27 @@ + return this.serverLevelData.getGameRules(); + } + ++ // Paper start - respect global sound events gamerule ++ public List getPlayersForGlobalSoundGamerule() { ++ return this.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) ? ((ServerLevel) this).getServer().getPlayerList().players : ((ServerLevel) this).players(); ++ } ++ ++ public double getGlobalSoundRangeSquared(java.util.function.Function rangeFunction) { ++ final double range = rangeFunction.apply(this.spigotConfig); ++ return range <= 0 ? 64.0 * 64.0 : range * range; // 64 is taken from default in ServerLevel#levelEvent ++ } ++ // Paper end - respect global sound events gamerule ++ // Paper start - notify observers even if grow failed ++ public void checkCapturedTreeStateForObserverNotify(final BlockPos pos, final org.bukkit.craftbukkit.block.CraftBlockState craftBlockState) { ++ // notify observers if the block state is the same and the Y level equals the original y level (for mega trees) ++ // blocks at the same Y level with the same state can be assumed to be saplings which trigger observers regardless of if the ++ // tree grew or not ++ if (craftBlockState.getPosition().getY() == pos.getY() && this.getBlockState(craftBlockState.getPosition()) == craftBlockState.getHandle()) { ++ this.notifyAndUpdatePhysics(craftBlockState.getPosition(), null, craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getFlag(), 512); ++ } ++ } ++ // Paper end - notify observers even if grow failed ++ + @Override + public CrashReportCategory fillReportDetails(CrashReport report) { + CrashReportCategory crashReportCategory = super.fillReportDetails(report); +@@ -1723,24 +_,32 @@ + + @Override + public void onTickingStart(Entity entity) { ++ if (entity instanceof net.minecraft.world.entity.Marker && !paperConfig().entities.markers.tick) return; // Paper - Configurable marker ticking + ServerLevel.this.entityTickList.add(entity); + } + + @Override + public void onTickingEnd(Entity entity) { + ServerLevel.this.entityTickList.remove(entity); ++ // Paper start - Reset pearls when they stop being ticked ++ if (ServerLevel.this.paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && ServerLevel.this.paperConfig().misc.legacyEnderPearlBehavior && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) { ++ pearl.cachedOwner = null; ++ pearl.ownerUUID = null; ++ } ++ // Paper end - Reset pearls when they stop being ticked + } + + @Override + public void onTrackingStart(Entity entity) { +- ServerLevel.this.getChunkSource().addEntity(entity); ++ org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot ++ // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true + if (entity instanceof ServerPlayer serverPlayer) { + ServerLevel.this.players.add(serverPlayer); + ServerLevel.this.updateSleepingPlayerList(); + } + + if (entity instanceof Mob mob) { +- if (ServerLevel.this.isUpdatingNavigations) { ++ if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning + String string = "onTrackingStart called during navigation iteration"; + Util.logAndPauseIfInIde( + "onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration") +@@ -1757,10 +_,61 @@ + } + + entity.updateDynamicGameEventListener(DynamicGameEventListener::add); ++ entity.inWorld = true; // CraftBukkit - Mark entity as in world ++ entity.valid = true; // CraftBukkit ++ ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server ++ // Paper start - Entity origin API ++ if (entity.getOriginVector() == null) { ++ entity.setOrigin(entity.getBukkitEntity().getLocation()); ++ } ++ // Default to current world if unknown, gross assumption but entities rarely change world ++ if (entity.getOriginWorld() == null) { ++ entity.setOrigin(entity.getOriginVector().toLocation(getWorld())); ++ } ++ // Paper end - Entity origin API ++ new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(entity.getBukkitEntity(), ServerLevel.this.getWorld()).callEvent(); // Paper - fire while valid + } + + @Override + public void onTrackingEnd(Entity entity) { ++ org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot ++ // Spigot start ++ if ( entity instanceof Player ) ++ { ++ com.google.common.collect.Streams.stream( ServerLevel.this.getServer().getAllLevels() ).map( ServerLevel::getDataStorage ).forEach( (worldData) -> ++ { ++ for (Object o : worldData.cache.values() ) ++ { ++ if ( o instanceof MapItemSavedData ) ++ { ++ MapItemSavedData map = (MapItemSavedData) o; ++ map.carriedByPlayers.remove( (Player) entity ); ++ for ( ++ java.util.Iterator iter = map.carriedBy.iterator(); ++ iter.hasNext(); ++ ) { ++ if ( iter.next().player == entity ) ++ { ++ iter.remove(); ++ } ++ } ++ } ++ } ++ } ); ++ } ++ // Spigot end ++ // Spigot Start ++ if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message ++ // Paper start - Fix merchant inventory not closing on entity removal ++ if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { ++ merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); ++ } ++ // Paper end - Fix merchant inventory not closing on entity removal ++ for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((org.bukkit.inventory.InventoryHolder) entity.getBukkitEntity()).getInventory().getViewers())) { ++ h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason ++ } ++ } ++ // Spigot End + ServerLevel.this.getChunkSource().removeEntity(entity); + if (entity instanceof ServerPlayer serverPlayer) { + ServerLevel.this.players.remove(serverPlayer); +@@ -1768,7 +_,7 @@ + } + + if (entity instanceof Mob mob) { +- if (ServerLevel.this.isUpdatingNavigations) { ++ if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning + String string = "onTrackingStart called during navigation iteration"; + Util.logAndPauseIfInIde( + "onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration") +@@ -1785,6 +_,15 @@ + } + + entity.updateDynamicGameEventListener(DynamicGameEventListener::remove); ++ // CraftBukkit start ++ entity.valid = false; ++ if (!(entity instanceof ServerPlayer)) { ++ for (ServerPlayer player : ServerLevel.this.server.getPlayerList().players) { // Paper - call onEntityRemove for all online players ++ player.getBukkitEntity().onEntityRemove(entity); ++ } ++ } ++ // CraftBukkit end ++ new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity(), ServerLevel.this.getWorld()).callEvent(); // Paper - fire while valid + } + + @Override +@@ -1792,4 +_,12 @@ + entity.updateDynamicGameEventListener(DynamicGameEventListener::move); + } + } ++ ++ // Paper start - check global player list where appropriate ++ @Override ++ @Nullable ++ public Player getGlobalPlayerByUUID(UUID uuid) { ++ return this.server.getPlayerList().getPlayer(uuid); ++ } ++ // Paper end - check global player list where appropriate + } diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch new file mode 100644 index 0000000000..afffdcd318 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -0,0 +1,1684 @@ +--- a/net/minecraft/server/level/ServerPlayer.java ++++ b/net/minecraft/server/level/ServerPlayer.java +@@ -135,10 +_,12 @@ + import net.minecraft.world.entity.projectile.ThrownEnderpearl; + import net.minecraft.world.entity.vehicle.AbstractBoat; + import net.minecraft.world.entity.vehicle.AbstractMinecart; ++import net.minecraft.world.food.FoodData; + import net.minecraft.world.inventory.AbstractContainerMenu; + import net.minecraft.world.inventory.ContainerListener; + import net.minecraft.world.inventory.ContainerSynchronizer; + import net.minecraft.world.inventory.HorseInventoryMenu; ++import net.minecraft.world.inventory.InventoryMenu; + import net.minecraft.world.inventory.ResultSlot; + import net.minecraft.world.inventory.Slot; + import net.minecraft.world.item.Item; +@@ -158,12 +_,14 @@ + import net.minecraft.world.level.biome.BiomeManager; + import net.minecraft.world.level.block.BedBlock; + import net.minecraft.world.level.block.Block; ++import net.minecraft.world.level.block.ChestBlock; + import net.minecraft.world.level.block.HorizontalDirectionalBlock; + import net.minecraft.world.level.block.RespawnAnchorBlock; + import net.minecraft.world.level.block.entity.BlockEntity; + import net.minecraft.world.level.block.entity.CommandBlockEntity; + import net.minecraft.world.level.block.entity.SignBlockEntity; + import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.dimension.LevelStem; + import net.minecraft.world.level.gameevent.GameEvent; + import net.minecraft.world.level.portal.TeleportTransition; + import net.minecraft.world.level.saveddata.maps.MapId; +@@ -223,7 +_,8 @@ + private int levitationStartTime; + private boolean disconnected; + private int requestedViewDistance = 2; +- public String language = "en_us"; ++ public String language = null; // CraftBukkit - default // Paper - default to null ++ public java.util.Locale adventure$locale = java.util.Locale.US; // Paper + @Nullable + private Vec3 startingToFallPosition; + @Nullable +@@ -258,6 +_,13 @@ + } + } + ++ // Paper start - Sync offhand slot in menus ++ @Override ++ public void sendOffHandSlotChange() { ++ ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(ServerPlayer.this.inventoryMenu.containerId, ServerPlayer.this.inventoryMenu.incrementStateId(), net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT, ServerPlayer.this.inventoryMenu.getSlot(net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT).getItem().copy())); ++ } ++ // Paper end - Sync offhand slot in menus ++ + @Override + public void sendSlotChange(AbstractContainerMenu container, int slot, ItemStack itemStack) { + ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(container.containerId, container.incrementStateId(), slot, itemStack)); +@@ -288,6 +_,31 @@ + } + } + ++ // Paper start - Add PlayerInventorySlotChangeEvent ++ @Override ++ public void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) { ++ Slot slot = handler.getSlot(slotId); ++ if (!(slot instanceof ResultSlot)) { ++ if (slot.container == ServerPlayer.this.getInventory()) { ++ if (io.papermc.paper.event.player.PlayerInventorySlotChangeEvent.getHandlerList().getRegisteredListeners().length == 0) { ++ CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack); ++ return; ++ } ++ io.papermc.paper.event.player.PlayerInventorySlotChangeEvent event = new io.papermc.paper.event.player.PlayerInventorySlotChangeEvent( ++ ServerPlayer.this.getBukkitEntity(), ++ slotId, ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(oldStack), ++ org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack) ++ ); ++ event.callEvent(); ++ if (event.shouldTriggerAdvancements()) { ++ CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack); ++ } ++ } ++ } ++ } ++ // Paper end - Add PlayerInventorySlotChangeEvent ++ + @Override + public void dataChanged(AbstractContainerMenu containerMenu, int dataSlotIndex, int value) { + } +@@ -316,9 +_,43 @@ + public void sendSystemMessage(Component component) { + ServerPlayer.this.sendSystemMessage(component); + } ++ ++ // CraftBukkit start ++ @Override ++ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) { ++ return ServerPlayer.this.getBukkitEntity(); ++ } ++ // CraftBukkit end + }; + private int containerCounter; + public boolean wonGame; ++ private int containerUpdateDelay; // Paper - Configurable container update tick rate ++ public long loginTime; // Paper - Replace OfflinePlayer#getLastPlayed ++ public int patrolSpawnDelay; // Paper - Pillager patrol spawn settings and per player options ++ // Paper start - cancellable death event ++ public boolean queueHealthUpdatePacket; ++ public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; ++ // Paper end - cancellable death event ++ // CraftBukkit start ++ public org.bukkit.craftbukkit.entity.CraftPlayer.TransferCookieConnection transferCookieConnection; ++ public String displayName; ++ public net.kyori.adventure.text.Component adventure$displayName; // Paper ++ public Component listName; ++ public int listOrder = 0; ++ public org.bukkit.Location compassTarget; ++ public int newExp = 0; ++ public int newLevel = 0; ++ public int newTotalExp = 0; ++ public boolean keepLevel = false; ++ public double maxHealthCache; ++ public boolean joining = true; ++ public boolean sentListPacket = false; ++ public boolean supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready ++ // CraftBukkit end ++ public boolean isRealPlayer; // Paper ++ public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent ++ public @Nullable String clientBrandName = null; // Paper - Brand support ++ public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event + + public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) { + super(level, level.getSharedSpawnPos(), level.getSharedSpawnAngle(), gameProfile); +@@ -328,16 +_,71 @@ + this.server = server; + this.stats = server.getPlayerList().getPlayerStats(this); + this.advancements = server.getPlayerList().getPlayerAdvancements(this); +- this.moveTo(this.adjustSpawnLocation(level, level.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); +- this.updateOptions(clientInformation); ++ // this.moveTo(this.adjustSpawnLocation(level, level.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); // Paper - Don't move existing players to world spawn ++ this.updateOptionsNoEvents(clientInformation); // Paper - don't call options events on login + this.object = null; ++ ++ // CraftBukkit start ++ this.displayName = this.getScoreboardName(); ++ this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getScoreboardName()); // Paper ++ this.bukkitPickUpLoot = true; ++ this.maxHealthCache = this.getMaxHealth(); ++ } ++ ++ // Use method to resend items in hands in case of client desync, because the item use got cancelled. ++ // For example, when cancelling the leash event ++ @Deprecated // Paper - this shouldn't be used, use the regular sendAllDataToRemote call to resync all ++ public void resendItemInHands() { ++ this.containerMenu.findSlot(this.getInventory(), this.getInventory().selected).ifPresent(s -> { ++ this.containerSynchronizer.sendSlotChange(this.containerMenu, s, this.getMainHandItem()); ++ }); ++ this.containerSynchronizer.sendSlotChange(this.inventoryMenu, InventoryMenu.SHIELD_SLOT, this.getOffhandItem()); ++ } ++ ++ // Yes, this doesn't match Vanilla, but it's the best we can do for now. ++ // If this is an issue, PRs are welcome ++ public final BlockPos getSpawnPoint(ServerLevel worldserver) { ++ BlockPos blockposition = worldserver.getSharedSpawnPos(); ++ ++ if (worldserver.dimensionType().hasSkyLight() && worldserver.serverLevelData.getGameType() != GameType.ADVENTURE) { ++ int i = Math.max(0, this.server.getSpawnRadius(worldserver)); ++ int j = Mth.floor(worldserver.getWorldBorder().getDistanceToBorder((double) blockposition.getX(), (double) blockposition.getZ())); ++ ++ if (j < i) { ++ i = j; ++ } ++ ++ if (j <= 1) { ++ i = 1; ++ } ++ ++ long k = (long) (i * 2 + 1); ++ long l = k * k; ++ int i1 = l > 2147483647L ? Integer.MAX_VALUE : (int) l; ++ int j1 = this.getCoprime(i1); ++ int k1 = RandomSource.create().nextInt(i1); ++ ++ for (int l1 = 0; l1 < i1; ++l1) { ++ int i2 = (k1 + j1 * l1) % i1; ++ int j2 = i2 % (i * 2 + 1); ++ int k2 = i2 / (i * 2 + 1); ++ BlockPos blockposition1 = PlayerRespawnLogic.getOverworldRespawnPos(worldserver, blockposition.getX() + j2 - i, blockposition.getZ() + k2 - i); ++ ++ if (blockposition1 != null) { ++ return blockposition1; ++ } ++ } ++ } ++ ++ return blockposition; ++ // CraftBukkit end + } + + @Override + public BlockPos adjustSpawnLocation(ServerLevel level, BlockPos pos) { + AABB aabb = this.getDimensions(Pose.STANDING).makeBoundingBox(Vec3.ZERO); + BlockPos blockPos = pos; +- if (level.dimensionType().hasSkyLight() && level.getServer().getWorldData().getGameType() != GameType.ADVENTURE) { ++ if (level.dimensionType().hasSkyLight() && level.serverLevelData.getGameType() != GameType.ADVENTURE) { // CraftBukkit + int max = Math.max(0, this.server.getSpawnRadius(level)); + int floor = Mth.floor(level.getWorldBorder().getDistanceToBorder(pos.getX(), pos.getZ())); + if (floor < max) { +@@ -420,11 +_,20 @@ + if (compound.contains("recipeBook", 10)) { + this.recipeBook.fromNbt(compound.getCompound("recipeBook"), key -> this.server.getRecipeManager().byKey(key).isPresent()); + } ++ this.getBukkitEntity().readExtraData(compound); // CraftBukkit + + if (this.isSleeping()) { + this.stopSleeping(); + } + ++ // CraftBukkit start ++ String spawnWorld = compound.getString("SpawnWorld"); ++ org.bukkit.craftbukkit.CraftWorld oldWorld = (org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getWorld(spawnWorld); ++ if (oldWorld != null) { ++ this.respawnDimension = oldWorld.getHandle().dimension(); ++ } ++ // CraftBukkit end ++ + if (compound.contains("SpawnX", 99) && compound.contains("SpawnY", 99) && compound.contains("SpawnZ", 99)) { + this.respawnPosition = new BlockPos(compound.getInt("SpawnX"), compound.getInt("SpawnY"), compound.getInt("SpawnZ")); + this.respawnForced = compound.getBoolean("SpawnForced"); +@@ -490,7 +_,18 @@ + private void saveParentVehicle(CompoundTag tag) { + Entity rootVehicle = this.getRootVehicle(); + Entity vehicle = this.getVehicle(); +- if (vehicle != null && rootVehicle != this && rootVehicle.hasExactlyOnePlayerPassenger()) { ++ // CraftBukkit start - handle non-persistent vehicles ++ boolean persistVehicle = true; ++ if (vehicle != null) { ++ for (Entity topVehicle = vehicle; topVehicle != null; topVehicle = topVehicle.getVehicle()) { ++ if (!topVehicle.persist) { ++ persistVehicle = false; ++ break; ++ } ++ } ++ } ++ if (persistVehicle && vehicle != null && rootVehicle != this && rootVehicle.hasExactlyOnePlayerPassenger() && !rootVehicle.isRemoved()) { // Paper - Ensure valid vehicle status ++ // CraftBukkit end + CompoundTag compoundTag = new CompoundTag(); + CompoundTag compoundTag1 = new CompoundTag(); + rootVehicle.save(compoundTag1); +@@ -504,7 +_,7 @@ + if (tag.isPresent() && tag.get().contains("RootVehicle", 10) && this.level() instanceof ServerLevel serverLevel) { + CompoundTag compound = tag.get().getCompound("RootVehicle"); + Entity entity = EntityType.loadEntityRecursive( +- compound.getCompound("Entity"), serverLevel, EntitySpawnReason.LOAD, entity2 -> !serverLevel.addWithUUID(entity2) ? null : entity2 ++ compound.getCompound("Entity"), serverLevel, EntitySpawnReason.LOAD, entity2 -> !serverLevel.addWithUUID(entity2, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT) ? null : entity2 // Paper - Entity#getEntitySpawnReason + ); + if (entity == null) { + return; +@@ -530,10 +_,10 @@ + + if (!this.isPassenger()) { + LOGGER.warn("Couldn't reattach entity to player"); +- entity.discard(); ++ entity.discard(null); // CraftBukkit - add Bukkit remove cause + + for (Entity entity1x : entity.getIndirectPassengers()) { +- entity1x.discard(); ++ entity1x.discard(null); // CraftBukkit - add Bukkit remove cause + } + } + } +@@ -544,6 +_,7 @@ + ListTag listTag = new ListTag(); + + for (ThrownEnderpearl thrownEnderpearl : this.enderPearls) { ++ if (thrownEnderpearl.level().paperConfig().misc.legacyEnderPearlBehavior) continue; // Paper - Allow using old ender pearl behavior + if (thrownEnderpearl.isRemoved()) { + LOGGER.warn("Trying to save removed ender pearl, skipping"); + } else { +@@ -593,6 +_,29 @@ + } + } + ++ // CraftBukkit start - World fallback code, either respawn location or global spawn ++ public void spawnIn(Level world) { ++ this.setLevel(world); ++ if (world == null) { ++ this.unsetRemoved(); ++ Vec3 position = null; ++ if (this.respawnDimension != null) { ++ world = this.server.getLevel(this.respawnDimension); ++ if (world != null && this.getRespawnPosition() != null) { ++ position = ServerPlayer.findRespawnAndUseSpawnBlock((ServerLevel) world, this.getRespawnPosition(), this.getRespawnAngle(), false, false).map(ServerPlayer.RespawnPosAngle::position).orElse(null); ++ } ++ } ++ if (world == null || position == null) { ++ world = ((org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getServer().getWorlds().get(0)).getHandle(); ++ position = Vec3.atCenterOf(world.getSharedSpawnPos()); ++ } ++ this.setLevel(world); ++ this.setPosRaw(position.x(), position.y(), position.z()); // Paper - don't register to chunks yet ++ } ++ this.gameMode.setLevel((ServerLevel) world); ++ } ++ // CraftBukkit end ++ + public void setExperiencePoints(int experiencePoints) { + float f = this.getXpNeededForNextLevel(); + float f1 = (f - 1.0F) / f; +@@ -650,6 +_,11 @@ + + @Override + public void tick() { ++ // CraftBukkit start ++ if (this.joining) { ++ this.joining = false; ++ } ++ // CraftBukkit end + this.tickClientLoadTimeout(); + this.gameMode.tick(); + this.wardenSpawnTracker.tick(); +@@ -657,9 +_,14 @@ + this.invulnerableTime--; + } + +- this.containerMenu.broadcastChanges(); +- if (!this.containerMenu.stillValid(this)) { +- this.closeContainer(); ++ // Paper start - Configurable container update tick rate ++ if (--this.containerUpdateDelay <= 0) { ++ this.containerMenu.broadcastChanges(); ++ this.containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate; ++ } ++ // Paper end - Configurable container update tick rate ++ if (this.containerMenu != this.inventoryMenu && (this.isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - Prevent opening inventories when frozen ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason + this.containerMenu = this.inventoryMenu; + } + +@@ -709,7 +_,7 @@ + + public void doTick() { + try { +- if (!this.isSpectator() || !this.touchingUnloadedChunk()) { ++ if (valid && !this.isSpectator() || !this.touchingUnloadedChunk()) { // Paper - don't tick dead players that are not in the world currently (pending respawn) + super.tick(); + } + +@@ -723,7 +_,7 @@ + if (this.getHealth() != this.lastSentHealth + || this.lastSentFood != this.foodData.getFoodLevel() + || this.foodData.getSaturationLevel() == 0.0F != this.lastFoodSaturationZero) { +- this.connection.send(new ClientboundSetHealthPacket(this.getHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel())); ++ this.connection.send(new ClientboundSetHealthPacket(this.getBukkitEntity().getScaledHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel())); // CraftBukkit + this.lastSentHealth = this.getHealth(); + this.lastSentFood = this.foodData.getFoodLevel(); + this.lastFoodSaturationZero = this.foodData.getSaturationLevel() == 0.0F; +@@ -754,6 +_,12 @@ + this.updateScoreForCriteria(ObjectiveCriteria.EXPERIENCE, Mth.ceil((float)this.lastRecordedExperience)); + } + ++ // CraftBukkit start - Force max health updates ++ if (this.maxHealthCache != this.getMaxHealth()) { ++ this.getBukkitEntity().updateScaledHealth(); ++ } ++ // CraftBukkit end ++ + if (this.experienceLevel != this.lastRecordedLevel) { + this.lastRecordedLevel = this.experienceLevel; + this.updateScoreForCriteria(ObjectiveCriteria.LEVEL, Mth.ceil((float)this.lastRecordedLevel)); +@@ -767,6 +_,21 @@ + if (this.tickCount % 20 == 0) { + CriteriaTriggers.LOCATION.trigger(this); + } ++ ++ // CraftBukkit start - initialize oldLevel, fire PlayerLevelChangeEvent, and tick client-sided world border ++ if (this.oldLevel == -1) { ++ this.oldLevel = this.experienceLevel; ++ } ++ ++ if (this.oldLevel != this.experienceLevel) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerLevelChangeEvent(this.getBukkitEntity(), this.oldLevel, this.experienceLevel); ++ this.oldLevel = this.experienceLevel; ++ } ++ ++ if (this.getBukkitEntity().hasClientWorldBorder()) { ++ ((org.bukkit.craftbukkit.CraftWorldBorder) this.getBukkitEntity().getWorldBorder()).getHandle().tick(); ++ } ++ // CraftBukkit end + } catch (Throwable var4) { + CrashReport crashReport = CrashReport.forThrowable(var4, "Ticking player"); + CrashReportCategory crashReportCategory = crashReport.addCategory("Player being ticked"); +@@ -791,7 +_,7 @@ + if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.serverLevel().getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION)) { + if (this.tickCount % 20 == 0) { + if (this.getHealth() < this.getMaxHealth()) { +- this.heal(1.0F); ++ this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit - added regain reason of "REGEN" for filtering purposes. + } + + float saturationLevel = this.foodData.getSaturationLevel(); +@@ -840,15 +_,100 @@ + } + + private void updateScoreForCriteria(ObjectiveCriteria criteria, int points) { +- this.getScoreboard().forAllObjectives(criteria, this, scoreAccess -> scoreAccess.set(points)); +- } ++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(criteria, this, scoreAccess -> scoreAccess.set(points)); ++ } ++ ++ // Paper start - PlayerDeathEvent#getItemsToKeep ++ private static void processKeep(org.bukkit.event.entity.PlayerDeathEvent event, NonNullList inv) { ++ List itemsToKeep = event.getItemsToKeep(); ++ if (inv == null) { ++ // remainder of items left in toKeep - plugin added stuff on death that wasn't in the initial loot? ++ if (!itemsToKeep.isEmpty()) { ++ for (org.bukkit.inventory.ItemStack itemStack : itemsToKeep) { ++ event.getEntity().getInventory().addItem(itemStack); ++ } ++ } ++ ++ return; ++ } ++ ++ for (int i = 0; i < inv.size(); ++i) { ++ ItemStack item = inv.get(i); ++ if (EnchantmentHelper.has(item, net.minecraft.world.item.enchantment.EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP) || itemsToKeep.isEmpty() || item.isEmpty()) { ++ inv.set(i, ItemStack.EMPTY); ++ continue; ++ } ++ ++ final org.bukkit.inventory.ItemStack bukkitStack = item.getBukkitStack(); ++ boolean keep = false; ++ final java.util.Iterator iterator = itemsToKeep.iterator(); ++ while (iterator.hasNext()) { ++ final org.bukkit.inventory.ItemStack itemStack = iterator.next(); ++ if (bukkitStack.equals(itemStack)) { ++ iterator.remove(); ++ keep = true; ++ break; ++ } ++ } ++ ++ if (!keep) { ++ inv.set(i, ItemStack.EMPTY); ++ } ++ } ++ } ++ // Paper end - PlayerDeathEvent#getItemsToKeep + + @Override + public void die(DamageSource cause) { +- this.gameEvent(GameEvent.ENTITY_DIE); +- boolean _boolean = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES); +- if (_boolean) { +- Component deathMessage = this.getCombatTracker().getDeathMessage(); ++ // this.gameEvent(GameEvent.ENTITY_DIE); // Paper - move below event cancellation check ++ boolean _boolean = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES); final boolean showDeathMessage = _boolean; // Paper - OBFHELPER ++ // CraftBukkit start - fire PlayerDeathEvent ++ if (this.isRemoved()) { ++ return; ++ } ++ List loot = new java.util.ArrayList<>(this.getInventory().getContainerSize()); // Paper - Restore vanilla drops behavior ++ boolean keepInventory = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator(); ++ if (!keepInventory) { ++ for (ItemStack item : this.getInventory().getContents()) { ++ if (!item.isEmpty() && !EnchantmentHelper.has(item, net.minecraft.world.item.enchantment.EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) { ++ loot.add(new DefaultDrop(item, stack -> this.drop(stack, true, false, false))); // Paper - Restore vanilla drops behavior; drop function taken from Inventory#dropAll (don't fire drop event) ++ } ++ } ++ } ++ if (this.shouldDropLoot() && this.serverLevel().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - fix player loottables running when mob loot gamerule is false ++ // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule) ++ this.dropFromLootTable(this.serverLevel(), cause, this.lastHurtByPlayerTime > 0); ++ // Paper - Restore vanilla drops behaviour; custom death loot is a noop on server player, remove. ++ loot.addAll(this.drops); ++ this.drops.clear(); // SPIGOT-5188: make sure to clear ++ } // Paper - fix player loottables running when mob loot gamerule is false ++ ++ Component defaultMessage = this.getCombatTracker().getDeathMessage(); ++ ++ String deathmessage = defaultMessage.getString(); ++ this.keepLevel = keepInventory; // SPIGOT-2222: pre-set keepLevel ++ org.bukkit.event.entity.PlayerDeathEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerDeathEvent(this, cause, loot, io.papermc.paper.adventure.PaperAdventure.asAdventure(defaultMessage), keepInventory); // Paper - Adventure ++ // Paper start - cancellable death event ++ if (event.isCancelled()) { ++ // make compatible with plugins that might have already set the health in an event listener ++ if (this.getHealth() <= 0) { ++ this.setHealth((float) event.getReviveHealth()); ++ } ++ return; ++ } ++ this.gameEvent(GameEvent.ENTITY_DIE); // moved from the top of this method ++ // Paper end ++ ++ // SPIGOT-943 - only call if they have an inventory open ++ if (this.containerMenu != this.inventoryMenu) { ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DEATH); // Paper - Inventory close reason ++ } ++ ++ net.kyori.adventure.text.Component apiDeathMessage = event.deathMessage() != null ? event.deathMessage() : net.kyori.adventure.text.Component.empty(); // Paper - Adventure ++ ++ if (apiDeathMessage != null && apiDeathMessage != net.kyori.adventure.text.Component.empty() && showDeathMessage) { // Paper - Adventure // TODO: allow plugins to override? ++ Component deathMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(apiDeathMessage); // Paper - Adventure ++ + this.connection + .send( + new ClientboundPlayerCombatKillPacket(this.getId(), deathMessage), +@@ -882,11 +_,22 @@ + this.tellNeutralMobsThatIDied(); + } + +- if (!this.isSpectator()) { +- this.dropAllDeathLoot(this.serverLevel(), cause); ++ // SPIGOT-5478 must be called manually now ++ if (event.shouldDropExperience()) this.dropExperience(this.serverLevel(), cause.getEntity()); // Paper - tie to event ++ // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. ++ if (!event.getKeepInventory()) { ++ // Paper start - PlayerDeathEvent#getItemsToKeep ++ for (NonNullList inv : this.getInventory().compartments) { ++ processKeep(event, inv); ++ } ++ processKeep(event, null); ++ // Paper end - PlayerDeathEvent#getItemsToKeep + } + +- this.getScoreboard().forAllObjectives(ObjectiveCriteria.DEATH_COUNT, this, ScoreAccess::increment); ++ this.setCamera(this); // Remove spectated target ++ // CraftBukkit end ++ ++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.DEATH_COUNT, this, ScoreAccess::increment); // CraftBukkit - Get our scores instead + LivingEntity killCredit = this.getKillCredit(); + if (killCredit != null) { + this.awardStat(Stats.ENTITY_KILLED_BY.get(killCredit.getType())); +@@ -919,10 +_,10 @@ + public void awardKillScore(Entity entity, DamageSource damageSource) { + if (entity != this) { + super.awardKillScore(entity, damageSource); +- this.getScoreboard().forAllObjectives(ObjectiveCriteria.KILL_COUNT_ALL, this, ScoreAccess::increment); ++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.KILL_COUNT_ALL, this, ScoreAccess::increment); // CraftBukkit - Get our scores instead + if (entity instanceof Player) { + this.awardStat(Stats.PLAYER_KILLS); +- this.getScoreboard().forAllObjectives(ObjectiveCriteria.KILL_COUNT_PLAYERS, this, ScoreAccess::increment); ++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.KILL_COUNT_PLAYERS, this, ScoreAccess::increment); // CraftBukkit - Get our scores instead + } else { + this.awardStat(Stats.MOB_KILLS); + } +@@ -938,7 +_,7 @@ + if (playersTeam != null) { + int id = playersTeam.getColor().getId(); + if (id >= 0 && id < crtieria.length) { +- this.getScoreboard().forAllObjectives(crtieria[id], scoreHolder, ScoreAccess::increment); ++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(crtieria[id], scoreHolder, ScoreAccess::increment); // CraftBukkit - Get our scores instead + } + } + } +@@ -949,9 +_,20 @@ + return false; + } else { + Entity entity = damageSource.getEntity(); +- return !(entity instanceof Player player && !this.canHarmPlayer(player)) ++ if (!( // Paper - split the if statement. If below statement is false, hurtServer would not have been evaluated. Return false. ++ !(entity instanceof Player player && !this.canHarmPlayer(player)) + && !(entity instanceof AbstractArrow abstractArrow && abstractArrow.getOwner() instanceof Player player1 && !this.canHarmPlayer(player1)) +- && super.hurtServer(level, damageSource, amount); ++ )) return false; // Paper - split the if statement. If below statement is false, hurtServer would not have been evaluated. Return false. ++ // Paper start - cancellable death events ++ this.queueHealthUpdatePacket = true; ++ boolean damaged = super.hurtServer(level, damageSource, amount); ++ this.queueHealthUpdatePacket = false; ++ if (this.queuedHealthUpdatePacket != null) { ++ this.connection.send(this.queuedHealthUpdatePacket); ++ this.queuedHealthUpdatePacket = null; ++ } ++ return damaged; ++ // Paper end - cancellable death events + } + } + +@@ -961,10 +_,15 @@ + } + + private boolean isPvpAllowed() { +- return this.server.isPvpAllowed(); ++ return this.level().pvpMode; // CraftBukkit - this.server.isPvpAllowed() -> this.world.pvpMode + } + +- public TeleportTransition findRespawnPositionAndUseSpawnBlock(boolean useCharge, TeleportTransition.PostTeleportTransition postTeleportTransition) { ++ // CraftBukkit start ++ public TeleportTransition findRespawnPositionAndUseSpawnBlock(boolean useCharge, TeleportTransition.PostTeleportTransition postTeleportTransition, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason respawnReason) { ++ TeleportTransition teleportTransition; ++ boolean isBedSpawn = false; ++ boolean isAnchorSpawn = false; ++ // CraftBukkit end + BlockPos respawnPosition = this.getRespawnPosition(); + float respawnAngle = this.getRespawnAngle(); + boolean isRespawnForced = this.isRespawnForced(); +@@ -973,13 +_,66 @@ + Optional optional = findRespawnAndUseSpawnBlock(level, respawnPosition, respawnAngle, isRespawnForced, useCharge); + if (optional.isPresent()) { + ServerPlayer.RespawnPosAngle respawnPosAngle = optional.get(); +- return new TeleportTransition(level, respawnPosAngle.position(), Vec3.ZERO, respawnPosAngle.yaw(), 0.0F, postTeleportTransition); ++ // CraftBukkit start ++ isBedSpawn = respawnPosAngle.isBedSpawn(); ++ isAnchorSpawn = respawnPosAngle.isAnchorSpawn(); ++ teleportTransition = new TeleportTransition(level, respawnPosAngle.position(), Vec3.ZERO, respawnPosAngle.yaw(), 0.0F, postTeleportTransition); ++ // CraftBukkit end + } else { +- return TeleportTransition.missingRespawnBlock(this.server.overworld(), this, postTeleportTransition); ++ teleportTransition = TeleportTransition.missingRespawnBlock(this.server.overworld(), this, postTeleportTransition); // CraftBukkit + } + } else { +- return new TeleportTransition(this.server.overworld(), this, postTeleportTransition); +- } ++ teleportTransition = new TeleportTransition(this.server.overworld(), this, postTeleportTransition); // CraftBukkit ++ } ++ // CraftBukkit start ++ if (respawnReason == null) { ++ return teleportTransition; ++ } ++ ++ org.bukkit.entity.Player respawnPlayer = this.getBukkitEntity(); ++ org.bukkit.Location location = org.bukkit.craftbukkit.util.CraftLocation.toBukkit( ++ teleportTransition.position(), ++ teleportTransition.newLevel().getWorld(), ++ teleportTransition.yRot(), ++ teleportTransition.xRot() ++ ); ++ ++ // Paper start - respawn flags ++ com.google.common.collect.ImmutableSet.Builder builder = com.google.common.collect.ImmutableSet.builder(); ++ if (respawnReason == org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.END_PORTAL) { ++ builder.add(org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag.END_PORTAL); ++ } ++ org.bukkit.event.player.PlayerRespawnEvent respawnEvent = new org.bukkit.event.player.PlayerRespawnEvent( ++ respawnPlayer, ++ location, ++ isBedSpawn, ++ isAnchorSpawn, ++ respawnReason, ++ builder ++ ); ++ // Paper end - respawn flags ++ this.level().getCraftServer().getPluginManager().callEvent(respawnEvent); ++ // Spigot Start ++ if (this.connection.isDisconnected()) { ++ return null; ++ } ++ // Spigot End ++ ++ location = respawnEvent.getRespawnLocation(); ++ ++ return new TeleportTransition( ++ ((org.bukkit.craftbukkit.CraftWorld) location.getWorld()).getHandle(), ++ org.bukkit.craftbukkit.util.CraftLocation.toVec3D(location), ++ teleportTransition.deltaMovement(), ++ location.getYaw(), ++ location.getPitch(), ++ teleportTransition.missingRespawnBlock(), ++ teleportTransition.asPassenger(), ++ teleportTransition.relatives(), ++ teleportTransition.postTeleportTransition(), ++ teleportTransition.cause() ++ ); ++ // CraftBukkit end + } + + public static Optional findRespawnAndUseSpawnBlock( +@@ -993,10 +_,10 @@ + level.setBlock(pos, blockState.setValue(RespawnAnchorBlock.CHARGE, Integer.valueOf(blockState.getValue(RespawnAnchorBlock.CHARGE) - 1)), 3); + } + +- return optional.map(vec3 -> ServerPlayer.RespawnPosAngle.of(vec3, pos)); ++ return optional.map(vec3 -> ServerPlayer.RespawnPosAngle.of(vec3, pos, false, true)); // CraftBukkit + } else if (block instanceof BedBlock && BedBlock.canSetSpawn(level)) { + return BedBlock.findStandUpPosition(EntityType.PLAYER, level, pos, blockState.getValue(BedBlock.FACING), angle) +- .map(vec3 -> ServerPlayer.RespawnPosAngle.of(vec3, pos)); ++ .map(vec3 -> ServerPlayer.RespawnPosAngle.of(vec3, pos, true, false)); // CraftBukkit + } else if (!forced) { + return Optional.empty(); + } else { +@@ -1004,7 +_,7 @@ + BlockState blockState1 = level.getBlockState(pos.above()); + boolean isPossibleToRespawnInThis1 = blockState1.getBlock().isPossibleToRespawnInThis(blockState1); + return isPossibleToRespawnInThis && isPossibleToRespawnInThis1 +- ? Optional.of(new ServerPlayer.RespawnPosAngle(new Vec3(pos.getX() + 0.5, pos.getY() + 0.1, pos.getZ() + 0.5), angle)) ++ ? Optional.of(new ServerPlayer.RespawnPosAngle(new Vec3(pos.getX() + 0.5, pos.getY() + 0.1, pos.getZ() + 0.5), angle, false, false)) // CraftBukkit + : Optional.empty(); + } + } +@@ -1022,6 +_,7 @@ + @Nullable + @Override + public ServerPlayer teleport(TeleportTransition teleportTransition) { ++ if (this.isSleeping()) return null; // CraftBukkit - SPIGOT-3154 + if (this.isRemoved()) { + return null; + } else { +@@ -1031,17 +_,52 @@ + + ServerLevel level = teleportTransition.newLevel(); + ServerLevel serverLevel = this.serverLevel(); +- ResourceKey resourceKey = serverLevel.dimension(); ++ // CraftBukkit start ++ ResourceKey resourceKey = serverLevel.getTypeKey(); ++ ++ org.bukkit.Location enter = this.getBukkitEntity().getLocation(); ++ PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTransition), teleportTransition.relatives()); ++ org.bukkit.Location exit = /* (worldserver == null) ? null : // Paper - always non-null */org.bukkit.craftbukkit.util.CraftLocation.toBukkit(absolutePosition.position(), level.getWorld(), absolutePosition.yRot(), absolutePosition.xRot()); ++ org.bukkit.event.player.PlayerTeleportEvent tpEvent = new org.bukkit.event.player.PlayerTeleportEvent(this.getBukkitEntity(), enter, exit, teleportTransition.cause()); ++ // Paper start - gateway-specific teleport event ++ if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.serverLevel().getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) { ++ tpEvent = new com.destroystokyo.paper.event.player.PlayerTeleportEndGatewayEvent(this.getBukkitEntity(), enter, exit, new org.bukkit.craftbukkit.block.CraftEndGateway(this.serverLevel().getWorld(), theEndGatewayBlockEntity)); ++ } ++ // Paper end - gateway-specific teleport event ++ org.bukkit.Bukkit.getServer().getPluginManager().callEvent(tpEvent); ++ org.bukkit.Location newExit = tpEvent.getTo(); ++ if (tpEvent.isCancelled() || newExit == null) { ++ return null; ++ } ++ if (!newExit.equals(exit)) { ++ level = ((org.bukkit.craftbukkit.CraftWorld) newExit.getWorld()).getHandle(); ++ teleportTransition = new TeleportTransition( ++ level, ++ org.bukkit.craftbukkit.util.CraftLocation.toVec3D(newExit), ++ Vec3.ZERO, ++ newExit.getYaw(), ++ newExit.getPitch(), ++ teleportTransition.missingRespawnBlock(), ++ teleportTransition.asPassenger(), ++ Set.of(), ++ teleportTransition.postTeleportTransition(), ++ teleportTransition.cause()); ++ } ++ // CraftBukkit end + if (!teleportTransition.asPassenger()) { + this.stopRiding(); + } + +- if (level.dimension() == resourceKey) { +- this.connection.teleport(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives()); ++ // CraftBukkit start ++ if (level != null && level.dimension() == serverLevel.dimension()) { ++ this.connection.internalTeleport(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives()); ++ // CraftBukkit end + this.connection.resetPosition(); + teleportTransition.postTeleportTransition().onTransition(this); + return this; + } else { ++ // CraftBukkit start ++ /* + this.isChangingDimension = true; + LevelData levelData = level.getLevelData(); + this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(level), (byte)3)); +@@ -1050,16 +_,30 @@ + playerList.sendPlayerPermissionLevel(this); + serverLevel.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); + this.unsetRemoved(); ++ */ ++ // CraftBukkit end + ProfilerFiller profilerFiller = Profiler.get(); + profilerFiller.push("moving"); +- if (resourceKey == Level.OVERWORLD && level.dimension() == Level.NETHER) { ++ if (level != null && resourceKey == LevelStem.OVERWORLD && level.getTypeKey() == LevelStem.NETHER) { // CraftBukkit - empty to fall through to null to event + this.enteredNetherPosition = this.position(); + } + + profilerFiller.pop(); + profilerFiller.push("placing"); ++ // CraftBukkit start ++ this.isChangingDimension = true; // CraftBukkit - Set teleport invulnerability only if player changing worlds ++ LevelData worlddata = level.getLevelData(); ++ ++ this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(level), (byte) 3)); ++ this.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); ++ PlayerList playerList = this.server.getPlayerList(); ++ ++ playerList.sendPlayerPermissionLevel(this); ++ serverLevel.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); ++ this.unsetRemoved(); ++ // CraftBukkit end + this.setServerLevel(level); +- this.connection.teleport(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives()); ++ this.connection.internalTeleport(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives()); // CraftBukkit - use internal teleport without event + this.connection.resetPosition(); + level.addDuringTeleport(this); + profilerFiller.pop(); +@@ -1073,10 +_,40 @@ + this.lastSentExp = -1; + this.lastSentHealth = -1.0F; + this.lastSentFood = -1; ++ ++ ++ // CraftBukkit start ++ org.bukkit.event.player.PlayerChangedWorldEvent changeEvent = new org.bukkit.event.player.PlayerChangedWorldEvent(this.getBukkitEntity(), serverLevel.getWorld()); ++ this.level().getCraftServer().getPluginManager().callEvent(changeEvent); ++ // CraftBukkit end ++ // Paper start - Reset shield blocking on dimension change ++ if (this.isBlocking()) { ++ this.stopUsingItem(); ++ } ++ // Paper end - Reset shield blocking on dimension change + return this; + } + } + } ++ ++ // CraftBukkit start ++ @Override ++ public org.bukkit.craftbukkit.event.CraftPortalEvent callPortalEvent( ++ Entity entity, ++ org.bukkit.Location exit, ++ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, ++ int searchRadius, ++ int creationRadius ++ ) { ++ org.bukkit.Location enter = this.getBukkitEntity().getLocation(); ++ org.bukkit.event.player.PlayerPortalEvent event = new org.bukkit.event.player.PlayerPortalEvent(this.getBukkitEntity(), enter, exit, cause, searchRadius, true, creationRadius); ++ org.bukkit.Bukkit.getServer().getPluginManager().callEvent(event); ++ if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null) { ++ return null; ++ } ++ return new org.bukkit.craftbukkit.event.CraftPortalEvent(event); ++ } ++ // CraftBukkit end + + @Override + public void forceSetRotation(float yRot, float xRot) { +@@ -1086,12 +_,26 @@ + public void triggerDimensionChangeTriggers(ServerLevel level) { + ResourceKey resourceKey = level.dimension(); + ResourceKey resourceKey1 = this.level().dimension(); +- CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourceKey, resourceKey1); +- if (resourceKey == Level.NETHER && resourceKey1 == Level.OVERWORLD && this.enteredNetherPosition != null) { ++ // CraftBukkit start ++ ResourceKey maindimensionkey = org.bukkit.craftbukkit.util.CraftDimensionUtil.getMainDimensionKey(level); ++ ResourceKey maindimensionkey1 = org.bukkit.craftbukkit.util.CraftDimensionUtil.getMainDimensionKey(this.level()); ++ // Paper start - Add option for strict advancement dimension checks ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck) { ++ maindimensionkey = resourceKey; ++ maindimensionkey1 = resourceKey1; ++ } ++ // Paper end - Add option for strict advancement dimension checks ++ CriteriaTriggers.CHANGED_DIMENSION.trigger(this, maindimensionkey, maindimensionkey1); ++ if (maindimensionkey != resourceKey || maindimensionkey1 != resourceKey1) { ++ CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourceKey, resourceKey1); ++ } ++ ++ if (maindimensionkey == Level.NETHER && maindimensionkey1 == Level.OVERWORLD && this.enteredNetherPosition != null) { ++ // CraftBukkit end + CriteriaTriggers.NETHER_TRAVEL.trigger(this, this.enteredNetherPosition); + } + +- if (resourceKey1 != Level.NETHER) { ++ if (maindimensionkey1 != Level.NETHER) { // CraftBukkit + this.enteredNetherPosition = null; + } + } +@@ -1110,16 +_,21 @@ + @Override + public Either startSleepInBed(BlockPos at) { + Direction direction = this.level().getBlockState(at).getValue(HorizontalDirectionalBlock.FACING); ++ // CraftBukkit start - moved bed result checks from below into separate method ++ return getBedResult(at, direction); ++ } ++ private Either getBedResult(BlockPos at, Direction direction) { ++ // CraftBukkit end - moved bed result checks from below into separate method + if (this.isSleeping() || !this.isAlive()) { + return Either.left(Player.BedSleepingProblem.OTHER_PROBLEM); +- } else if (!this.level().dimensionType().natural()) { ++ } else if (!this.level().dimensionType().natural() && !this.level().dimensionType().bedWorks()) { // CraftBukkit - moved bed result checks from below into separate method + return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_HERE); + } else if (!this.bedInRange(at, direction)) { + return Either.left(Player.BedSleepingProblem.TOO_FAR_AWAY); + } else if (this.bedBlocked(at, direction)) { + return Either.left(Player.BedSleepingProblem.OBSTRUCTED); + } else { +- this.setRespawnPosition(this.level().dimension(), at, this.getYRot(), false, true); ++ this.setRespawnPosition(this.level().dimension(), at, this.getYRot(), false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.BED); // Paper - Add PlayerSetSpawnEvent + if (this.level().isDay()) { + return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_NOW); + } else { +@@ -1138,7 +_,34 @@ + } + } + +- Either either = super.startSleepInBed(at).ifRight(unit -> { ++ // CraftBukkit start ++ return Either.right(Unit.INSTANCE); ++ } ++ } ++ } ++ ++ @Override ++ public Either startSleepInBed(BlockPos at, boolean force) { ++ Direction enumdirection = (Direction) this.level().getBlockState(at).getValue(HorizontalDirectionalBlock.FACING); ++ Either bedResult = this.getBedResult(at, enumdirection); ++ ++ if (bedResult.left().orElse(null) == net.minecraft.world.entity.player.Player.BedSleepingProblem.OTHER_PROBLEM) { ++ return bedResult; // return immediately if the result is not bypassable by plugins ++ } ++ ++ if (force) { ++ bedResult = Either.right(Unit.INSTANCE); ++ } ++ ++ bedResult = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBedEnterEvent(this, at, bedResult); ++ if (bedResult.left().isPresent()) { ++ return bedResult; ++ } ++ ++ { ++ { ++ Either either = super.startSleepInBed(at, force).ifRight((unit) -> { ++ // CraftBukkit end + this.awardStat(Stats.SLEEP_IN_BED); + CriteriaTriggers.SLEPT_IN_BED.trigger(this); + }); +@@ -1174,13 +_,31 @@ + + @Override + public void stopSleepInBed(boolean wakeImmediately, boolean updateLevelForSleepingPlayers) { ++ if (!this.isSleeping()) return; // CraftBukkit - Can't leave bed if not in one! ++ // CraftBukkit start - fire PlayerBedLeaveEvent ++ org.bukkit.craftbukkit.entity.CraftPlayer player = this.getBukkitEntity(); ++ BlockPos bedPosition = this.getSleepingPos().orElse(null); ++ ++ org.bukkit.block.Block bed; ++ if (bedPosition != null) { ++ bed = this.level().getWorld().getBlockAt(bedPosition.getX(), bedPosition.getY(), bedPosition.getZ()); ++ } else { ++ bed = this.level().getWorld().getBlockAt(player.getLocation()); ++ } ++ ++ org.bukkit.event.player.PlayerBedLeaveEvent event = new org.bukkit.event.player.PlayerBedLeaveEvent(player, bed, true); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + if (this.isSleeping()) { + this.serverLevel().getChunkSource().broadcastAndSend(this, new ClientboundAnimatePacket(this, 2)); + } + + super.stopSleepInBed(wakeImmediately, updateLevelForSleepingPlayers); + if (this.connection != null) { +- this.connection.teleport(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); ++ this.connection.teleport(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot(), org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.EXIT_BED); // CraftBukkit + } + } + +@@ -1192,9 +_,9 @@ + + @Override + public boolean isInvulnerableTo(ServerLevel level, DamageSource damageSource) { +- return super.isInvulnerableTo(level, damageSource) ++ return (super.isInvulnerableTo(level, damageSource) // Paper - disable player cramming; + || this.isChangingDimension() && !damageSource.is(DamageTypes.ENDER_PEARL) +- || !this.hasClientLoaded(); ++ || !this.hasClientLoaded()) || (!this.level().paperConfig().collisions.allowPlayerCrammingDamage && damageSource.is(DamageTypes.CRAMMING)); // Paper - disable player cramming; + } + + @Override +@@ -1237,8 +_,9 @@ + this.connection.send(new ClientboundOpenSignEditorPacket(signEntity.getBlockPos(), isFrontText)); + } + +- public void nextContainerCounter() { ++ public int nextContainerCounter() { // CraftBukkit - void -> int + this.containerCounter = this.containerCounter % 100 + 1; ++ return this.containerCounter; // CraftBukkit + } + + @Override +@@ -1246,12 +_,43 @@ + if (menu == null) { + return OptionalInt.empty(); + } else { ++ // CraftBukkit start - SPIGOT-6552: Handle inventory closing in CraftEventFactory#callInventoryOpenEvent(...) ++ /* + if (this.containerMenu != this.inventoryMenu) { + this.closeContainer(); + } ++ */ ++ // CraftBukkit end + + this.nextContainerCounter(); + AbstractContainerMenu abstractContainerMenu = menu.createMenu(this.containerCounter, this.getInventory(), this); ++ Component title = null; // Paper - Add titleOverride to InventoryOpenEvent ++ // CraftBukkit start - Inventory open hook ++ if (abstractContainerMenu != null) { ++ abstractContainerMenu.setTitle(menu.getDisplayName()); ++ ++ boolean cancelled = false; ++ // Paper start - Add titleOverride to InventoryOpenEvent ++ final com.mojang.datafixers.util.Pair result = org.bukkit.craftbukkit.event.CraftEventFactory.callInventoryOpenEventWithTitle(this, abstractContainerMenu, cancelled); ++ abstractContainerMenu = result.getSecond(); ++ title = io.papermc.paper.adventure.PaperAdventure.asVanilla(result.getFirst()); ++ // Paper end - Add titleOverride to InventoryOpenEvent ++ if (abstractContainerMenu == null && !cancelled) { // Let pre-cancelled events fall through ++ // SPIGOT-5263 - close chest if cancelled ++ if (menu instanceof Container) { ++ ((Container) menu).stopOpen(this); ++ } else if (menu instanceof ChestBlock.DoubleInventory) { ++ // SPIGOT-5355 - double chests too :( ++ ((ChestBlock.DoubleInventory) menu).inventorylargechest.stopOpen(this); ++ // Paper start - Fix InventoryOpenEvent cancellation ++ } else if (!this.enderChestInventory.isActiveChest(null)) { ++ this.enderChestInventory.stopOpen(this); ++ // Paper end - Fix InventoryOpenEvent cancellation ++ } ++ return OptionalInt.empty(); ++ } ++ } ++ // CraftBukkit end + if (abstractContainerMenu == null) { + if (this.isSpectator()) { + this.displayClientMessage(Component.translatable("container.spectatorCantOpen").withStyle(ChatFormatting.RED), true); +@@ -1259,10 +_,14 @@ + + return OptionalInt.empty(); + } else { ++ // CraftBukkit start ++ this.containerMenu = abstractContainerMenu; // Moved up ++ if (!this.isImmobile()) + this.connection +- .send(new ClientboundOpenScreenPacket(abstractContainerMenu.containerId, abstractContainerMenu.getType(), menu.getDisplayName())); ++ .send(new net.minecraft.network.protocol.game.ClientboundOpenScreenPacket(abstractContainerMenu.containerId, abstractContainerMenu.getType(), java.util.Objects.requireNonNullElseGet(title, abstractContainerMenu::getTitle))); // Paper - Add titleOverride to InventoryOpenEven ++ // CraftBukkit end + this.initMenu(abstractContainerMenu); +- this.containerMenu = abstractContainerMenu; ++ // CraftBukkit - moved up + return OptionalInt.of(this.containerCounter); + } + } +@@ -1275,14 +_,25 @@ + + @Override + public void openHorseInventory(AbstractHorse horse, Container inventory) { +- if (this.containerMenu != this.inventoryMenu) { +- this.closeContainer(); +- } +- ++ // CraftBukkit start - Inventory open hook + this.nextContainerCounter(); ++ AbstractContainerMenu container = new HorseInventoryMenu(this.containerCounter, this.getInventory(), inventory, horse, horse.getInventoryColumns()); ++ container.setTitle(horse.getDisplayName()); ++ container = org.bukkit.craftbukkit.event.CraftEventFactory.callInventoryOpenEvent(this, container); ++ ++ if (container == null) { ++ inventory.stopOpen(this); ++ return; ++ } ++ // CraftBukkit end ++ if (this.containerMenu != this.inventoryMenu) { ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason ++ } ++ ++ // this.nextContainerCounter(); // CraftBukkit - moved up + int inventoryColumns = horse.getInventoryColumns(); + this.connection.send(new ClientboundHorseScreenOpenPacket(this.containerCounter, inventoryColumns, horse.getId())); +- this.containerMenu = new HorseInventoryMenu(this.containerCounter, this.getInventory(), inventory, horse, inventoryColumns); ++ this.containerMenu = container; // CraftBukkit + this.initMenu(this.containerMenu); + } + +@@ -1304,9 +_,28 @@ + + @Override + public void closeContainer() { ++ // Paper start - Inventory close reason ++ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN); ++ } ++ @Override ++ public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit ++ // Paper end - Inventory close reason + this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); + this.doCloseContainer(); + } ++ // Paper start - special close for unloaded inventory ++ @Override ++ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { ++ // copied from above ++ org.bukkit.craftbukkit.event.CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit ++ // Paper end ++ // copied from below ++ this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); ++ this.containerMenu = this.inventoryMenu; ++ // do not run close logic ++ } ++ // Paper end - special close for unloaded inventory + + @Override + public void doCloseContainer() { +@@ -1330,19 +_,19 @@ + int rounded = Math.round((float)Math.sqrt(dx * dx + dy * dy + dz * dz) * 100.0F); + if (rounded > 0) { + this.awardStat(Stats.SWIM_ONE_CM, rounded); +- this.causeFoodExhaustion(0.01F * rounded * 0.01F); ++ this.causeFoodExhaustion(this.level().spigotConfig.swimMultiplier * (float) rounded * 0.01F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.SWIM); // CraftBukkit - EntityExhaustionEvent // Spigot + } + } else if (this.isEyeInFluid(FluidTags.WATER)) { + int rounded = Math.round((float)Math.sqrt(dx * dx + dy * dy + dz * dz) * 100.0F); + if (rounded > 0) { + this.awardStat(Stats.WALK_UNDER_WATER_ONE_CM, rounded); +- this.causeFoodExhaustion(0.01F * rounded * 0.01F); ++ this.causeFoodExhaustion(this.level().spigotConfig.swimMultiplier * (float) rounded * 0.01F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.WALK_UNDERWATER); // CraftBukkit - EntityExhaustionEvent // Spigot + } + } else if (this.isInWater()) { + int rounded = Math.round((float)Math.sqrt(dx * dx + dz * dz) * 100.0F); + if (rounded > 0) { + this.awardStat(Stats.WALK_ON_WATER_ONE_CM, rounded); +- this.causeFoodExhaustion(0.01F * rounded * 0.01F); ++ this.causeFoodExhaustion(this.level().spigotConfig.swimMultiplier * (float) rounded * 0.01F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.WALK_ON_WATER); // CraftBukkit - EntityExhaustionEvent // Spigot + } + } else if (this.onClimbable()) { + if (dy > 0.0) { +@@ -1353,13 +_,13 @@ + if (rounded > 0) { + if (this.isSprinting()) { + this.awardStat(Stats.SPRINT_ONE_CM, rounded); +- this.causeFoodExhaustion(0.1F * rounded * 0.01F); ++ this.causeFoodExhaustion(this.level().spigotConfig.sprintMultiplier * (float) rounded * 0.01F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.SPRINT); // CraftBukkit - EntityExhaustionEvent // Spigot + } else if (this.isCrouching()) { + this.awardStat(Stats.CROUCH_ONE_CM, rounded); +- this.causeFoodExhaustion(0.0F * rounded * 0.01F); ++ this.causeFoodExhaustion(this.level().spigotConfig.otherMultiplier * (float) rounded * 0.01F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.CROUCH); // CraftBukkit - EntityExhaustionEvent // Spigot + } else { + this.awardStat(Stats.WALK_ONE_CM, rounded); +- this.causeFoodExhaustion(0.0F * rounded * 0.01F); ++ this.causeFoodExhaustion(this.level().spigotConfig.otherMultiplier * (float) rounded * 0.01F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.WALK); // CraftBukkit - EntityExhaustionEvent // Spigot + } + } + } else if (this.isFallFlying()) { +@@ -1399,13 +_,13 @@ + @Override + public void awardStat(Stat stat, int amount) { + this.stats.increment(this, stat, amount); +- this.getScoreboard().forAllObjectives(stat, this, score -> score.add(amount)); ++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(stat, this, score -> score.add(amount)); // CraftBukkit - Get our scores instead + } + + @Override + public void resetStat(Stat stat) { + this.stats.setValue(this, stat, 0); +- this.getScoreboard().forAllObjectives(stat, this, ScoreAccess::reset); ++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(stat, this, ScoreAccess::reset); // CraftBukkit - Get our scores instead + } + + @Override +@@ -1436,9 +_,9 @@ + super.jumpFromGround(); + this.awardStat(Stats.JUMP); + if (this.isSprinting()) { +- this.causeFoodExhaustion(0.2F); ++ this.causeFoodExhaustion(this.level().spigotConfig.jumpSprintExhaustion, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.JUMP_SPRINT); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value + } else { +- this.causeFoodExhaustion(0.05F); ++ this.causeFoodExhaustion(this.level().spigotConfig.jumpWalkExhaustion, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.JUMP); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value + } + } + +@@ -1451,6 +_,13 @@ + public void disconnect() { + this.disconnected = true; + this.ejectPassengers(); ++ ++ // Paper start - Workaround vehicle not tracking the passenger disconnection dismount ++ if (this.isPassenger() && this.getVehicle() instanceof ServerPlayer) { ++ this.stopRiding(); ++ } ++ // Paper end - Workaround vehicle not tracking the passenger disconnection dismount ++ + if (this.isSleeping()) { + this.stopSleepInBed(true, false); + } +@@ -1462,6 +_,7 @@ + + public void resetSentInfo() { + this.lastSentHealth = -1.0E8F; ++ this.lastSentExp = -1; // CraftBukkit - Added to reset + } + + @Override +@@ -1496,12 +_,12 @@ + this.onUpdateAbilities(); + if (keepEverything) { + this.getAttributes().assignBaseValues(that.getAttributes()); +- this.getAttributes().assignPermanentModifiers(that.getAttributes()); ++ // this.getAttributes().assignPermanentModifiers(that.getAttributes()); // CraftBukkit + this.setHealth(that.getHealth()); + this.foodData = that.foodData; + + for (MobEffectInstance mobEffectInstance : that.getActiveEffects()) { +- this.addEffect(new MobEffectInstance(mobEffectInstance)); ++ // this.addEffect(new MobEffectInstance(mobEffectInstance)); // CraftBukkit + } + + this.getInventory().replaceWith(that.getInventory()); +@@ -1512,7 +_,7 @@ + this.portalProcess = that.portalProcess; + } else { + this.getAttributes().assignBaseValues(that.getAttributes()); +- this.setHealth(this.getMaxHealth()); ++ // this.setHealth(this.getMaxHealth()); // CraftBukkit + if (this.serverLevel().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || that.isSpectator()) { + this.getInventory().replaceWith(that.getInventory()); + this.experienceLevel = that.experienceLevel; +@@ -1528,7 +_,7 @@ + this.lastSentExp = -1; + this.lastSentHealth = -1.0F; + this.lastSentFood = -1; +- this.recipeBook.copyOverData(that.recipeBook); ++ // this.recipeBook.copyOverData(that.recipeBook); // CraftBukkit + this.seenCredits = that.seenCredits; + this.enteredNetherPosition = that.enteredNetherPosition; + this.chunkTrackingView = that.chunkTrackingView; +@@ -1581,7 +_,7 @@ + } + + @Override +- public boolean teleportTo(ServerLevel level, double x, double y, double z, Set relativeMovements, float yaw, float pitch, boolean setCamera) { ++ public boolean teleportTo(ServerLevel level, double x, double y, double z, Set relativeMovements, float yaw, float pitch, boolean setCamera, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { // CraftBukkit + if (this.isSleeping()) { + this.stopSleepInBed(true, true); + } +@@ -1590,7 +_,7 @@ + this.setCamera(this); + } + +- boolean flag = super.teleportTo(level, x, y, z, relativeMovements, yaw, pitch, setCamera); ++ boolean flag = super.teleportTo(level, x, y, z, relativeMovements, yaw, pitch, setCamera, cause); // CraftBukkit + if (flag) { + this.setYHeadRot(relativeMovements.contains(Relative.Y_ROT) ? this.getYHeadRot() + yaw : yaw); + } +@@ -1627,9 +_,17 @@ + } + + public boolean setGameMode(GameType gameMode) { ++ // Paper start - Expand PlayerGameModeChangeEvent ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); ++ return event == null ? false : event.isCancelled(); ++ } ++ @Nullable ++ public org.bukkit.event.player.PlayerGameModeChangeEvent setGameMode(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component message) { + boolean isSpectator = this.isSpectator(); +- if (!this.gameMode.changeGameModeForPlayer(gameMode)) { +- return false; ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.gameMode.changeGameModeForPlayer(gameMode, cause, message); ++ if (event == null || event.isCancelled()) { ++ return null; ++ // Paper end - Expand PlayerGameModeChangeEvent + } else { + this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, gameMode.getId())); + if (gameMode == GameType.SPECTATOR) { +@@ -1645,7 +_,7 @@ + + this.onUpdateAbilities(); + this.updateEffectVisibility(); +- return true; ++ return event; // Paper - Expand PlayerGameModeChangeEvent + } + } + +@@ -1705,8 +_,13 @@ + } + + public void sendChatMessage(OutgoingChatMessage message, boolean filtered, ChatType.Bound boundType) { ++ // Paper start ++ this.sendChatMessage(message, filtered, boundType, null); ++ } ++ public void sendChatMessage(OutgoingChatMessage message, boolean filtered, ChatType.Bound boundType, @Nullable Component unsigned) { ++ // Paper end + if (this.acceptsChatMessages()) { +- message.sendToPlayer(this, filtered, boundType); ++ message.sendToPlayer(this, filtered, boundType, unsigned); // Paper + } + } + +@@ -1717,7 +_,42 @@ + } + + public void updateOptions(ClientInformation clientInformation) { ++ // Paper start - settings event ++ new com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent(this.getBukkitEntity(), Util.make(new java.util.IdentityHashMap<>(), map -> { ++ map.put(com.destroystokyo.paper.ClientOption.LOCALE, clientInformation.language()); ++ map.put(com.destroystokyo.paper.ClientOption.VIEW_DISTANCE, clientInformation.viewDistance()); ++ map.put(com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY, com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(clientInformation.chatVisibility().name())); ++ map.put(com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED, clientInformation.chatColors()); ++ map.put(com.destroystokyo.paper.ClientOption.SKIN_PARTS, new com.destroystokyo.paper.PaperSkinParts(clientInformation.modelCustomisation())); ++ map.put(com.destroystokyo.paper.ClientOption.MAIN_HAND, clientInformation.mainHand() == HumanoidArm.LEFT ? org.bukkit.inventory.MainHand.LEFT : org.bukkit.inventory.MainHand.RIGHT); ++ map.put(com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED, clientInformation.textFilteringEnabled()); ++ map.put(com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS, clientInformation.allowsListing()); ++ map.put(com.destroystokyo.paper.ClientOption.PARTICLE_VISIBILITY, com.destroystokyo.paper.ClientOption.ParticleVisibility.valueOf(clientInformation.particleStatus().name())); ++ })).callEvent(); ++ // Paper end - settings event ++ // CraftBukkit start ++ if (this.getMainArm() != clientInformation.mainHand()) { ++ org.bukkit.event.player.PlayerChangedMainHandEvent event = new org.bukkit.event.player.PlayerChangedMainHandEvent( ++ this.getBukkitEntity(), ++ this.getMainArm() == HumanoidArm.LEFT ? org.bukkit.inventory.MainHand.LEFT : org.bukkit.inventory.MainHand.RIGHT ++ ); ++ this.server.server.getPluginManager().callEvent(event); ++ } ++ if (this.language == null || !this.language.equals(clientInformation.language())) { // Paper ++ org.bukkit.event.player.PlayerLocaleChangeEvent event = new org.bukkit.event.player.PlayerLocaleChangeEvent( ++ this.getBukkitEntity(), ++ clientInformation.language() ++ ); ++ this.server.server.getPluginManager().callEvent(event); ++ } ++ // CraftBukkit end ++ // Paper start - don't call options events on login ++ this.updateOptionsNoEvents(clientInformation); ++ } ++ public void updateOptionsNoEvents(ClientInformation clientInformation) { ++ // Paper end + this.language = clientInformation.language(); ++ this.adventure$locale = java.util.Objects.requireNonNullElse(net.kyori.adventure.translation.Translator.parseLocale(this.language), java.util.Locale.US); // Paper + this.requestedViewDistance = clientInformation.viewDistance(); + this.chatVisibility = clientInformation.chatVisibility(); + this.canChatColor = clientInformation.chatColors(); +@@ -1803,8 +_,23 @@ + Entity camera = this.getCamera(); + this.camera = (Entity)(entityToSpectate == null ? this : entityToSpectate); + if (camera != this.camera) { ++ // Paper start - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity ++ if (this.camera == this) { ++ com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent playerStopSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent(this.getBukkitEntity(), camera.getBukkitEntity()); ++ if (!playerStopSpectatingEntityEvent.callEvent()) { ++ this.camera = camera; // rollback camera entity again ++ return; ++ } ++ } else { ++ com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent playerStartSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent(this.getBukkitEntity(), camera.getBukkitEntity(), entityToSpectate.getBukkitEntity()); ++ if (!playerStartSpectatingEntityEvent.callEvent()) { ++ this.camera = camera; // rollback camera entity again ++ return; ++ } ++ } ++ // Paper end - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity + if (this.camera.level() instanceof ServerLevel serverLevel) { +- this.teleportTo(serverLevel, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), false); ++ this.teleportTo(serverLevel, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), false, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit + } + + if (entityToSpectate != null) { +@@ -1838,11 +_,11 @@ + + @Nullable + public Component getTabListDisplayName() { +- return null; ++ return this.listName; // CraftBukkit + } + + public int getTabListOrder() { +- return 0; ++ return this.listOrder; // CraftBukkit + } + + @Override +@@ -1884,11 +_,56 @@ + this.setRespawnPosition(player.getRespawnDimension(), player.getRespawnPosition(), player.getRespawnAngle(), player.isRespawnForced(), false); + } + ++ @Deprecated // Paper - Add PlayerSetSpawnEvent + public void setRespawnPosition(ResourceKey dimension, @Nullable BlockPos position, float angle, boolean forced, boolean sendMessage) { ++ // Paper start - Add PlayerSetSpawnEvent ++ this.setRespawnPosition(dimension, position, angle, forced, sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.UNKNOWN); ++ } ++ @Deprecated ++ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos position, float angle, boolean forced, boolean sendMessage, org.bukkit.event.player.PlayerSpawnChangeEvent.Cause cause) { ++ return this.setRespawnPosition(dimension, position, angle, forced, sendMessage, cause == org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.RESET ? ++ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN : com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.valueOf(cause.name())); ++ } ++ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos position, float angle, boolean forced, boolean sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause cause) { ++ org.bukkit.Location spawnLoc = null; ++ boolean willNotify = false; + if (position != null) { + boolean flag = position.equals(this.respawnPosition) && dimension.equals(this.respawnDimension); +- if (sendMessage && !flag) { +- this.sendSystemMessage(Component.translatable("block.minecraft.set_spawn")); ++ spawnLoc = io.papermc.paper.util.MCUtil.toLocation(this.getServer().getLevel(dimension), position); ++ spawnLoc.setYaw(angle); ++ willNotify = sendMessage && !flag; ++ } ++ org.bukkit.event.player.PlayerSpawnChangeEvent dumbEvent = new org.bukkit.event.player.PlayerSpawnChangeEvent( ++ this.getBukkitEntity(), ++ spawnLoc, ++ forced, ++ cause == com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN ++ ? org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.RESET ++ : org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.valueOf(cause.name()) ++ ); ++ dumbEvent.callEvent(); ++ ++ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent event = new com.destroystokyo.paper.event.player.PlayerSetSpawnEvent( ++ this.getBukkitEntity(), ++ cause, ++ dumbEvent.getNewSpawn(), ++ dumbEvent.isForced(), ++ willNotify, ++ willNotify ? net.kyori.adventure.text.Component.translatable("block.minecraft.set_spawn") : null ++ ); ++ event.setCancelled(dumbEvent.isCancelled()); ++ if (!event.callEvent()) { ++ return false; ++ } ++ if (event.getLocation() != null) { ++ dimension = event.getLocation().getWorld() != null ? ((org.bukkit.craftbukkit.CraftWorld) event.getLocation().getWorld()).getHandle().dimension() : dimension; ++ position = io.papermc.paper.util.MCUtil.toBlockPosition(event.getLocation()); ++ angle = event.getLocation().getYaw(); ++ forced = event.isForced(); ++ // Paper end - Add PlayerSetSpawnEvent ++ ++ if (event.willNotifyPlayer() && event.getNotification() != null) { // Paper - Add PlayerSetSpawnEvent ++ this.sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.getNotification())); // Paper - Add PlayerSetSpawnEvent + } + + this.respawnPosition = position; +@@ -1901,6 +_,8 @@ + this.respawnAngle = 0.0F; + this.respawnForced = false; + } ++ ++ return true; // Paper - Add PlayerSetSpawnEvent + } + + public SectionPos getLastSectionPos() { +@@ -1930,16 +_,41 @@ + } + + @Override +- public ItemEntity drop(ItemStack droppedItem, boolean dropAround, boolean traceItem) { ++ public ItemEntity drop(ItemStack droppedItem, boolean dropAround, boolean traceItem, boolean callEvent) { // CraftBukkit - SPIGOT-2942: Add boolean to call event + ItemEntity itemEntity = this.createItemStackToDrop(droppedItem, dropAround, traceItem); + if (itemEntity == null) { + return null; + } else { ++ // CraftBukkit start - fire PlayerDropItemEvent ++ if (callEvent) { ++ org.bukkit.entity.Player player = this.getBukkitEntity(); ++ org.bukkit.entity.Item drop = (org.bukkit.entity.Item) itemEntity.getBukkitEntity(); ++ ++ org.bukkit.event.player.PlayerDropItemEvent event = new org.bukkit.event.player.PlayerDropItemEvent(player, drop); ++ this.level().getCraftServer().getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ org.bukkit.inventory.ItemStack cur = player.getInventory().getItemInHand(); ++ if (traceItem && (cur == null || cur.getAmount() == 0)) { ++ // The complete stack was dropped ++ player.getInventory().setItemInHand(drop.getItemStack()); ++ } else if (traceItem && cur.isSimilar(drop.getItemStack()) && cur.getAmount() < cur.getMaxStackSize() && drop.getItemStack().getAmount() == 1) { ++ // Only one item is dropped ++ cur.setAmount(cur.getAmount() + 1); ++ player.getInventory().setItemInHand(cur); ++ } else { ++ // Fallback ++ player.getInventory().addItem(drop.getItemStack()); ++ } ++ return null; ++ } ++ } ++ // CraftBukkit end + this.level().addFreshEntity(itemEntity); + ItemStack item = itemEntity.getItem(); + if (traceItem) { + if (!item.isEmpty()) { +- this.awardStat(Stats.ITEM_DROPPED.get(item.getItem()), droppedItem.getCount()); ++ this.awardStat(Stats.ITEM_DROPPED.get(item.getItem()), item.getCount()); // Paper - Fix PlayerDropItemEvent using wrong item + } + + this.awardStat(Stats.DROP); +@@ -1955,6 +_,11 @@ + return null; + } else { + double d = this.getEyeY() - 0.3F; ++ // Paper start ++ ItemStack tmp = droppedItem.copy(); ++ tmp.setCount(0); ++ droppedItem = tmp; ++ // Paper end + ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), d, this.getZ(), droppedItem); + itemEntity.setPickUpDelay(40); + if (includeThrowerName) { +@@ -2008,6 +_,16 @@ + } + + public void loadGameTypes(@Nullable CompoundTag tag) { ++ // Paper start - Expand PlayerGameModeChangeEvent ++ if (this.server.getForcedGameType() != null && this.server.getForcedGameType() != ServerPlayer.readPlayerMode(tag, "playerGameType")) { ++ if (new org.bukkit.event.player.PlayerGameModeChangeEvent(this.getBukkitEntity(), org.bukkit.GameMode.getByValue(this.server.getDefaultGameType().getId()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null).callEvent()) { ++ this.gameMode.setGameModeForPlayer(this.server.getForcedGameType(), GameType.DEFAULT_MODE); ++ } else { ++ this.gameMode.setGameModeForPlayer(ServerPlayer.readPlayerMode(tag,"playerGameType"), ServerPlayer.readPlayerMode(tag, "previousPlayerGameType")); ++ } ++ return; ++ } ++ // Paper end - Expand PlayerGameModeChangeEvent + this.gameMode + .setGameModeForPlayer(this.calculateGameModeForNewPlayer(readPlayerMode(tag, "playerGameType")), readPlayerMode(tag, "previousPlayerGameType")); + } +@@ -2108,8 +_,14 @@ + + @Override + public void stopRiding() { ++ // Paper start - Force entity dismount during teleportation ++ this.stopRiding(false); ++ } ++ @Override ++ public void stopRiding(boolean suppressCancellation) { ++ // Paper end - Force entity dismount during teleportation + Entity vehicle = this.getVehicle(); +- super.stopRiding(); ++ super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation + if (vehicle instanceof LivingEntity livingEntity) { + for (MobEffectInstance mobEffectInstance : livingEntity.getActiveEffects()) { + this.connection.send(new ClientboundRemoveMobEffectPacket(vehicle.getId(), mobEffectInstance.getEffect())); +@@ -2204,13 +_,15 @@ + } + + public static long placeEnderPearlTicket(ServerLevel level, ChunkPos pos) { +- level.getChunkSource().addRegionTicket(TicketType.ENDER_PEARL, pos, 2, pos); ++ if (!level.paperConfig().misc.legacyEnderPearlBehavior) level.getChunkSource().addRegionTicket(TicketType.ENDER_PEARL, pos, 2, pos); // Paper - Allow using old ender pearl behavior + return TicketType.ENDER_PEARL.timeout(); + } + +- public record RespawnPosAngle(Vec3 position, float yaw) { +- public static ServerPlayer.RespawnPosAngle of(Vec3 position, BlockPos towardsPos) { +- return new ServerPlayer.RespawnPosAngle(position, calculateLookAtYaw(position, towardsPos)); ++ // CraftBukkit start ++ public record RespawnPosAngle(Vec3 position, float yaw, boolean isBedSpawn, boolean isAnchorSpawn) { ++ public static ServerPlayer.RespawnPosAngle of(Vec3 position, BlockPos towardsPos, boolean isBedSpawn, boolean isAnchorSpawn) { ++ return new ServerPlayer.RespawnPosAngle(position, calculateLookAtYaw(position, towardsPos), isBedSpawn, isAnchorSpawn); ++ // CraftBukkit end + } + + private static float calculateLookAtYaw(Vec3 position, BlockPos towardsPos) { +@@ -2218,4 +_,147 @@ + return (float)Mth.wrapDegrees(Mth.atan2(vec3.z, vec3.x) * 180.0F / (float)Math.PI - 90.0); + } + } ++ ++ // CraftBukkit start - Add per-player time and weather. ++ public long timeOffset = 0; ++ public boolean relativeTime = true; ++ ++ public long getPlayerTime() { ++ if (this.relativeTime) { ++ // Adds timeOffset to the current server time. ++ return this.level().getDayTime() + this.timeOffset; ++ } else { ++ // Adds timeOffset to the beginning of this day. ++ return this.level().getDayTime() - (this.level().getDayTime() % 24000) + this.timeOffset; ++ } ++ } ++ ++ public org.bukkit.WeatherType weather = null; ++ ++ public org.bukkit.WeatherType getPlayerWeather() { ++ return this.weather; ++ } ++ ++ public void setPlayerWeather(org.bukkit.WeatherType type, boolean plugin) { ++ if (!plugin && this.weather != null) { ++ return; ++ } ++ ++ if (plugin) { ++ this.weather = type; ++ } ++ ++ if (type == org.bukkit.WeatherType.DOWNFALL) { ++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.STOP_RAINING, 0)); ++ } else { ++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0)); ++ } ++ } ++ ++ private float pluginRainPosition; ++ private float pluginRainPositionPrevious; ++ ++ public void updateWeather(float oldRain, float newRain, float oldThunder, float newThunder) { ++ if (this.weather == null) { ++ // Vanilla ++ if (oldRain != newRain) { ++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, newRain)); ++ } ++ } else { ++ // Plugin ++ if (this.pluginRainPositionPrevious != this.pluginRainPosition) { ++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.pluginRainPosition)); ++ } ++ } ++ ++ if (oldThunder != newThunder) { ++ if (this.weather == org.bukkit.WeatherType.DOWNFALL || this.weather == null) { ++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, newThunder)); ++ } else { ++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 0)); ++ } ++ } ++ } ++ ++ public void tickWeather() { ++ if (this.weather == null) return; ++ ++ this.pluginRainPositionPrevious = this.pluginRainPosition; ++ if (this.weather == org.bukkit.WeatherType.DOWNFALL) { ++ this.pluginRainPosition += 0.01; ++ } else { ++ this.pluginRainPosition -= 0.01; ++ } ++ ++ this.pluginRainPosition = Mth.clamp(this.pluginRainPosition, 0.0F, 1.0F); ++ } ++ ++ public void resetPlayerWeather() { ++ this.weather = null; ++ this.setPlayerWeather(this.level().getLevelData().isRaining() ? org.bukkit.WeatherType.DOWNFALL : org.bukkit.WeatherType.CLEAR, false); ++ } ++ ++ @Override ++ public String toString() { ++ return super.toString() + "(" + this.getScoreboardName() + " at " + this.getX() + "," + this.getY() + "," + this.getZ() + ")"; ++ } ++ ++ // SPIGOT-1903, MC-98153 ++ public void forceSetPositionRotation(double x, double y, double z, float yaw, float pitch) { ++ this.moveTo(x, y, z, yaw, pitch); ++ this.connection.resetPosition(); ++ } ++ ++ @Override ++ public boolean isImmobile() { ++ return super.isImmobile() || (this.connection != null && this.connection.isDisconnected()); // Paper - Fix duplication bugs ++ } ++ ++ @Override ++ public net.minecraft.world.scores.Scoreboard getScoreboard() { ++ return this.getBukkitEntity().getScoreboard().getHandle(); ++ } ++ ++ public void reset() { ++ float exp = 0; ++ ++ if (this.keepLevel) { // CraftBukkit - SPIGOT-6687: Only use keepLevel (was pre-set with RULE_KEEPINVENTORY value in PlayerDeathEvent) ++ exp = this.experienceProgress; ++ this.newTotalExp = this.totalExperience; ++ this.newLevel = this.experienceLevel; ++ } ++ ++ this.setHealth(this.getMaxHealth()); ++ this.stopUsingItem(); // CraftBukkit - SPIGOT-6682: Clear active item on reset ++ this.setAirSupply(this.getMaxAirSupply()); // Paper - Reset players airTicks on respawn ++ this.setRemainingFireTicks(0); ++ this.fallDistance = 0; ++ this.foodData = new FoodData(); ++ this.experienceLevel = this.newLevel; ++ this.totalExperience = this.newTotalExp; ++ this.experienceProgress = 0; ++ this.deathTime = 0; ++ this.setArrowCount(0, true); // CraftBukkit - ArrowBodyCountChangeEvent ++ this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DEATH); ++ this.effectsDirty = true; ++ this.containerMenu = this.inventoryMenu; ++ this.lastHurtByPlayer = null; ++ this.lastHurtByMob = null; ++ this.combatTracker = new net.minecraft.world.damagesource.CombatTracker(this); ++ this.lastSentExp = -1; ++ if (this.keepLevel) { // CraftBukkit - SPIGOT-6687: Only use keepLevel (was pre-set with RULE_KEEPINVENTORY value in PlayerDeathEvent) ++ this.experienceProgress = exp; ++ } else { ++ this.giveExperiencePoints(this.newExp); ++ } ++ this.keepLevel = false; ++ this.setDeltaMovement(0, 0, 0); // CraftBukkit - SPIGOT-6948: Reset velocity on death ++ this.skipDropExperience = false; // CraftBukkit - SPIGOT-7462: Reset experience drop skip, so that further deaths drop xp ++ } ++ ++ @Override ++ public org.bukkit.craftbukkit.entity.CraftPlayer getBukkitEntity() { ++ return (org.bukkit.craftbukkit.entity.CraftPlayer) super.getBukkitEntity(); ++ } ++ // CraftBukkit end + } diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/ServerPlayerGameMode.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch similarity index 63% rename from paper-server/patches/unapplied/net/minecraft/server/level/ServerPlayerGameMode.java.patch rename to paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch index aba7ebeae1..eac24793a9 100644 --- a/paper-server/patches/unapplied/net/minecraft/server/level/ServerPlayerGameMode.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayerGameMode.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/server/level/ServerPlayerGameMode.java +++ b/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -13,6 +13,7 @@ +@@ -13,6 +_,7 @@ import net.minecraft.world.InteractionResult; import net.minecraft.world.MenuProvider; import net.minecraft.world.entity.EquipmentSlot; @@ -8,75 +8,51 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.item.enchantment.EnchantmentHelper; -@@ -20,12 +21,30 @@ - import net.minecraft.world.level.Level; - import net.minecraft.world.level.block.Block; - import net.minecraft.world.level.block.GameMasterBlock; -+import net.minecraft.world.level.block.TrapDoorBlock; - import net.minecraft.world.level.block.entity.BlockEntity; - import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; - import net.minecraft.world.phys.BlockHitResult; - import net.minecraft.world.phys.Vec3; - import org.slf4j.Logger; - -+// CraftBukkit start -+import java.util.ArrayList; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.CakeBlock; -+import net.minecraft.world.level.block.DoorBlock; -+import org.bukkit.GameMode; -+import org.bukkit.craftbukkit.block.CraftBlock; -+import org.bukkit.event.block.BlockBreakEvent; -+import org.bukkit.craftbukkit.event.CraftEventFactory; -+import org.bukkit.event.Event; -+import org.bukkit.event.block.Action; -+import org.bukkit.event.player.PlayerGameModeChangeEvent; -+import org.bukkit.event.player.PlayerInteractEvent; -+// CraftBukkit end -+ - public class ServerPlayerGameMode { - - private static final Logger LOGGER = LogUtils.getLogger(); -@@ -42,6 +61,8 @@ - private BlockPos delayedDestroyPos; +@@ -41,6 +_,8 @@ + private BlockPos delayedDestroyPos = BlockPos.ZERO; private int delayedTickStart; - private int lastSentState; + private int lastSentState = -1; + public boolean captureSentBlockEntities = false; // Paper - Send block entities after destroy prediction + public boolean capturedBlockEntity = false; // Paper - Send block entities after destroy prediction public ServerPlayerGameMode(ServerPlayer player) { - this.gameModeForPlayer = GameType.DEFAULT_MODE; -@@ -53,18 +74,32 @@ + this.player = player; +@@ -48,21 +_,39 @@ } - public boolean changeGameModeForPlayer(GameType gameMode) { + public boolean changeGameModeForPlayer(GameType gameModeForPlayer) { + // Paper start - Expand PlayerGameModeChangeEvent -+ PlayerGameModeChangeEvent event = this.changeGameModeForPlayer(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.changeGameModeForPlayer(gameModeForPlayer, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); + return event != null && event.isCancelled(); + } + @Nullable -+ public PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component cancelMessage) { ++ public org.bukkit.event.player.PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause playerGameModeChangeCause, @Nullable net.kyori.adventure.text.Component cancelMessage) { + // Paper end - Expand PlayerGameModeChangeEvent - if (gameMode == this.gameModeForPlayer) { + if (gameModeForPlayer == this.gameModeForPlayer) { - return false; + return null; // Paper - Expand PlayerGameModeChangeEvent } else { -- this.setGameModeForPlayer(gameMode, this.previousGameModeForPlayer); +- this.setGameModeForPlayer(gameModeForPlayer, this.previousGameModeForPlayer); + // CraftBukkit start -+ PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId()), cause, cancelMessage); // Paper -+ this.level.getCraftServer().getPluginManager().callEvent(event); -+ if (event.isCancelled()) { ++ org.bukkit.event.player.PlayerGameModeChangeEvent event = new org.bukkit.event.player.PlayerGameModeChangeEvent( ++ this.player.getBukkitEntity(), ++ org.bukkit.GameMode.getByValue(gameMode.getId()), ++ playerGameModeChangeCause, // Paper ++ cancelMessage ++ ); ++ if (!event.callEvent()) { + return event; // Paper - Expand PlayerGameModeChangeEvent + } + // CraftBukkit end + this.setGameModeForPlayer(gameMode, this.gameModeForPlayer); // Paper - Fix MC-259571 this.player.onUpdateAbilities(); -- this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player)); -+ this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player), this.player); // CraftBukkit + this.player + .server + .getPlayerList() +- .broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player)); ++ .broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player), this.player); // CraftBukkit this.level.updateSleepingPlayerList(); - if (gameMode == GameType.CREATIVE) { + if (gameModeForPlayer == GameType.CREATIVE) { this.player.resetCurrentImpulseContext(); } @@ -85,61 +61,61 @@ } } -@@ -92,12 +127,12 @@ +@@ -90,10 +_,10 @@ } public void tick() { -- ++this.gameTicks; -+ this.gameTicks = MinecraftServer.currentTick; // CraftBukkit; - BlockState iblockdata; - +- this.gameTicks++; ++ this.gameTicks = net.minecraft.server.MinecraftServer.currentTick; // CraftBukkit; if (this.hasDelayedDestroy) { -- iblockdata = this.level.getBlockState(this.delayedDestroyPos); -- if (iblockdata.isAir()) { -+ iblockdata = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks -+ if (iblockdata == null || iblockdata.isAir()) { // Paper - Don't allow digging into unloaded chunks +- BlockState blockState = this.level.getBlockState(this.delayedDestroyPos); +- if (blockState.isAir()) { ++ BlockState blockState = this.level.getBlockStateIfLoaded(this.delayedDestroyPos); // Paper - Don't allow digging into unloaded chunks ++ if (blockState == null || blockState.isAir()) { // Paper - Don't allow digging into unloaded chunks this.hasDelayedDestroy = false; } else { - float f = this.incrementDestroyProgress(iblockdata, this.delayedDestroyPos, this.delayedTickStart); -@@ -108,7 +143,13 @@ + float f = this.incrementDestroyProgress(blockState, this.delayedDestroyPos, this.delayedTickStart); +@@ -103,7 +_,13 @@ } } } else if (this.isDestroyingBlock) { -- iblockdata = this.level.getBlockState(this.destroyPos); +- BlockState blockState = this.level.getBlockState(this.destroyPos); + // Paper start - Don't allow digging into unloaded chunks; don't want to do same logic as above, return instead -+ iblockdata = this.level.getBlockStateIfLoaded(this.destroyPos); -+ if (iblockdata == null) { ++ BlockState blockState = this.level.getBlockStateIfLoaded(this.destroyPos); ++ if (blockState == null) { + this.isDestroyingBlock = false; + return; + } + // Paper end - Don't allow digging into unloaded chunks - if (iblockdata.isAir()) { + if (blockState.isAir()) { this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); this.lastSentState = -1; -@@ -137,6 +178,7 @@ +@@ -131,6 +_,7 @@ - public void handleBlockBreakAction(BlockPos pos, ServerboundPlayerActionPacket.Action action, Direction direction, int worldHeight, int sequence) { - if (!this.player.canInteractWithBlock(pos, 1.0D)) { + public void handleBlockBreakAction(BlockPos pos, ServerboundPlayerActionPacket.Action action, Direction face, int maxBuildHeight, int sequence) { + if (!this.player.canInteractWithBlock(pos, 1.0)) { + if (true) return; // Paper - Don't allow digging into unloaded chunks; Don't notify if unreasonably far away this.debugLogging(pos, false, sequence, "too far"); - } else if (pos.getY() > worldHeight) { + } else if (pos.getY() > maxBuildHeight) { this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); -@@ -146,16 +188,40 @@ - +@@ -138,16 +_,40 @@ + } else { if (action == ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK) { if (!this.level.mayInteract(this.player, pos)) { + // CraftBukkit start - fire PlayerInteractEvent -+ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, direction, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); ++ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(this.player, org.bukkit.event.block.Action.LEFT_CLICK_BLOCK, pos, face, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); this.debugLogging(pos, false, sequence, "may not interact"); +- return; +- } + // Update any tile entity data for this block + capturedBlockEntity = true; // Paper - Send block entities after destroy prediction + // CraftBukkit end - return; - } - ++ return; ++ } ++ + // CraftBukkit start -+ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, direction, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); ++ org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(this.player, org.bukkit.event.block.Action.LEFT_CLICK_BLOCK, pos, face, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND); + if (event.isCancelled()) { + // Let the client know the block still exists + // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks @@ -148,7 +124,7 @@ + return; + } + // CraftBukkit end -+ + if (this.isCreative()) { this.destroyAndAck(pos, sequence, "creative destroy"); return; @@ -156,7 +132,7 @@ + // Spigot start - handle debug stick left click for non-creative + if (this.player.getMainHandItem().is(net.minecraft.world.item.Items.DEBUG_STICK) -+ && ((net.minecraft.world.item.DebugStickItem) net.minecraft.world.item.Items.DEBUG_STICK).handleInteraction(this.player, this.level.getBlockState(pos), this.level, pos, false, this.player.getMainHandItem())) { ++ && ((net.minecraft.world.item.DebugStickItem) net.minecraft.world.item.Items.DEBUG_STICK).handleInteraction(this.player, this.level.getBlockState(pos), this.level, pos, false, this.player.getMainHandItem())) { + // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync block + return; + } @@ -165,13 +141,13 @@ if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) { this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos))); this.debugLogging(pos, false, sequence, "block action restricted"); -@@ -166,7 +232,21 @@ +@@ -157,7 +_,21 @@ + this.destroyProgressStart = this.gameTicks; float f = 1.0F; - - iblockdata = this.level.getBlockState(pos); -- if (!iblockdata.isAir()) { + BlockState blockState = this.level.getBlockState(pos); +- if (!blockState.isAir()) { + // CraftBukkit start - Swings at air do *NOT* exist. -+ if (event.useInteractedBlock() == Event.Result.DENY) { ++ if (event.useInteractedBlock() == org.bukkit.event.Event.Result.DENY) { + // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door. + // Paper start - Don't resync blocks + //BlockState data = this.level.getBlockState(pos); @@ -184,22 +160,22 @@ + // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); + //} + // Paper end - Don't resync blocks -+ } else if (!iblockdata.isAir()) { - EnchantmentHelper.onHitBlock(this.level, this.player.getMainHandItem(), this.player, this.player, EquipmentSlot.MAINHAND, Vec3.atCenterOf(pos), iblockdata, (item) -> { - this.player.onEquippedItemBroken(item, EquipmentSlot.MAINHAND); - }); -@@ -174,6 +254,26 @@ - f = iblockdata.getDestroyProgress(this.player, this.player.level(), pos); ++ } else if (!blockState.isAir()) { + EnchantmentHelper.onHitBlock( + this.level, + this.player.getMainHandItem(), +@@ -172,6 +_,26 @@ + f = blockState.getDestroyProgress(this.player, this.player.level(), pos); } -+ if (event.useItemInHand() == Event.Result.DENY) { ++ if (event.useItemInHand() == org.bukkit.event.Event.Result.DENY) { + // If we 'insta destroyed' then the client needs to be informed. + if (f > 1.0f) { + // this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // Paper - Don't resync blocks + } + return; + } -+ org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos, direction, this.player.getInventory().getSelected(), f >= 1.0f); // Paper - Add BlockFace to BlockDamageEvent ++ org.bukkit.event.block.BlockDamageEvent blockEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockDamageEvent(this.player, pos, face, this.player.getInventory().getSelected(), f >= 1.0f); // Paper - Add BlockFace to BlockDamageEvent + + if (blockEvent.isCancelled()) { + // Let the client know the block still exists @@ -212,15 +188,15 @@ + } + // CraftBukkit end + - if (!iblockdata.isAir() && f >= 1.0F) { + if (!blockState.isAir() && f >= 1.0F) { this.destroyAndAck(pos, sequence, "insta mine"); } else { -@@ -217,14 +317,18 @@ +@@ -212,14 +_,18 @@ this.debugLogging(pos, true, sequence, "stopped destroying"); } else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) { this.isDestroyingBlock = false; - if (!Objects.equals(this.destroyPos, pos)) { -- ServerPlayerGameMode.LOGGER.warn("Mismatch in destroy block pos: {} {}", this.destroyPos, pos); +- LOGGER.warn("Mismatch in destroy block pos: {} {}", this.destroyPos, pos); - this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1); - this.debugLogging(pos, true, sequence, "aborted mismatched destroying"); + if (!Objects.equals(this.destroyPos, pos) && !BlockPos.ZERO.equals(this.destroyPos)) { // Paper @@ -234,31 +210,30 @@ this.level.destroyBlockProgress(this.player.getId(), pos, -1); this.debugLogging(pos, true, sequence, "aborted destroying"); + -+ CraftEventFactory.callBlockDamageAbortEvent(this.player, pos, this.player.getInventory().getSelected()); // CraftBukkit ++ org.bukkit.craftbukkit.event.CraftEventFactory.callBlockDamageAbortEvent(this.player, pos, this.player.getInventory().getSelected()); // CraftBukkit } - } -@@ -242,19 +346,82 @@ + } +@@ -235,36 +_,125 @@ public boolean destroyBlock(BlockPos pos) { - BlockState iblockdata = this.level.getBlockState(pos); + BlockState blockState = this.level.getBlockState(pos); +- if (!this.player.getMainHandItem().getItem().canAttackBlock(blockState, this.level, pos, this.player)) { + // CraftBukkit start - fire BlockBreakEvent -+ org.bukkit.block.Block bblock = CraftBlock.at(this.level, pos); -+ BlockBreakEvent event = null; - -- if (!this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player)) { ++ org.bukkit.block.Block bblock = org.bukkit.craftbukkit.block.CraftBlock.at(this.level, pos); ++ org.bukkit.event.block.BlockBreakEvent event = null; + if (this.player instanceof ServerPlayer) { + // Sword + Creative mode pre-cancel -+ boolean isSwordNoBreak = !this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player); ++ boolean isSwordNoBreak = !this.player.getMainHandItem().getItem().canAttackBlock(blockState, this.level, pos, this.player); + + // Tell client the block is gone immediately then process events + // Don't tell the client if its a creative sword break because its not broken! + if (false && this.level.getBlockEntity(pos) == null && !isSwordNoBreak) { // Paper - Don't resync block -+ ClientboundBlockUpdatePacket packet = new ClientboundBlockUpdatePacket(pos, Blocks.AIR.defaultBlockState()); ++ ClientboundBlockUpdatePacket packet = new ClientboundBlockUpdatePacket(pos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState()); + this.player.connection.send(packet); + } + -+ event = new BlockBreakEvent(bblock, this.player.getBukkitEntity()); ++ event = new org.bukkit.event.block.BlockBreakEvent(bblock, this.player.getBukkitEntity()); + + // Sword + Creative mode pre-cancel + event.setCancelled(isSwordNoBreak); @@ -291,40 +266,38 @@ + + // Update any tile entity data for this block + if (!captureSentBlockEntities) { // Paper - Send block entities after destroy prediction -+ BlockEntity tileentity = this.level.getBlockEntity(pos); -+ if (tileentity != null) { -+ this.player.connection.send(tileentity.getUpdatePacket()); -+ } ++ BlockEntity tileentity = this.level.getBlockEntity(pos); ++ if (tileentity != null) { ++ this.player.connection.send(tileentity.getUpdatePacket()); ++ } + } else {capturedBlockEntity = true;} // Paper - Send block entities after destroy prediction + return false; + } + } + // CraftBukkit end + -+ if (false && !this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player)) { // CraftBukkit - false ++ if (false && !this.player.getMainHandItem().getItem().canAttackBlock(blockState, this.level, pos, this.player)) { // CraftBukkit - false return false; } else { -+ iblockdata = this.level.getBlockState(pos); // CraftBukkit - update state from plugins -+ if (iblockdata.isAir()) return false; // CraftBukkit - A plugin set block to air without cancelling - BlockEntity tileentity = this.level.getBlockEntity(pos); - Block block = iblockdata.getBlock(); - ++ blockState = this.level.getBlockState(pos); // CraftBukkit - update state from plugins ++ if (blockState.isAir()) return false; // CraftBukkit - A plugin set block to air without cancelling + BlockEntity blockEntity = this.level.getBlockEntity(pos); + Block block = blockState.getBlock(); - if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks()) { + if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks() && !(block instanceof net.minecraft.world.level.block.CommandBlock && (this.player.isCreative() && this.player.getBukkitEntity().hasPermission("minecraft.commandblock")))) { // Paper - command block permission - this.level.sendBlockUpdated(pos, iblockdata, iblockdata, 3); + this.level.sendBlockUpdated(pos, blockState, blockState, 3); return false; } else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) { return false; } else { + // CraftBukkit start + org.bukkit.block.BlockState state = bblock.getState(); -+ this.level.captureDrops = new ArrayList<>(); ++ this.level.captureDrops = new java.util.ArrayList<>(); + // CraftBukkit end - BlockState iblockdata1 = block.playerWillDestroy(this.level, pos, iblockdata, this.player); + BlockState blockState1 = block.playerWillDestroy(this.level, pos, blockState, this.player); boolean flag = this.level.removeBlock(pos, false); - -@@ -262,20 +429,46 @@ - block.destroy(this.level, pos, iblockdata1); + if (flag) { + block.destroy(this.level, pos, blockState1); } + ItemStack mainHandStack = null; // Paper - Trigger bee_nest_destroyed trigger in the correct place @@ -333,22 +306,24 @@ - return true; + // return true; // CraftBukkit } else { - ItemStack itemstack = this.player.getMainHandItem(); - ItemStack itemstack1 = itemstack.copy(); - boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata1); -+ mainHandStack = itemstack1; // Paper - Trigger bee_nest_destroyed trigger in the correct place -+ isCorrectTool = flag1; // Paper - Trigger bee_nest_destroyed trigger in the correct place - - itemstack.mineBlock(this.level, iblockdata1, pos, this.player); -- if (flag && flag1) { -- block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1); -+ if (flag && flag1/* && event.isDropItems() */) { // CraftBukkit - Check if block should drop items // Paper - fix drops not preventing stats/food exhaustion -+ block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1, event.isDropItems(), false); // Paper - fix drops not preventing stats/food exhaustion - } - + ItemStack mainHandItem = this.player.getMainHandItem(); + ItemStack itemStack = mainHandItem.copy(); + boolean hasCorrectToolForDrops = this.player.hasCorrectToolForDrops(blockState1); ++ mainHandStack = itemStack; // Paper - Trigger bee_nest_destroyed trigger in the correct place ++ isCorrectTool = hasCorrectToolForDrops; // Paper - Trigger bee_nest_destroyed trigger in the correct place + mainHandItem.mineBlock(this.level, blockState1, pos, this.player); +- if (flag && hasCorrectToolForDrops) { +- block.playerDestroy(this.level, this.player, pos, blockState1, blockEntity, itemStack); +- } +- - return true; +- } ++ if (flag && hasCorrectToolForDrops/* && event.isDropItems() */) { // CraftBukkit - Check if block should drop items // Paper - fix drops not preventing stats/food exhaustion ++ block.playerDestroy(this.level, this.player, pos, blockState1, blockEntity, itemStack, event.isDropItems(), false); // Paper - fix drops not preventing stats/food exhaustion ++ } ++ + // return true; // CraftBukkit - } ++ } + // CraftBukkit start + java.util.List itemsToDrop = this.level.captureDrops; // Paper - capture all item additions to the world + this.level.captureDrops = null; // Paper - capture all item additions to the world; Remove this earlier so that we can actually drop stuff @@ -359,12 +334,12 @@ + + // Drop event experience + if (flag && event != null) { -+ iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper ++ blockState.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper + } + // Paper start - Trigger bee_nest_destroyed trigger in the correct place (check impls of block#playerDestroy) + if (mainHandStack != null) { -+ if (flag && isCorrectTool && event.isDropItems() && block instanceof net.minecraft.world.level.block.BeehiveBlock && tileentity instanceof net.minecraft.world.level.block.entity.BeehiveBlockEntity beehiveBlockEntity) { // simulates the guard on block#playerDestroy above -+ CriteriaTriggers.BEE_NEST_DESTROYED.trigger(player, iblockdata, mainHandStack, beehiveBlockEntity.getOccupantCount()); ++ if (flag && isCorrectTool && event.isDropItems() && block instanceof net.minecraft.world.level.block.BeehiveBlock && blockEntity instanceof net.minecraft.world.level.block.entity.BeehiveBlockEntity beehiveBlockEntity) { // simulates the guard on block#playerDestroy above ++ CriteriaTriggers.BEE_NEST_DESTROYED.trigger(player, blockState, mainHandStack, beehiveBlockEntity.getOccupantCount()); + } + } + // Paper end - Trigger bee_nest_destroyed trigger in the correct place @@ -374,7 +349,7 @@ } } } -@@ -321,17 +514,63 @@ +@@ -307,15 +_,61 @@ } } @@ -384,40 +359,38 @@ + public BlockPos interactPosition; + public InteractionHand interactHand; + public ItemStack interactItemStack; - public InteractionResult useItemOn(ServerPlayer player, Level world, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) { - BlockPos blockposition = hitResult.getBlockPos(); - BlockState iblockdata = world.getBlockState(blockposition); + public InteractionResult useItemOn(ServerPlayer player, Level level, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) { + BlockPos blockPos = hitResult.getBlockPos(); + BlockState blockState = level.getBlockState(blockPos); + boolean cancelledBlock = false; + boolean cancelledItem = false; // Paper - correctly handle items on cooldown - - if (!iblockdata.getBlock().isEnabled(world.enabledFeatures())) { + if (!blockState.getBlock().isEnabled(level.enabledFeatures())) { return InteractionResult.FAIL; } else if (this.gameModeForPlayer == GameType.SPECTATOR) { - MenuProvider itileinventory = iblockdata.getMenuProvider(world, blockposition); -+ cancelledBlock = !(itileinventory instanceof MenuProvider); + MenuProvider menuProvider = blockState.getMenuProvider(level, blockPos); +- if (menuProvider != null) { +- player.openMenu(menuProvider); ++ cancelledBlock = !(menuProvider instanceof MenuProvider); + } - -- if (itileinventory != null) { -- player.openMenu(itileinventory); ++ + if (player.getCooldowns().isOnCooldown(stack)) { + cancelledItem = true; // Paper - correctly handle items on cooldown + } -+ -+ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, cancelledItem, hand, hitResult.getLocation()); // Paper - correctly handle items on cooldown ++ org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(player, org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK, blockPos, hitResult.getDirection(), stack, cancelledBlock, cancelledItem, hand, hitResult.getLocation()); // Paper - correctly handle items on cooldown + this.firedInteract = true; -+ this.interactResult = event.useItemInHand() == Event.Result.DENY; -+ this.interactPosition = blockposition.immutable(); ++ this.interactResult = event.useItemInHand() == org.bukkit.event.Event.Result.DENY; ++ this.interactPosition = blockPos.immutable(); + this.interactHand = hand; + this.interactItemStack = stack.copy(); + -+ if (event.useInteractedBlock() == Event.Result.DENY) { ++ if (event.useInteractedBlock() == org.bukkit.event.Event.Result.DENY) { + // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door. -+ if (iblockdata.getBlock() instanceof DoorBlock) { ++ if (blockState.getBlock() instanceof net.minecraft.world.level.block.DoorBlock) { + // Paper start - Don't resync blocks + // boolean bottom = iblockdata.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER; + // player.connection.send(new ClientboundBlockUpdatePacket(world, bottom ? blockposition.above() : blockposition.below())); + // Paper end - Don't resync blocks -+ } else if (iblockdata.getBlock() instanceof CakeBlock) { ++ } else if (blockState.getBlock() instanceof net.minecraft.world.level.block.CakeBlock) { + player.getBukkitEntity().sendHealthUpdate(); // SPIGOT-1341 - reset health for cake + } else if (this.interactItemStack.getItem() instanceof DoubleHighBlockItem) { + // send a correcting update to the client, as it already placed the upper half of the bisected item @@ -425,33 +398,33 @@ + + // send a correcting update to the client for the block above as well, this because of replaceable blocks (such as grass, sea grass etc) + //player.connection.send(new ClientboundBlockUpdatePacket(world, blockposition.above())); // Paper - Don't resync blocks -+ // Paper start - extend Player Interact cancellation // TODO: consider merging this into the extracted method -+ } else if (iblockdata.is(Blocks.JIGSAW) || iblockdata.is(Blocks.STRUCTURE_BLOCK) || iblockdata.getBlock() instanceof net.minecraft.world.level.block.CommandBlock) { ++ // Paper start - extend Player Interact cancellation // TODO: consider merging this into the extracted method ++ } else if (blockState.is(net.minecraft.world.level.block.Blocks.JIGSAW) || blockState.is(net.minecraft.world.level.block.Blocks.STRUCTURE_BLOCK) || blockState.getBlock() instanceof net.minecraft.world.level.block.CommandBlock) { + player.connection.send(new net.minecraft.network.protocol.game.ClientboundContainerClosePacket(this.player.containerMenu.containerId)); + } + // Paper end - extend Player Interact cancellation + player.getBukkitEntity().updateInventory(); // SPIGOT-2867 + this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items -+ return (event.useItemInHand() != Event.Result.ALLOW) ? InteractionResult.SUCCESS : InteractionResult.PASS; ++ return (event.useItemInHand() != org.bukkit.event.Event.Result.ALLOW) ? InteractionResult.SUCCESS : InteractionResult.PASS; + } else if (this.gameModeForPlayer == GameType.SPECTATOR) { -+ MenuProvider itileinventory = iblockdata.getMenuProvider(world, blockposition); ++ MenuProvider itileinventory = blockState.getMenuProvider(level, blockPos); + + if (itileinventory != null && player.openMenu(itileinventory).isPresent()) { // Paper - Fix InventoryOpenEvent cancellation return InteractionResult.CONSUME; } else { return InteractionResult.PASS; -@@ -359,7 +598,7 @@ +@@ -340,7 +_,7 @@ } } - if (!stack.isEmpty() && !player.getCooldowns().isOnCooldown(stack)) { + if (!stack.isEmpty() && !this.interactResult) { // add !interactResult SPIGOT-764 - UseOnContext itemactioncontext = new UseOnContext(player, hand, hitResult); - + UseOnContext useOnContext = new UseOnContext(player, hand, hitResult); + InteractionResult interactionResult1; if (this.isCreative()) { -@@ -377,6 +616,11 @@ +@@ -357,6 +_,11 @@ - return enuminteractionresult; + return interactionResult1; } else { + // Paper start - Properly cancel usable items; Cancel only if cancelled + if the interact result is different from default response + if (this.interactResult && this.interactResult != cancelledItem) { diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/TicketType.java.patch b/paper-server/patches/sources/net/minecraft/server/level/TicketType.java.patch similarity index 63% rename from paper-server/patches/unapplied/net/minecraft/server/level/TicketType.java.patch rename to paper-server/patches/sources/net/minecraft/server/level/TicketType.java.patch index ca9484bbeb..12796a0ffe 100644 --- a/paper-server/patches/unapplied/net/minecraft/server/level/TicketType.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/TicketType.java.patch @@ -1,20 +1,20 @@ --- a/net/minecraft/server/level/TicketType.java +++ b/net/minecraft/server/level/TicketType.java -@@ -7,6 +7,7 @@ +@@ -7,6 +_,7 @@ import net.minecraft.world.level.ChunkPos; public class TicketType { + public static final TicketType FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper - private final String name; private final Comparator comparator; -@@ -22,6 +23,9 @@ - public static final TicketType PORTAL = TicketType.create("portal", Vec3i::compareTo, 300); - public static final TicketType ENDER_PEARL = TicketType.create("ender_pearl", Comparator.comparingLong(ChunkPos::toLong), 40); - public static final TicketType UNKNOWN = TicketType.create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1); + public long timeout; +@@ -17,6 +_,9 @@ + public static final TicketType PORTAL = create("portal", Vec3i::compareTo, 300); + public static final TicketType ENDER_PEARL = create("ender_pearl", Comparator.comparingLong(ChunkPos::toLong), 40); + public static final TicketType UNKNOWN = create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1); + public static final TicketType PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit + public static final TicketType PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit + public static final TicketType POST_TELEPORT = TicketType.create("post_teleport", Integer::compare, 5); // Paper - post teleport ticket type - public static TicketType create(String name, Comparator argumentComparator) { - return new TicketType<>(name, argumentComparator, 0L); + public static TicketType create(String name, Comparator comparator) { + return new TicketType<>(name, comparator, 0L); diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/WorldGenRegion.java.patch b/paper-server/patches/sources/net/minecraft/server/level/WorldGenRegion.java.patch similarity index 58% rename from paper-server/patches/unapplied/net/minecraft/server/level/WorldGenRegion.java.patch rename to paper-server/patches/sources/net/minecraft/server/level/WorldGenRegion.java.patch index 415887c78f..6daf126e10 100644 --- a/paper-server/patches/unapplied/net/minecraft/server/level/WorldGenRegion.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/WorldGenRegion.java.patch @@ -1,11 +1,9 @@ --- a/net/minecraft/server/level/WorldGenRegion.java +++ b/net/minecraft/server/level/WorldGenRegion.java -@@ -167,7 +167,27 @@ - int k = this.center.getPos().getChessboardDistance(chunkX, chunkZ); +@@ -151,6 +_,26 @@ + return chessboardDistance < this.generatingStep.directDependencies().size(); + } - return k < this.generatingStep.directDependencies().size(); -+ } -+ + // Paper start - if loaded util + @Nullable + @Override @@ -23,36 +21,43 @@ + public final FluidState getFluidIfLoaded(BlockPos blockposition) { + ChunkAccess chunk = this.getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4); + return chunk == null ? null : chunk.getFluidState(blockposition); - } ++ } + // Paper end - ++ @Override public BlockState getBlockState(BlockPos pos) { -@@ -217,7 +237,8 @@ - if (iblockdata.isAir()) { + return this.getChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())).getBlockState(pos); +@@ -198,7 +_,8 @@ + if (blockState.isAir()) { return false; } else { -- if (drop) { -+ if (drop) LOGGER.warn("Potential async entity add during worldgen", new Throwable()); // Paper - Fix async entity add due to fungus trees; log when this happens +- if (dropBlock) { ++ if (dropBlock) LOGGER.warn("Potential async entity add during worldgen", new Throwable()); // Paper - Fix async entity add due to fungus trees; log when this happens + if (false) { // CraftBukkit - SPIGOT-6833: Do not drop during world generation - BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null; - - Block.dropResources(iblockdata, this.level, pos, tileentity, breakingEntity, ItemStack.EMPTY); -@@ -264,6 +285,7 @@ + BlockEntity blockEntity = blockState.hasBlockEntity() ? this.getBlockEntity(pos) : null; + Block.dropResources(blockState, this.level, pos, blockEntity, entity, ItemStack.EMPTY); + } +@@ -242,6 +_,7 @@ } } + private boolean hasSetFarWarned = false; // Paper - Buffer OOB setBlock calls @Override public boolean ensureCanWrite(BlockPos pos) { - int i = SectionPos.blockToSectionCoord(pos.getX()); -@@ -283,7 +305,15 @@ + int sectionPosX = SectionPos.blockToSectionCoord(pos.getX()); +@@ -259,6 +_,8 @@ return true; } else { + // Paper start - Buffer OOB setBlock calls + if (!hasSetFarWarned) { - Util.logAndPauseIfInIde("Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + String.valueOf(pos) + ", status: " + String.valueOf(this.generatingStep.targetStatus()) + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String) this.currentlyGenerating.get())); + Util.logAndPauseIfInIde( + "Detected setBlock in a far chunk [" + + sectionPosX +@@ -270,6 +_,12 @@ + + this.generatingStep.targetStatus() + + (this.currentlyGenerating == null ? "" : ", currently generating: " + this.currentlyGenerating.get()) + ); + hasSetFarWarned = true; + if (this.getServer() != null && this.getServer().isDebugging()) { + io.papermc.paper.util.TraceUtil.dumpTraceForThread("far setBlock call"); @@ -62,17 +67,17 @@ return false; } } -@@ -294,7 +324,7 @@ +@@ -280,7 +_,7 @@ return false; } else { - ChunkAccess ichunkaccess = this.getChunk(pos); -- BlockState iblockdata1 = ichunkaccess.setBlockState(pos, state, false); -+ BlockState iblockdata1 = ichunkaccess.setBlockState(pos, state, false); final BlockState previousBlockState = iblockdata1; // Paper - Clear block entity before setting up a DUMMY block entity - obfhelper - - if (iblockdata1 != null) { - this.level.onBlockStateChange(pos, iblockdata1, state); -@@ -310,6 +340,17 @@ - ichunkaccess.removeBlockEntity(pos); + ChunkAccess chunk = this.getChunk(pos); +- BlockState blockState = chunk.setBlockState(pos, state, false); ++ BlockState blockState = chunk.setBlockState(pos, state, false); final BlockState previousBlockState = blockState; // Paper - Clear block entity before setting up a DUMMY block entity - obfhelper + if (blockState != null) { + this.level.onBlockStateChange(pos, blockState, state); + } +@@ -294,6 +_,17 @@ + chunk.removeBlockEntity(pos); } } else { + // Paper start - Clear block entity before setting up a DUMMY block entity @@ -83,13 +88,13 @@ + // be waterlogged would remove its existing block entity (see PaperMC/Paper#10750) + // This logic is *also* found in LevelChunk#setBlockState. + if (previousBlockState != null && !java.util.Objects.equals(previousBlockState.getBlock(), state.getBlock())) { -+ ichunkaccess.removeBlockEntity(pos); ++ chunk.removeBlockEntity(pos); + } + // Paper end - Clear block entity before setting up a DUMMY block entity - CompoundTag nbttagcompound = new CompoundTag(); - - nbttagcompound.putInt("x", pos.getX()); -@@ -336,6 +377,13 @@ + CompoundTag compoundTag = new CompoundTag(); + compoundTag.putInt("x", pos.getX()); + compoundTag.putInt("y", pos.getY()); +@@ -319,6 +_,13 @@ @Override public boolean addFreshEntity(Entity entity) { @@ -100,6 +105,6 @@ + @Override + public boolean addFreshEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { + // CraftBukkit end - int i = SectionPos.blockToSectionCoord(entity.getBlockX()); - int j = SectionPos.blockToSectionCoord(entity.getBlockZ()); - + int sectionPosX = SectionPos.blockToSectionCoord(entity.getBlockX()); + int sectionPosZ = SectionPos.blockToSectionCoord(entity.getBlockZ()); + this.getChunk(sectionPosX, sectionPosZ).addEntity(entity); diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/ChunkMap.java.patch b/paper-server/patches/unapplied/net/minecraft/server/level/ChunkMap.java.patch deleted file mode 100644 index 9a5c5895b8..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/server/level/ChunkMap.java.patch +++ /dev/null @@ -1,433 +0,0 @@ ---- a/net/minecraft/server/level/ChunkMap.java -+++ b/net/minecraft/server/level/ChunkMap.java -@@ -104,6 +104,10 @@ - import org.apache.commons.lang3.mutable.MutableBoolean; - import org.slf4j.Logger; - -+// CraftBukkit start -+import org.bukkit.craftbukkit.generator.CustomChunkGenerator; -+// CraftBukkit end -+ - public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider, GeneratingChunkMap { - - private static final ChunkResult> UNLOADED_CHUNK_LIST_RESULT = ChunkResult.error("Unloaded chunks found in range"); -@@ -149,6 +153,33 @@ - public int serverViewDistance; - private final WorldGenContext worldGenContext; - -+ // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback() -+ public final CallbackExecutor callbackExecutor = new CallbackExecutor(); -+ public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable { -+ -+ private final java.util.Queue queue = new java.util.ArrayDeque<>(); -+ -+ @Override -+ public void execute(Runnable runnable) { -+ this.queue.add(runnable); -+ } -+ -+ @Override -+ public void run() { -+ Runnable task; -+ while ((task = this.queue.poll()) != null) { -+ task.run(); -+ } -+ } -+ }; -+ // CraftBukkit end -+ -+ // Paper start -+ public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) { -+ return this.pendingUnloads.get(ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ // Paper end -+ - public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { - super(new RegionStorageInfo(session.getLevelId(), world.dimension(), "chunk"), session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); - this.visibleChunkMap = this.updatingChunkMap.clone(); -@@ -170,13 +201,19 @@ - RegistryAccess iregistrycustom = world.registryAccess(); - long j = world.getSeed(); - -- if (chunkGenerator instanceof NoiseBasedChunkGenerator chunkgeneratorabstract) { -+ // CraftBukkit start - SPIGOT-7051: It's a rigged game! Use delegate for random state creation, otherwise it is not so random. -+ ChunkGenerator randomGenerator = chunkGenerator; -+ if (randomGenerator instanceof CustomChunkGenerator customChunkGenerator) { -+ randomGenerator = customChunkGenerator.getDelegate(); -+ } -+ if (randomGenerator instanceof NoiseBasedChunkGenerator chunkgeneratorabstract) { -+ // CraftBukkit end - this.randomState = RandomState.create((NoiseGeneratorSettings) chunkgeneratorabstract.generatorSettings().value(), (HolderGetter) iregistrycustom.lookupOrThrow(Registries.NOISE), j); - } else { - this.randomState = RandomState.create(NoiseGeneratorSettings.dummy(), (HolderGetter) iregistrycustom.lookupOrThrow(Registries.NOISE), j); - } - -- this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j); -+ this.chunkGeneratorState = chunkGenerator.createState(iregistrycustom.lookupOrThrow(Registries.STRUCTURE_SET), this.randomState, j, world.spigotConfig); // Spigot - this.mainThreadExecutor = mainThreadExecutor; - ConsecutiveExecutor consecutiveexecutor = new ConsecutiveExecutor(executor, "worldgen"); - -@@ -198,6 +235,12 @@ - this.chunksToEagerlySave.add(pos.toLong()); - } - -+ // Paper start -+ public int getMobCountNear(final ServerPlayer player, final net.minecraft.world.entity.MobCategory mobCategory) { -+ return -1; -+ } -+ // Paper end -+ - protected ChunkGenerator generator() { - return this.worldGenContext.generator(); - } -@@ -325,7 +368,7 @@ - throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a"); - } - -- ChunkAccess ichunkaccess = (ChunkAccess) chunkresult.orElse((Object) null); -+ ChunkAccess ichunkaccess = (ChunkAccess) chunkresult.orElse(null); // CraftBukkit - decompile error - - if (ichunkaccess == null) { - return ChunkMap.UNLOADED_CHUNK_LIST_RESULT; -@@ -354,9 +397,9 @@ - }; - - stringbuilder.append("Updating:").append(System.lineSeparator()); -- this.updatingChunkMap.values().forEach(consumer); -+ ca.spottedleaf.moonrise.common.util.ChunkSystem.getUpdatingChunkHolders(this.level).forEach(consumer); // Paper - stringbuilder.append("Visible:").append(System.lineSeparator()); -- this.visibleChunkMap.values().forEach(consumer); -+ ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).forEach(consumer); // Paper - CrashReport crashreport = CrashReport.forThrowable(exception, "Chunk loading"); - CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Chunk loading"); - -@@ -398,6 +441,9 @@ - holder.setTicketLevel(level); - } else { - holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this::onLevelChange, this); -+ // Paper start -+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderCreate(this.level, holder); -+ // Paper end - } - - this.updatingChunkMap.put(pos, holder); -@@ -427,7 +473,7 @@ - - protected void saveAllChunks(boolean flush) { - if (flush) { -- List list = this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList(); -+ List list = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).toList(); // Paper - MutableBoolean mutableboolean = new MutableBoolean(); - - do { -@@ -453,7 +499,7 @@ - } else { - this.nextChunkSaveTime.clear(); - long i = Util.getMillis(); -- ObjectIterator objectiterator = this.visibleChunkMap.values().iterator(); -+ Iterator objectiterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper - - while (objectiterator.hasNext()) { - ChunkHolder playerchunk = (ChunkHolder) objectiterator.next(); -@@ -478,7 +524,7 @@ - } - - public boolean hasWork() { -- return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets(); -+ return this.lightEngine.hasLightWork() || !this.pendingUnloads.isEmpty() || ca.spottedleaf.moonrise.common.util.ChunkSystem.hasAnyChunkHolders(this.level) || !this.updatingChunkMap.isEmpty() || this.poiManager.hasWork() || !this.toDrop.isEmpty() || !this.unloadQueue.isEmpty() || this.worldgenTaskDispatcher.hasWork() || this.lightTaskDispatcher.hasWork() || this.distanceManager.hasTickets(); - } - - private void processUnloads(BooleanSupplier shouldKeepTicking) { -@@ -537,8 +583,11 @@ - this.scheduleUnload(pos, chunk); - } else { - ChunkAccess ichunkaccess = chunk.getLatestChunk(); -- -- if (this.pendingUnloads.remove(pos, chunk) && ichunkaccess != null) { -+ // Paper start -+ boolean removed; -+ if ((removed = this.pendingUnloads.remove(pos, chunk)) && ichunkaccess != null) { -+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk); -+ // Paper end - LevelChunk chunk1; - - if (ichunkaccess instanceof LevelChunk) { -@@ -556,7 +605,9 @@ - this.lightEngine.tryScheduleUpdate(); - this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null); - this.nextChunkSaveTime.remove(ichunkaccess.getPos().toLong()); -- } -+ } else if (removed) { // Paper start -+ ca.spottedleaf.moonrise.common.util.ChunkSystem.onChunkHolderDelete(this.level, chunk); -+ } // Paper end - - } - }; -@@ -905,7 +956,7 @@ - } - } - -- protected void setServerViewDistance(int watchDistance) { -+ public void setServerViewDistance(int watchDistance) { // Paper - public - int j = Mth.clamp(watchDistance, 2, 32); - - if (j != this.serverViewDistance) { -@@ -922,7 +973,7 @@ - - } - -- int getPlayerViewDistance(ServerPlayer player) { -+ public int getPlayerViewDistance(ServerPlayer player) { // Paper - public - return Mth.clamp(player.requestedViewDistance(), 2, this.serverViewDistance); - } - -@@ -951,7 +1002,7 @@ - } - - public int size() { -- return this.visibleChunkMap.size(); -+ return ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolderCount(this.level); // Paper - } - - public DistanceManager getDistanceManager() { -@@ -959,25 +1010,26 @@ - } - - protected Iterable getChunks() { -- return Iterables.unmodifiableIterable(this.visibleChunkMap.values()); -+ return Iterables.unmodifiableIterable(ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level)); // Paper - } - - void dumpChunks(Writer writer) throws IOException { - CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer); - TickingTracker tickingtracker = this.distanceManager.tickingTracker(); -- ObjectBidirectionalIterator objectbidirectionaliterator = this.visibleChunkMap.long2ObjectEntrySet().iterator(); -+ Iterator objectbidirectionaliterator = ca.spottedleaf.moonrise.common.util.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper - - while (objectbidirectionaliterator.hasNext()) { -- Entry entry = (Entry) objectbidirectionaliterator.next(); -- long i = entry.getLongKey(); -+ ChunkHolder playerchunk = objectbidirectionaliterator.next(); // Paper -+ long i = playerchunk.pos.toLong(); // Paper - ChunkPos chunkcoordintpair = new ChunkPos(i); -- ChunkHolder playerchunk = (ChunkHolder) entry.getValue(); -+ // Paper - move up - Optional optional = Optional.ofNullable(playerchunk.getLatestChunk()); - Optional optional1 = optional.flatMap((ichunkaccess) -> { - return ichunkaccess instanceof LevelChunk ? Optional.of((LevelChunk) ichunkaccess) : Optional.empty(); - }); - -- csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getPersistedStatus).orElse((Object) null), optional1.map(LevelChunk::getFullStatus).orElse((Object) null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> { -+ // CraftBukkit - decompile error -+ csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getPersistedStatus).orElse(null), optional1.map(LevelChunk::getFullStatus).orElse(null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> { - return chunk.getBlockEntities().size(); - }).orElse(0), tickingtracker.getTicketDebugString(i), tickingtracker.getLevel(i), optional1.map((chunk) -> { - return chunk.getBlockTicks().count(); -@@ -990,7 +1042,7 @@ - - private static String printFuture(CompletableFuture> future) { - try { -- ChunkResult chunkresult = (ChunkResult) future.getNow((Object) null); -+ ChunkResult chunkresult = (ChunkResult) future.getNow(null); // CraftBukkit - decompile error - - return chunkresult != null ? (chunkresult.isSuccess() ? "done" : "unloaded") : "not completed"; - } catch (CompletionException completionexception) { -@@ -1002,12 +1054,14 @@ - - private CompletableFuture> readChunk(ChunkPos chunkPos) { - return this.read(chunkPos).thenApplyAsync((optional) -> { -- return optional.map(this::upgradeChunkTag); -+ return optional.map((nbttagcompound) -> this.upgradeChunkTag(nbttagcompound, chunkPos)); // CraftBukkit - }, Util.backgroundExecutor().forName("upgradeChunk")); - } - -- private CompoundTag upgradeChunkTag(CompoundTag nbt) { -- return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, nbt, this.generator().getTypeNameForDataFixer()); -+ // CraftBukkit start -+ private CompoundTag upgradeChunkTag(CompoundTag nbttagcompound, ChunkPos chunkcoordintpair) { -+ return this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, this.generator().getTypeNameForDataFixer(), chunkcoordintpair, this.level); -+ // CraftBukkit end - } - - void forEachSpawnCandidateChunk(Consumer callback) { -@@ -1025,10 +1079,23 @@ - } - - public boolean anyPlayerCloseEnoughForSpawning(ChunkPos pos) { -- return !this.distanceManager.hasPlayersNearby(pos.toLong()) ? false : this.anyPlayerCloseEnoughForSpawningInternal(pos); -+ // Spigot start -+ return this.anyPlayerCloseEnoughForSpawning(pos, false); - } - -+ boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) { -+ return !this.distanceManager.hasPlayersNearby(chunkcoordintpair.toLong()) ? false : this.anyPlayerCloseEnoughForSpawningInternal(chunkcoordintpair, reducedRange); -+ // Spigot end -+ } -+ - private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos pos) { -+ // Spigot start -+ return this.anyPlayerCloseEnoughForSpawningInternal(pos, false); -+ } -+ -+ private boolean anyPlayerCloseEnoughForSpawningInternal(ChunkPos chunkcoordintpair, boolean reducedRange) { -+ double blockRange; // Paper - use from event -+ // Spigot end - Iterator iterator = this.playerMap.getAllPlayers().iterator(); - - ServerPlayer entityplayer; -@@ -1039,7 +1106,16 @@ - } - - entityplayer = (ServerPlayer) iterator.next(); -- } while (!this.playerIsCloseEnoughForSpawning(entityplayer, pos)); -+ // Paper start - PlayerNaturallySpawnCreaturesEvent -+ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event; -+ blockRange = 16384.0D; -+ if (reducedRange) { -+ event = entityplayer.playerNaturallySpawnedEvent; -+ if (event == null || event.isCancelled()) continue; -+ blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4)); -+ } -+ // Paper end - PlayerNaturallySpawnCreaturesEvent -+ } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot - - return true; - } -@@ -1056,7 +1132,7 @@ - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); - -- if (this.playerIsCloseEnoughForSpawning(entityplayer, pos)) { -+ if (this.playerIsCloseEnoughForSpawning(entityplayer, pos, 16384.0D)) { // Spigot - builder.add(entityplayer); - } - } -@@ -1065,13 +1141,13 @@ - } - } - -- private boolean playerIsCloseEnoughForSpawning(ServerPlayer player, ChunkPos pos) { -- if (player.isSpectator()) { -+ private boolean playerIsCloseEnoughForSpawning(ServerPlayer entityplayer, ChunkPos chunkcoordintpair, double range) { // Spigot -+ if (entityplayer.isSpectator()) { - return false; - } else { -- double d0 = ChunkMap.euclideanDistanceSquared(pos, player); -+ double d0 = ChunkMap.euclideanDistanceSquared(chunkcoordintpair, entityplayer); - -- return d0 < 16384.0D; -+ return d0 < range; // Spigot - } - } - -@@ -1215,9 +1291,19 @@ - } - - public void addEntity(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 || this.entityMap.containsKey(entity.getId())) { -+ LOGGER.error("Illegal ChunkMap::addEntity for world " + this.level.getWorld().getName() -+ + ": " + entity + (this.entityMap.containsKey(entity.getId()) ? " ALREADY CONTAINED (This would have crashed your server)" : ""), new Throwable()); -+ return; -+ } -+ // Paper end - ignore and warn about illegal addEntity calls instead of crashing server -+ if (entity instanceof ServerPlayer && ((ServerPlayer) entity).supressTrackerForLogin) return; // Paper - Fire PlayerJoinEvent when Player is actually ready; Delay adding to tracker until after list packets - if (!(entity instanceof EnderDragonPart)) { - EntityType entitytypes = entity.getType(); - int i = entitytypes.clientTrackingRange() * 16; -+ i = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, i); // Spigot - - if (i != 0) { - int j = entitytypes.updateInterval(); -@@ -1250,6 +1336,7 @@ - } - - protected void removeEntity(Entity entity) { -+ org.spigotmc.AsyncCatcher.catchOp("entity untrack"); // Spigot - if (entity instanceof ServerPlayer entityplayer) { - this.updatePlayerStatus(entityplayer, false); - ObjectIterator objectiterator = this.entityMap.values().iterator(); -@@ -1391,7 +1478,7 @@ - }); - } - -- private class ChunkDistanceManager extends DistanceManager { -+ public class ChunkDistanceManager extends DistanceManager { // Paper - public - - protected ChunkDistanceManager(final Executor workerExecutor, final Executor mainThreadExecutor) { - super(workerExecutor, mainThreadExecutor); -@@ -1421,10 +1508,10 @@ - final Entity entity; - private final int range; - SectionPos lastSectionPos; -- public final Set seenBy = Sets.newIdentityHashSet(); -+ public final Set seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl - - public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) { -- this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast); -+ this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit - this.entity = entity; - this.range = i; - this.lastSectionPos = SectionPos.of((EntityAccess) entity); -@@ -1469,6 +1556,7 @@ - } - - public void removePlayer(ServerPlayer player) { -+ org.spigotmc.AsyncCatcher.catchOp("player tracker clear"); // Spigot - if (this.seenBy.remove(player.connection)) { - this.serverEntity.removePairing(player); - } -@@ -1476,17 +1564,41 @@ - } - - public void updatePlayer(ServerPlayer player) { -+ org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot - if (player != this.entity) { -- Vec3 vec3d = player.position().subtract(this.entity.position()); -+ // Paper start - remove allocation of Vec3D here -+ // Vec3 vec3d = player.position().subtract(this.entity.position()); -+ double vec3d_dx = player.getX() - this.entity.getX(); -+ double vec3d_dz = player.getZ() - this.entity.getZ(); -+ // Paper end - remove allocation of Vec3D here - int i = ChunkMap.this.getPlayerViewDistance(player); - double d0 = (double) Math.min(this.getEffectiveRange(), i * 16); -- double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z; -+ double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper - double d2 = d0 * d0; -- boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); -+ // Paper start - Configurable entity tracking range by Y -+ boolean flag = d1 <= d2; -+ if (flag && level.paperConfig().entities.trackingRangeY.enabled) { -+ double rangeY = level.paperConfig().entities.trackingRangeY.get(this.entity, -1); -+ if (rangeY != -1) { -+ double vec3d_dy = player.getY() - this.entity.getY(); -+ flag = vec3d_dy * vec3d_dy <= rangeY * rangeY; -+ } -+ } -+ flag = flag && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z); -+ // Paper end - Configurable entity tracking range by Y - -+ // CraftBukkit start - respect vanish API -+ if (flag && !player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) { // Paper - only consider hits -+ flag = false; -+ } -+ // CraftBukkit end - if (flag) { - if (this.seenBy.add(player.connection)) { -+ // Paper start - entity tracking events -+ if (io.papermc.paper.event.player.PlayerTrackEntityEvent.getHandlerList().getRegisteredListeners().length == 0 || new io.papermc.paper.event.player.PlayerTrackEntityEvent(player.getBukkitEntity(), this.entity.getBukkitEntity()).callEvent()) { - this.serverEntity.addPairing(player); -+ } -+ // Paper end - entity tracking events - } - } else if (this.seenBy.remove(player.connection)) { - this.serverEntity.removePairing(player); -@@ -1506,6 +1618,7 @@ - while (iterator.hasNext()) { - Entity entity = (Entity) iterator.next(); - int j = entity.getType().clientTrackingRange() * 16; -+ j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper - - if (j > i) { - i = j; diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/DistanceManager.java.patch b/paper-server/patches/unapplied/net/minecraft/server/level/DistanceManager.java.patch deleted file mode 100644 index 60e1437870..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/server/level/DistanceManager.java.patch +++ /dev/null @@ -1,152 +0,0 @@ ---- a/net/minecraft/server/level/DistanceManager.java -+++ b/net/minecraft/server/level/DistanceManager.java -@@ -117,8 +117,17 @@ - - ChunkHolder playerchunk; - -+ // CraftBukkit start - SPIGOT-7780: Call chunk unload events before updateHighestAllowedStatus - while (iterator.hasNext()) { - playerchunk = (ChunkHolder) iterator.next(); -+ playerchunk.callEventIfUnloading(chunkLoadingManager); -+ } -+ -+ iterator = this.chunksToUpdateFutures.iterator(); -+ // CraftBukkit end -+ -+ while (iterator.hasNext()) { -+ playerchunk = (ChunkHolder) iterator.next(); - playerchunk.updateHighestAllowedStatus(chunkLoadingManager); - } - -@@ -165,30 +174,33 @@ - } - } - -- void addTicket(long position, Ticket ticket) { -- SortedArraySet> arraysetsorted = this.getTickets(position); -+ boolean addTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean -+ SortedArraySet> arraysetsorted = this.getTickets(i); - int j = DistanceManager.getTicketLevelAt(arraysetsorted); - Ticket ticket1 = (Ticket) arraysetsorted.addOrGet(ticket); - - ticket1.setCreatedTick(this.ticketTickCounter); - if (ticket.getTicketLevel() < j) { -- this.ticketTracker.update(position, ticket.getTicketLevel(), true); -+ this.ticketTracker.update(i, ticket.getTicketLevel(), true); - } - -+ return ticket == ticket1; // CraftBukkit - } - -- void removeTicket(long pos, Ticket ticket) { -- SortedArraySet> arraysetsorted = this.getTickets(pos); -+ boolean removeTicket(long i, Ticket ticket) { // CraftBukkit - void -> boolean -+ SortedArraySet> arraysetsorted = this.getTickets(i); - -+ boolean removed = false; // CraftBukkit - if (arraysetsorted.remove(ticket)) { -- ; -+ removed = true; // CraftBukkit - } - - if (arraysetsorted.isEmpty()) { -- this.tickets.remove(pos); -+ this.tickets.remove(i); - } - -- this.ticketTracker.update(pos, DistanceManager.getTicketLevelAt(arraysetsorted), false); -+ this.ticketTracker.update(i, DistanceManager.getTicketLevelAt(arraysetsorted), false); -+ return removed; // CraftBukkit - } - - public void addTicket(TicketType type, ChunkPos pos, int level, T argument) { -@@ -202,19 +214,33 @@ - } - - public void addRegionTicket(TicketType type, ChunkPos pos, int radius, T argument) { -- Ticket ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - radius, argument); -- long j = pos.toLong(); -+ // CraftBukkit start -+ this.addRegionTicketAtDistance(type, pos, radius, argument); -+ } - -- this.addTicket(j, ticket); -+ public boolean addRegionTicketAtDistance(TicketType tickettype, ChunkPos chunkcoordintpair, int i, T t0) { -+ // CraftBukkit end -+ Ticket ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); -+ long j = chunkcoordintpair.toLong(); -+ -+ boolean added = this.addTicket(j, ticket); // CraftBukkit - this.tickingTicketsTracker.addTicket(j, ticket); -+ return added; // CraftBukkit - } - - public void removeRegionTicket(TicketType type, ChunkPos pos, int radius, T argument) { -- Ticket ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - radius, argument); -- long j = pos.toLong(); -+ // CraftBukkit start -+ this.removeRegionTicketAtDistance(type, pos, radius, argument); -+ } - -- this.removeTicket(j, ticket); -+ public boolean removeRegionTicketAtDistance(TicketType tickettype, ChunkPos chunkcoordintpair, int i, T t0) { -+ // CraftBukkit end -+ Ticket ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0); -+ long j = chunkcoordintpair.toLong(); -+ -+ boolean removed = this.removeTicket(j, ticket); // CraftBukkit - this.tickingTicketsTracker.removeTicket(j, ticket); -+ return removed; // CraftBukkit - } - - private SortedArraySet> getTickets(long position) { -@@ -253,9 +279,10 @@ - ChunkPos chunkcoordintpair = pos.chunk(); - long i = chunkcoordintpair.toLong(); - ObjectSet objectset = (ObjectSet) this.playersPerChunk.get(i); -+ if (objectset == null) return; // CraftBukkit - SPIGOT-6208 - -- objectset.remove(player); -- if (objectset.isEmpty()) { -+ 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.playerTicketManager.update(i, Integer.MAX_VALUE, false); -@@ -358,7 +385,7 @@ - } - - public void removeTicketsOnClosing() { -- ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN); -+ ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve - ObjectIterator>>> objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); - - while (objectiterator.hasNext()) { -@@ -389,7 +416,27 @@ - - public boolean hasTickets() { - return !this.tickets.isEmpty(); -+ } -+ -+ // CraftBukkit start -+ public void removeAllTicketsFor(TicketType ticketType, int ticketLevel, T ticketIdentifier) { -+ Ticket target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier); -+ -+ for (java.util.Iterator>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ Entry>> entry = iterator.next(); -+ SortedArraySet> tickets = entry.getValue(); -+ if (tickets.remove(target)) { -+ // copied from removeTicket -+ this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false); -+ -+ // can't use entry after it's removed -+ if (tickets.isEmpty()) { -+ iterator.remove(); -+ } -+ } -+ } - } -+ // CraftBukkit end - - private class ChunkTicketTracker extends ChunkTracker { - diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/ServerEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/server/level/ServerEntity.java.patch deleted file mode 100644 index f458422c50..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/server/level/ServerEntity.java.patch +++ /dev/null @@ -1,179 +0,0 @@ ---- a/net/minecraft/server/level/ServerEntity.java -+++ b/net/minecraft/server/level/ServerEntity.java -@@ -31,7 +31,6 @@ - import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket; - import net.minecraft.network.protocol.game.VecDeltaCodec; - import net.minecraft.network.syncher.SynchedEntityData; --import net.minecraft.util.Mth; - import net.minecraft.world.entity.Entity; - import net.minecraft.world.entity.EquipmentSlot; - import net.minecraft.world.entity.Leashable; -@@ -50,6 +49,13 @@ - import net.minecraft.world.phys.Vec3; - import org.slf4j.Logger; - -+// CraftBukkit start -+import net.minecraft.server.network.ServerPlayerConnection; -+import net.minecraft.util.Mth; -+import org.bukkit.entity.Player; -+import org.bukkit.event.player.PlayerVelocityEvent; -+// CraftBukkit end -+ - public class ServerEntity { - - private static final Logger LOGGER = LogUtils.getLogger(); -@@ -69,18 +75,22 @@ - private Vec3 lastSentMovement; - private int tickCount; - private int teleportDelay; -- private List lastPassengers = Collections.emptyList(); -+ private List lastPassengers = com.google.common.collect.ImmutableList.of(); // Paper - optimize passenger checks - private boolean wasRiding; - private boolean wasOnGround; - @Nullable - private List> trackedDataValues; -+ // CraftBukkit start -+ private final Set trackedPlayers; - -- public ServerEntity(ServerLevel world, Entity entity, int tickInterval, boolean alwaysUpdateVelocity, Consumer> receiver) { -- this.level = world; -- this.broadcast = receiver; -+ public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer> consumer, Set trackedPlayers) { -+ this.trackedPlayers = trackedPlayers; -+ // CraftBukkit end -+ this.level = worldserver; -+ this.broadcast = consumer; - this.entity = entity; -- this.updateInterval = tickInterval; -- this.trackDelta = alwaysUpdateVelocity; -+ this.updateInterval = i; -+ this.trackDelta = flag; - this.positionCodec.setBase(entity.trackingPosition()); - this.lastSentMovement = entity.getDeltaMovement(); - this.lastSentYRot = Mth.packDegrees(entity.getYRot()); -@@ -94,7 +104,7 @@ - List list = this.entity.getPassengers(); - - if (!list.equals(this.lastPassengers)) { -- this.broadcast.accept(new ClientboundSetPassengersPacket(this.entity)); -+ this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit - ServerEntity.removedPassengers(list, this.lastPassengers).forEach((entity) -> { - if (entity instanceof ServerPlayer entityplayer) { - entityplayer.connection.teleport(entityplayer.getX(), entityplayer.getY(), entityplayer.getZ(), entityplayer.getYRot(), entityplayer.getXRot()); -@@ -106,19 +116,19 @@ - - Entity entity = this.entity; - -- if (entity instanceof ItemFrame entityitemframe) { -- if (this.tickCount % 10 == 0) { -+ if (!this.trackedPlayers.isEmpty() && entity instanceof ItemFrame entityitemframe) { // Paper - Perf: Only tick item frames if players can see it -+ if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block - ItemStack itemstack = entityitemframe.getItem(); - -- if (itemstack.getItem() instanceof MapItem) { -- MapId mapid = (MapId) itemstack.get(DataComponents.MAP_ID); -+ if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable -+ MapId mapid = entityitemframe.cachedMapId; // Paper - Perf: Cache map ids on item frames - MapItemSavedData worldmap = MapItem.getSavedData(mapid, this.level); - - if (worldmap != null) { -- Iterator iterator = this.level.players().iterator(); -+ Iterator iterator = this.trackedPlayers.iterator(); // CraftBukkit - - while (iterator.hasNext()) { -- ServerPlayer entityplayer = (ServerPlayer) iterator.next(); -+ ServerPlayer entityplayer = iterator.next().getPlayer(); // CraftBukkit - - worldmap.tickCarriedBy(entityplayer, itemstack); - Packet packet = worldmap.getUpdatePacket(mapid, entityplayer); -@@ -168,7 +178,13 @@ - - ++this.teleportDelay; - Vec3 vec3d = this.entity.trackingPosition(); -- boolean flag1 = this.positionCodec.delta(vec3d).lengthSqr() >= 7.62939453125E-6D; -+ // Paper start - reduce allocation of Vec3D here -+ Vec3 base = this.positionCodec.base; -+ double vec3d_dx = vec3d.x - base.x; -+ double vec3d_dy = vec3d.y - base.y; -+ double vec3d_dz = vec3d.z - base.z; -+ boolean flag1 = (vec3d_dx * vec3d_dx + vec3d_dy * vec3d_dy + vec3d_dz * vec3d_dz) >= 7.62939453125E-6D; -+ // Paper end - reduce allocation of Vec3D here - Packet packet1 = null; - boolean flag2 = flag1 || this.tickCount % 60 == 0; - boolean flag3 = false; -@@ -248,6 +264,27 @@ - - ++this.tickCount; - if (this.entity.hurtMarked) { -+ // CraftBukkit start - Create PlayerVelocity event -+ boolean cancelled = false; -+ -+ if (this.entity instanceof ServerPlayer) { -+ Player player = (Player) this.entity.getBukkitEntity(); -+ org.bukkit.util.Vector velocity = player.getVelocity(); -+ -+ PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity.clone()); -+ this.entity.level().getCraftServer().getPluginManager().callEvent(event); -+ -+ if (event.isCancelled()) { -+ cancelled = true; -+ } else if (!velocity.equals(event.getVelocity())) { -+ player.setVelocity(event.getVelocity()); -+ } -+ } -+ -+ if (cancelled) { -+ return; -+ } -+ // CraftBukkit end - this.entity.hurtMarked = false; - this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity)); - } -@@ -298,7 +335,10 @@ - - public void sendPairingData(ServerPlayer player, Consumer> sender) { - if (this.entity.isRemoved()) { -- ServerEntity.LOGGER.warn("Fetching packet for removed entity {}", this.entity); -+ // CraftBukkit start - Remove useless error spam, just return -+ // EntityTrackerEntry.LOGGER.warn("Fetching packet for removed entity {}", this.entity); -+ return; -+ // CraftBukkit end - } - - Packet packet = this.entity.getAddEntityPacket(this); -@@ -313,6 +353,12 @@ - if (this.entity instanceof LivingEntity) { - Collection collection = ((LivingEntity) this.entity).getAttributes().getSyncableAttributes(); - -+ // CraftBukkit start - If sending own attributes send scaled health instead of current maximum health -+ if (this.entity.getId() == player.getId()) { -+ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(collection, false); -+ } -+ // CraftBukkit end -+ - if (!collection.isEmpty()) { - sender.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), collection)); - } -@@ -342,8 +388,9 @@ - } - - if (!list.isEmpty()) { -- sender.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list)); -+ sender.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list, true)); // Paper - data sanitization - } -+ ((LivingEntity) this.entity).detectEquipmentUpdatesPublic(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending - } - - if (!this.entity.getPassengers().isEmpty()) { -@@ -396,6 +443,11 @@ - Set set = ((LivingEntity) this.entity).getAttributes().getAttributesToSync(); - - if (!set.isEmpty()) { -+ // CraftBukkit start - Send scaled max health -+ if (this.entity instanceof ServerPlayer) { -+ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(set, false); -+ } -+ // CraftBukkit end - this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), set)); - } - diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/ServerLevel.java.patch b/paper-server/patches/unapplied/net/minecraft/server/level/ServerLevel.java.patch deleted file mode 100644 index d9915e3d7d..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/server/level/ServerLevel.java.patch +++ /dev/null @@ -1,1261 +0,0 @@ ---- a/net/minecraft/server/level/ServerLevel.java -+++ b/net/minecraft/server/level/ServerLevel.java -@@ -58,7 +58,6 @@ - import net.minecraft.network.protocol.game.ClientboundDamageEventPacket; - import net.minecraft.network.protocol.game.ClientboundEntityEventPacket; - import net.minecraft.network.protocol.game.ClientboundExplodePacket; --import net.minecraft.network.protocol.game.ClientboundGameEventPacket; - import net.minecraft.network.protocol.game.ClientboundLevelEventPacket; - import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket; - import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket; -@@ -124,6 +123,7 @@ - import net.minecraft.world.level.StructureManager; - import net.minecraft.world.level.WorldGenLevel; - import net.minecraft.world.level.biome.Biome; -+import net.minecraft.world.level.biome.BiomeSource; - import net.minecraft.world.level.block.Block; - import net.minecraft.world.level.block.Blocks; - import net.minecraft.world.level.block.SnowLayerBlock; -@@ -149,7 +149,9 @@ - import net.minecraft.world.level.gameevent.DynamicGameEventListener; - import net.minecraft.world.level.gameevent.GameEvent; - import net.minecraft.world.level.gameevent.GameEventDispatcher; -+import net.minecraft.world.level.levelgen.FlatLevelSource; - import net.minecraft.world.level.levelgen.Heightmap; -+import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; - import net.minecraft.world.level.levelgen.structure.BoundingBox; - import net.minecraft.world.level.levelgen.structure.Structure; - import net.minecraft.world.level.levelgen.structure.StructureCheck; -@@ -165,7 +167,7 @@ - import net.minecraft.world.level.saveddata.maps.MapItemSavedData; - import net.minecraft.world.level.storage.DimensionDataStorage; - import net.minecraft.world.level.storage.LevelStorageSource; --import net.minecraft.world.level.storage.ServerLevelData; -+import net.minecraft.world.level.storage.PrimaryLevelData; - import net.minecraft.world.phys.AABB; - import net.minecraft.world.phys.Vec3; - import net.minecraft.world.phys.shapes.BooleanOp; -@@ -173,6 +175,16 @@ - import net.minecraft.world.phys.shapes.VoxelShape; - import net.minecraft.world.ticks.LevelTicks; - import org.slf4j.Logger; -+import org.bukkit.Bukkit; -+import org.bukkit.WeatherType; -+import org.bukkit.craftbukkit.event.CraftEventFactory; -+import org.bukkit.craftbukkit.generator.CustomWorldChunkManager; -+import org.bukkit.craftbukkit.util.WorldUUID; -+import org.bukkit.event.entity.CreatureSpawnEvent; -+import org.bukkit.event.server.MapInitializeEvent; -+import org.bukkit.event.weather.LightningStrikeEvent; -+import org.bukkit.event.world.TimeSkipEvent; -+// CraftBukkit end - - public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLevel { - -@@ -187,7 +199,7 @@ - final List players = Lists.newArrayList(); - public final ServerChunkCache chunkSource; - private final MinecraftServer server; -- public final ServerLevelData serverLevelData; -+ public final PrimaryLevelData serverLevelData; // CraftBukkit - type - private int lastSpawnChunkRadius; - final EntityTickList entityTickList = new EntityTickList(); - public final PersistentEntitySectionManager entityManager; -@@ -214,54 +226,204 @@ - private final boolean tickTime; - private final RandomSequences randomSequences; - -- public ServerLevel(MinecraftServer server, Executor workerExecutor, LevelStorageSource.LevelStorageAccess session, ServerLevelData properties, ResourceKey worldKey, LevelStem dimensionOptions, ChunkProgressListener worldGenerationProgressListener, boolean debugWorld, long seed, List spawners, boolean shouldTickTime, @Nullable RandomSequences randomSequencesState) { -- super(properties, worldKey, server.registryAccess(), dimensionOptions.type(), false, debugWorld, seed, server.getMaxChainedNeighborUpdates()); -- this.tickTime = shouldTickTime; -- this.server = server; -- this.customSpawners = spawners; -- this.serverLevelData = properties; -- ChunkGenerator chunkgenerator = dimensionOptions.generator(); -- boolean flag2 = server.forceSynchronousWrites(); -- DataFixer datafixer = server.getFixerUpper(); -- EntityPersistentStorage entitypersistentstorage = new EntityStorage(new SimpleRegionStorage(new RegionStorageInfo(session.getLevelId(), worldKey, "entities"), session.getDimensionPath(worldKey).resolve("entities"), datafixer, flag2, DataFixTypes.ENTITY_CHUNK), this, server); -+ // CraftBukkit start -+ public final LevelStorageSource.LevelStorageAccess convertable; -+ public final UUID uuid; -+ public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent -+ public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent -+ -+ public LevelChunk getChunkIfLoaded(int x, int z) { -+ return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately -+ } -+ -+ @Override -+ public ResourceKey getTypeKey() { -+ return this.convertable.dimensionType; -+ } -+ -+ // Paper start -+ public final boolean areChunksLoadedForMove(AABB axisalignedbb) { -+ // copied code from collision methods, so that we can guarantee that they wont load chunks (we don't override -+ // ICollisionAccess methods for VoxelShapes) -+ // be more strict too, add a block (dumb plugins in move events?) -+ int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; -+ int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; -+ -+ int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; -+ int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; -+ -+ int minChunkX = minBlockX >> 4; -+ int maxChunkX = maxBlockX >> 4; -+ -+ int minChunkZ = minBlockZ >> 4; -+ int maxChunkZ = maxBlockZ >> 4; -+ -+ ServerChunkCache chunkProvider = this.getChunkSource(); -+ -+ for (int cx = minChunkX; cx <= maxChunkX; ++cx) { -+ for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { -+ if (chunkProvider.getChunkAtIfLoadedImmediately(cx, cz) == null) { -+ return false; -+ } -+ } -+ } -+ -+ return true; -+ } -+ -+ public final void loadChunksForMoveAsync(AABB axisalignedbb, ca.spottedleaf.concurrentutil.util.Priority priority, -+ java.util.function.Consumer> onLoad) { -+ if (Thread.currentThread() != this.thread) { -+ this.getChunkSource().mainThreadProcessor.execute(() -> { -+ this.loadChunksForMoveAsync(axisalignedbb, priority, onLoad); -+ }); -+ return; -+ } -+ int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; -+ int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; -+ -+ int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; -+ int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; -+ -+ int minChunkX = minBlockX >> 4; -+ int minChunkZ = minBlockZ >> 4; -+ -+ int maxChunkX = maxBlockX >> 4; -+ int maxChunkZ = maxBlockZ >> 4; -+ -+ this.loadChunks(minChunkX, minChunkZ, maxChunkX, maxChunkZ, priority, onLoad); -+ } -+ -+ public final void loadChunks(int minChunkX, int minChunkZ, int maxChunkX, int maxChunkZ, -+ ca.spottedleaf.concurrentutil.util.Priority priority, -+ java.util.function.Consumer> onLoad) { -+ List ret = new java.util.ArrayList<>(); -+ it.unimi.dsi.fastutil.ints.IntArrayList ticketLevels = new it.unimi.dsi.fastutil.ints.IntArrayList(); -+ ServerChunkCache chunkProvider = this.getChunkSource(); - -+ int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1); -+ int[] loadedChunks = new int[1]; -+ -+ Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++); -+ -+ java.util.function.Consumer consumer = (net.minecraft.world.level.chunk.ChunkAccess chunk) -> { -+ if (chunk != null) { -+ int ticketLevel = Math.max(33, chunkProvider.chunkMap.getUpdatingChunkIfPresent(chunk.getPos().toLong()).getTicketLevel()); -+ ret.add(chunk); -+ ticketLevels.add(ticketLevel); -+ chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier); -+ } -+ if (++loadedChunks[0] == requiredChunks) { -+ try { -+ onLoad.accept(java.util.Collections.unmodifiableList(ret)); -+ } finally { -+ for (int i = 0, len = ret.size(); i < len; ++i) { -+ ChunkPos chunkPos = ret.get(i).getPos(); -+ int ticketLevel = ticketLevels.getInt(i); -+ -+ chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); -+ chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier); -+ } -+ } -+ } -+ }; -+ -+ for (int cx = minChunkX; cx <= maxChunkX; ++cx) { -+ for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { -+ ca.spottedleaf.moonrise.common.util.ChunkSystem.scheduleChunkLoad( -+ this, cx, cz, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true, priority, consumer -+ ); -+ } -+ } -+ } -+ // Paper end -+ -+ // Paper start - optimise getPlayerByUUID -+ @Nullable -+ @Override -+ public Player getPlayerByUUID(UUID uuid) { -+ final Player player = this.getServer().getPlayerList().getPlayer(uuid); -+ return player != null && player.level() == this ? player : null; -+ } -+ // Paper end - optimise getPlayerByUUID -+ -+ // Add env and gen to constructor, IWorldDataServer -> WorldDataServer -+ public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) { -+ super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env, spigotConfig -> minecraftserver.paperConfigurations.createWorldConfig(io.papermc.paper.configuration.PaperConfigurations.createWorldContextMap(convertable_conversionsession.levelDirectory.path(), iworlddataserver.getLevelName(), resourcekey.location(), spigotConfig, minecraftserver.registryAccess(), iworlddataserver.getGameRules()))); // Paper - create paper world configs -+ this.pvpMode = minecraftserver.isPvpAllowed(); -+ this.convertable = convertable_conversionsession; -+ this.uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile()); -+ // CraftBukkit end -+ this.tickTime = flag1; -+ this.server = minecraftserver; -+ this.customSpawners = list; -+ this.serverLevelData = iworlddataserver; -+ ChunkGenerator chunkgenerator = worlddimension.generator(); -+ // CraftBukkit start -+ this.serverLevelData.setWorld(this); -+ -+ if (biomeProvider != null) { -+ BiomeSource worldChunkManager = new CustomWorldChunkManager(this.getWorld(), biomeProvider, this.server.registryAccess().lookupOrThrow(Registries.BIOME), chunkgenerator.getBiomeSource()); // Paper - add vanillaBiomeProvider -+ if (chunkgenerator instanceof NoiseBasedChunkGenerator cga) { -+ chunkgenerator = new NoiseBasedChunkGenerator(worldChunkManager, cga.settings); -+ } else if (chunkgenerator instanceof FlatLevelSource cpf) { -+ chunkgenerator = new FlatLevelSource(cpf.settings(), worldChunkManager); -+ } -+ } -+ -+ if (gen != null) { -+ chunkgenerator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, chunkgenerator, gen); -+ } -+ // CraftBukkit end -+ boolean flag2 = minecraftserver.forceSynchronousWrites(); -+ DataFixer datafixer = minecraftserver.getFixerUpper(); -+ EntityPersistentStorage entitypersistentstorage = new EntityStorage(new SimpleRegionStorage(new RegionStorageInfo(convertable_conversionsession.getLevelId(), resourcekey, "entities"), convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, DataFixTypes.ENTITY_CHUNK), this, minecraftserver); -+ - this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage); -- StructureTemplateManager structuretemplatemanager = server.getStructureManager(); -- int j = server.getPlayerList().getViewDistance(); -- int k = server.getPlayerList().getSimulationDistance(); -+ StructureTemplateManager structuretemplatemanager = minecraftserver.getStructureManager(); -+ int j = this.spigotConfig.viewDistance; // Spigot -+ int k = this.spigotConfig.simulationDistance; // Spigot - PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; - - Objects.requireNonNull(this.entityManager); -- this.chunkSource = new ServerChunkCache(this, session, datafixer, structuretemplatemanager, workerExecutor, chunkgenerator, j, k, flag2, worldGenerationProgressListener, persistententitysectionmanager::updateChunkStatus, () -> { -- return server.overworld().getDataStorage(); -+ this.chunkSource = new ServerChunkCache(this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, persistententitysectionmanager::updateChunkStatus, () -> { -+ return minecraftserver.overworld().getDataStorage(); - }); - this.chunkSource.getGeneratorState().ensureStructuresGenerated(); - this.portalForcer = new PortalForcer(this); - this.updateSkyBrightness(); - this.prepareWeather(); -- this.getWorldBorder().setAbsoluteMaxSize(server.getAbsoluteMaxWorldSize()); -+ this.getWorldBorder().setAbsoluteMaxSize(minecraftserver.getAbsoluteMaxWorldSize()); - this.raids = (Raids) this.getDataStorage().computeIfAbsent(Raids.factory(this), Raids.getFileId(this.dimensionTypeRegistration())); -- if (!server.isSingleplayer()) { -- properties.setGameType(server.getDefaultGameType()); -+ if (!minecraftserver.isSingleplayer()) { -+ iworlddataserver.setGameType(minecraftserver.getDefaultGameType()); - } - -- long l = server.getWorldData().worldGenOptions().seed(); -+ long l = minecraftserver.getWorldData().worldGenOptions().seed(); - -- this.structureCheck = new StructureCheck(this.chunkSource.chunkScanner(), this.registryAccess(), server.getStructureManager(), worldKey, chunkgenerator, this.chunkSource.randomState(), this, chunkgenerator.getBiomeSource(), l, datafixer); -- this.structureManager = new StructureManager(this, server.getWorldData().worldGenOptions(), this.structureCheck); -- if (this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) { -- this.dragonFight = new EndDragonFight(this, l, server.getWorldData().endDragonFightData()); -+ this.structureCheck = new StructureCheck(this.chunkSource.chunkScanner(), this.registryAccess(), minecraftserver.getStructureManager(), this.getTypeKey(), chunkgenerator, this.chunkSource.randomState(), this, chunkgenerator.getBiomeSource(), l, datafixer); // Paper - Fix missing CB diff -+ this.structureManager = new StructureManager(this, this.serverLevelData.worldGenOptions(), this.structureCheck); // CraftBukkit -+ if ((this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) || env == org.bukkit.World.Environment.THE_END) { // CraftBukkit - Allow to create EnderDragonBattle in default and custom END -+ this.dragonFight = new EndDragonFight(this, this.serverLevelData.worldGenOptions().seed(), this.serverLevelData.endDragonFightData()); // CraftBukkit - } else { - this.dragonFight = null; - } - - this.sleepStatus = new SleepStatus(); - this.gameEventDispatcher = new GameEventDispatcher(this); -- this.randomSequences = (RandomSequences) Objects.requireNonNullElseGet(randomSequencesState, () -> { -+ this.randomSequences = (RandomSequences) Objects.requireNonNullElseGet(randomsequences, () -> { - return (RandomSequences) this.getDataStorage().computeIfAbsent(RandomSequences.factory(l), "random_sequences"); - }); -+ this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit - } - -+ // Paper start -+ @Override -+ public boolean hasChunk(int chunkX, int chunkZ) { -+ return this.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) != null; -+ } -+ // Paper end -+ - /** @deprecated */ - @Deprecated - @VisibleForTesting -@@ -273,8 +435,8 @@ - this.serverLevelData.setClearWeatherTime(clearDuration); - this.serverLevelData.setRainTime(rainDuration); - this.serverLevelData.setThunderTime(rainDuration); -- this.serverLevelData.setRaining(raining); -- this.serverLevelData.setThundering(thundering); -+ this.serverLevelData.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents -+ this.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents - } - - @Override -@@ -305,12 +467,20 @@ - long j; - - if (this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) { -+ // CraftBukkit start -+ j = this.levelData.getDayTime() + 24000L; -+ TimeSkipEvent event = new TimeSkipEvent(this.getWorld(), TimeSkipEvent.SkipReason.NIGHT_SKIP, (j - j % 24000L) - this.getDayTime()); - if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { -- j = this.levelData.getDayTime() + 24000L; -- this.setDayTime(j - j % 24000L); -+ this.getCraftServer().getPluginManager().callEvent(event); -+ if (!event.isCancelled()) { -+ this.setDayTime(this.getDayTime() + event.getSkipAmount()); -+ } - } - -- this.wakeUpAllPlayers(); -+ if (!event.isCancelled()) { -+ this.wakeUpAllPlayers(); -+ } -+ // CraftBukkit end - if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE) && this.isRaining()) { - this.resetWeatherCycle(); - } -@@ -325,9 +495,9 @@ - if (!this.isDebug() && flag) { - j = this.getGameTime(); - gameprofilerfiller.push("blockTicks"); -- this.blockTicks.tick(j, 65536, this::tickBlock); -+ this.blockTicks.tick(j, paperConfig().environment.maxBlockTicks, this::tickBlock); // Paper - configurable max block ticks - gameprofilerfiller.popPush("fluidTicks"); -- this.fluidTicks.tick(j, 65536, this::tickFluid); -+ this.fluidTicks.tick(j, paperConfig().environment.maxFluidTicks, this::tickFluid); // Paper - configurable max fluid ticks - gameprofilerfiller.pop(); - } - -@@ -345,7 +515,7 @@ - - this.handlingTick = false; - gameprofilerfiller.pop(); -- boolean flag1 = !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); -+ boolean flag1 = !paperConfig().unsupportedSettings.disableWorldTickingWhenEmpty || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players // Paper - restore this - - if (flag1) { - this.resetEmptyTime(); -@@ -359,6 +529,7 @@ - gameprofilerfiller.pop(); - } - -+ org.spigotmc.ActivationRange.activateEntities(this); // Spigot - this.entityTickList.forEach((entity) -> { - if (!entity.isRemoved()) { - if (!tickratemanager.isEntityFrozen(entity)) { -@@ -429,7 +600,7 @@ - - private void wakeUpAllPlayers() { - this.sleepStatus.removeAllSleepers(); -- ((List) this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList())).forEach((entityplayer) -> { -+ (this.players.stream().filter(LivingEntity::isSleeping).collect(Collectors.toList())).forEach((entityplayer) -> { // CraftBukkit - decompile error - entityplayer.stopSleepInBed(false, false); - }); - } -@@ -442,12 +613,12 @@ - ProfilerFiller gameprofilerfiller = Profiler.get(); - - gameprofilerfiller.push("thunder"); -- if (flag && this.isThundering() && this.random.nextInt(100000) == 0) { -+ if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder - BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); - - if (this.isRainingAt(blockposition)) { - DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition); -- boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * 0.01D && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); -+ boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper - Configurable spawn chances for skeleton horses - - if (flag1) { - SkeletonHorse entityhorseskeleton = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT); -@@ -456,7 +627,7 @@ - entityhorseskeleton.setTrap(true); - entityhorseskeleton.setAge(0); - entityhorseskeleton.setPos((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()); -- this.addFreshEntity(entityhorseskeleton); -+ this.addFreshEntity(entityhorseskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit - } - } - -@@ -465,18 +636,20 @@ - if (entitylightning != null) { - entitylightning.moveTo(Vec3.atBottomCenterOf(blockposition)); - entitylightning.setVisualOnly(flag1); -- this.addFreshEntity(entitylightning); -+ this.strikeLightning(entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause.WEATHER); // CraftBukkit - } - } - } - - gameprofilerfiller.popPush("iceandsnow"); - -+ if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow - for (int l = 0; l < randomTickSpeed; ++l) { - if (this.random.nextInt(48) == 0) { - this.tickPrecipitation(this.getBlockRandomPos(j, 0, k, 15)); - } - } -+ } // Paper - Option to disable ice and snow - - gameprofilerfiller.popPush("tickBlocks"); - if (randomTickSpeed > 0) { -@@ -521,7 +694,7 @@ - Biome biomebase = (Biome) this.getBiome(blockposition1).value(); - - if (biomebase.shouldFreeze(this, blockposition2)) { -- this.setBlockAndUpdate(blockposition2, Blocks.ICE.defaultBlockState()); -+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition2, Blocks.ICE.defaultBlockState(), null); // CraftBukkit - } - - if (this.isRaining()) { -@@ -537,10 +710,10 @@ - BlockState iblockdata1 = (BlockState) iblockdata.setValue(SnowLayerBlock.LAYERS, j + 1); - - Block.pushEntitiesUp(iblockdata, iblockdata1, this, blockposition1); -- this.setBlockAndUpdate(blockposition1, iblockdata1); -+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, iblockdata1, null); // CraftBukkit - } - } else { -- this.setBlockAndUpdate(blockposition1, Blocks.SNOW.defaultBlockState()); -+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit - } - } - -@@ -568,6 +741,11 @@ - } - - protected BlockPos findLightningTargetAround(BlockPos pos) { -+ // Paper start - Add methods to find targets for lightning strikes -+ return this.findLightningTargetAround(pos, false); -+ } -+ public BlockPos findLightningTargetAround(BlockPos pos, boolean returnNullWhenNoTarget) { -+ // Paper end - Add methods to find targets for lightning strikes - BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos); - Optional optional = this.findLightningRod(blockposition1); - -@@ -576,12 +754,13 @@ - } else { - AABB axisalignedbb = AABB.encapsulatingFullBlocks(blockposition1, blockposition1.atY(this.getMaxY() + 1)).inflate(3.0D); - List list = this.getEntitiesOfClass(LivingEntity.class, axisalignedbb, (entityliving) -> { -- return entityliving != null && entityliving.isAlive() && this.canSeeSky(entityliving.blockPosition()); -+ return entityliving != null && entityliving.isAlive() && this.canSeeSky(entityliving.blockPosition()) && !entityliving.isSpectator(); // Paper - Fix lightning being able to hit spectators (MC-262422) - }); - - if (!list.isEmpty()) { - return ((LivingEntity) list.get(this.random.nextInt(list.size()))).blockPosition(); - } else { -+ if (returnNullWhenNoTarget) return null; // Paper - Add methods to find targets for lightning strikes - if (blockposition1.getY() == this.getMinY() - 1) { - blockposition1 = blockposition1.above(2); - } -@@ -679,8 +858,8 @@ - this.serverLevelData.setThunderTime(j); - this.serverLevelData.setRainTime(k); - this.serverLevelData.setClearWeatherTime(i); -- this.serverLevelData.setThundering(flag1); -- this.serverLevelData.setRaining(flag2); -+ this.serverLevelData.setThundering(flag1, org.bukkit.event.weather.ThunderChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents -+ this.serverLevelData.setRaining(flag2, org.bukkit.event.weather.WeatherChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents - } - - this.oThunderLevel = this.thunderLevel; -@@ -701,33 +880,67 @@ - this.rainLevel = Mth.clamp(this.rainLevel, 0.0F, 1.0F); - } - -+ /* CraftBukkit start - if (this.oRainLevel != this.rainLevel) { -- this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.rainLevel), this.dimension()); -+ this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.RAIN_LEVEL_CHANGE, this.rainLevel), this.dimension()); - } - - if (this.oThunderLevel != this.thunderLevel) { -- this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel), this.dimension()); -+ this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.THUNDER_LEVEL_CHANGE, this.thunderLevel), this.dimension()); - } - - if (flag != this.isRaining()) { - if (flag) { -- this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.STOP_RAINING, 0.0F)); -- } else { -- this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0F)); -+ this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.STOP_RAINING, 0.0F)); -+ } else { -+ this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.START_RAINING, 0.0F)); - } - -- this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.rainLevel)); -- this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel)); -+ this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.RAIN_LEVEL_CHANGE, this.rainLevel)); -+ this.server.getPlayerList().broadcastAll(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.THUNDER_LEVEL_CHANGE, this.thunderLevel)); - } -+ // */ -+ for (int idx = 0; idx < this.players.size(); ++idx) { -+ if (((ServerPlayer) this.players.get(idx)).level() == this) { -+ ((ServerPlayer) this.players.get(idx)).tickWeather(); -+ } -+ } - -+ if (flag != this.isRaining()) { -+ // Only send weather packets to those affected -+ for (int idx = 0; idx < this.players.size(); ++idx) { -+ if (((ServerPlayer) this.players.get(idx)).level() == this) { -+ ((ServerPlayer) this.players.get(idx)).setPlayerWeather((!flag ? WeatherType.DOWNFALL : WeatherType.CLEAR), false); -+ } -+ } -+ } -+ for (int idx = 0; idx < this.players.size(); ++idx) { -+ if (((ServerPlayer) this.players.get(idx)).level() == this) { -+ ((ServerPlayer) this.players.get(idx)).updateWeather(this.oRainLevel, this.rainLevel, this.oThunderLevel, this.thunderLevel); -+ } -+ } -+ // CraftBukkit end -+ - } - - @VisibleForTesting - public void resetWeatherCycle() { -- this.serverLevelData.setRainTime(0); -- this.serverLevelData.setRaining(false); -- this.serverLevelData.setThunderTime(0); -- this.serverLevelData.setThundering(false); -+ // CraftBukkit start -+ this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents -+ // If we stop due to everyone sleeping we should reset the weather duration to some other random value. -+ // Not that everyone ever manages to get the whole server to sleep at the same time.... -+ if (!this.serverLevelData.isRaining()) { -+ this.serverLevelData.setRainTime(0); -+ } -+ // CraftBukkit end -+ this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents -+ // CraftBukkit start -+ // If we stop due to everyone sleeping we should reset the weather duration to some other random value. -+ // Not that everyone ever manages to get the whole server to sleep at the same time.... -+ if (!this.serverLevelData.isThundering()) { -+ this.serverLevelData.setThunderTime(0); -+ } -+ // CraftBukkit end - } - - public void resetEmptyTime() { -@@ -754,6 +967,13 @@ - } - - public void tickNonPassenger(Entity entity) { -+ // Spigot start -+ if (!org.spigotmc.ActivationRange.checkIfActive(entity)) { -+ entity.tickCount++; -+ entity.inactiveTick(); -+ return; -+ } -+ // Spigot end - entity.setOldPosAndRot(); - ProfilerFiller gameprofilerfiller = Profiler.get(); - -@@ -763,6 +983,7 @@ - }); - gameprofilerfiller.incrementCounter("tickNonPassenger"); - entity.tick(); -+ entity.postTick(); // CraftBukkit - gameprofilerfiller.pop(); - Iterator iterator = entity.getPassengers().iterator(); - -@@ -786,6 +1007,7 @@ - }); - gameprofilerfiller.incrementCounter("tickPassenger"); - passenger.rideTick(); -+ passenger.postTick(); // CraftBukkit - gameprofilerfiller.pop(); - Iterator iterator = passenger.getPassengers().iterator(); - -@@ -810,6 +1032,7 @@ - ServerChunkCache chunkproviderserver = this.getChunkSource(); - - if (!savingDisabled) { -+ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(this.getWorld())); // CraftBukkit - if (progressListener != null) { - progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel")); - } -@@ -827,11 +1050,19 @@ - } - - } -+ -+ // CraftBukkit start - moved from MinecraftServer.saveChunks -+ ServerLevel worldserver1 = this; -+ -+ this.serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings()); -+ this.serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save(this.registryAccess())); -+ this.convertable.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData()); -+ // CraftBukkit end - } - - private void saveLevelData(boolean flush) { - if (this.dragonFight != null) { -- this.server.getWorldData().setEndDragonFightData(this.dragonFight.saveData()); -+ this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit - } - - DimensionDataStorage worldpersistentdata = this.getChunkSource().getDataStorage(); -@@ -903,18 +1134,40 @@ - - @Override - public boolean addFreshEntity(Entity entity) { -- return this.addEntity(entity); -+ // CraftBukkit start -+ return this.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.DEFAULT); - } - -+ @Override -+ public boolean addFreshEntity(Entity entity, CreatureSpawnEvent.SpawnReason reason) { -+ return this.addEntity(entity, reason); -+ // CraftBukkit end -+ } -+ - public boolean addWithUUID(Entity entity) { -- return this.addEntity(entity); -+ // CraftBukkit start -+ return this.addWithUUID(entity, CreatureSpawnEvent.SpawnReason.DEFAULT); -+ } -+ -+ public boolean addWithUUID(Entity entity, CreatureSpawnEvent.SpawnReason reason) { -+ return this.addEntity(entity, reason); -+ // CraftBukkit end - } - - public void addDuringTeleport(Entity entity) { -+ // CraftBukkit start -+ // SPIGOT-6415: Don't call spawn event for entities which travel trough worlds, -+ // since it is only an implementation detail, that a new entity is created when -+ // they are traveling between worlds. -+ this.addDuringTeleport(entity, null); -+ } -+ -+ public void addDuringTeleport(Entity entity, CreatureSpawnEvent.SpawnReason reason) { -+ // CraftBukkit end - if (entity instanceof ServerPlayer entityplayer) { - this.addPlayer(entityplayer); - } else { -- this.addEntity(entity); -+ this.addEntity(entity, reason); // CraftBukkit - } - - } -@@ -939,41 +1192,116 @@ - this.entityManager.addNewEntity(player); - } - -- private boolean addEntity(Entity entity) { -+ // CraftBukkit start -+ private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) { -+ org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot -+ entity.generation = false; // Paper - Don't fire sync event during generation; Reset flag if it was added during a ServerLevel generation process -+ // Paper start - extra debug info -+ if (entity.valid) { -+ MinecraftServer.LOGGER.error("Attempted Double World add on {}", entity, new Throwable()); -+ return true; -+ } -+ // Paper end - extra debug info -+ if (entity.spawnReason == null) entity.spawnReason = spawnReason; // Paper - Entity#getEntitySpawnReason - if (entity.isRemoved()) { -- ServerLevel.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityType.getKey(entity.getType())); -+ // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit - return false; - } else { -+ if (entity instanceof net.minecraft.world.entity.item.ItemEntity itemEntity && itemEntity.getItem().isEmpty()) return false; // Paper - Prevent empty items from being added -+ // Paper start - capture all item additions to the world -+ if (captureDrops != null && entity instanceof net.minecraft.world.entity.item.ItemEntity) { -+ captureDrops.add((net.minecraft.world.entity.item.ItemEntity) entity); -+ return true; -+ } -+ // Paper end - capture all item additions to the world -+ // SPIGOT-6415: Don't call spawn event when reason is null. For example when an entity teleports to a new world. -+ if (spawnReason != null && !CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) { -+ return false; -+ } -+ // CraftBukkit end -+ - return this.entityManager.addNewEntity(entity); - } - } - - public boolean tryAddFreshEntityWithPassengers(Entity entity) { -- Stream stream = entity.getSelfAndPassengers().map(Entity::getUUID); -+ // CraftBukkit start -+ return this.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT); -+ } -+ -+ public boolean tryAddFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) { -+ // CraftBukkit end -+ Stream stream = entity.getSelfAndPassengers().map(Entity::getUUID); // CraftBukkit - decompile error - PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; - - Objects.requireNonNull(this.entityManager); - if (stream.anyMatch(persistententitysectionmanager::isLoaded)) { - return false; - } else { -- this.addFreshEntityWithPassengers(entity); -+ this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit - return true; - } - } - - public void unload(LevelChunk chunk) { -+ // Spigot Start -+ for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) { -+ if (tileentity instanceof net.minecraft.world.Container) { -+ // Paper start - this area looks like it can load chunks, change the behavior -+ // chests for example can apply physics to the world -+ // so instead we just change the active container and call the event -+ for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) { -+ ((org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason -+ } -+ // Paper end - this area looks like it can load chunks, change the behavior -+ } -+ } -+ // Spigot End - chunk.clearAllBlockEntities(); - chunk.unregisterTickContainerFromLevel(this); - } - - public void removePlayerImmediately(ServerPlayer player, Entity.RemovalReason reason) { -- player.remove(reason); -+ player.remove(reason, null); // CraftBukkit - add Bukkit remove cause - } - -+ // CraftBukkit start -+ public boolean strikeLightning(Entity entitylightning) { -+ return this.strikeLightning(entitylightning, LightningStrikeEvent.Cause.UNKNOWN); -+ } -+ -+ public boolean strikeLightning(Entity entitylightning, LightningStrikeEvent.Cause cause) { -+ LightningStrikeEvent lightning = CraftEventFactory.callLightningStrikeEvent((org.bukkit.entity.LightningStrike) entitylightning.getBukkitEntity(), cause); -+ -+ if (lightning.isCancelled()) { -+ return false; -+ } -+ -+ return this.addFreshEntity(entitylightning); -+ } -+ // CraftBukkit end -+ - @Override - public void destroyBlockProgress(int entityId, BlockPos pos, int progress) { - Iterator iterator = this.server.getPlayerList().getPlayers().iterator(); - -+ // CraftBukkit start -+ Player entityhuman = null; -+ Entity entity = this.getEntity(entityId); -+ if (entity instanceof Player) entityhuman = (Player) entity; -+ // CraftBukkit end -+ -+ // Paper start - Add BlockBreakProgressUpdateEvent -+ // If a plugin is using this method to send destroy packets for a client-side only entity id, no block progress occurred on the server. -+ // Hence, do not call the event. -+ if (entity != null) { -+ float progressFloat = Mth.clamp(progress, 0, 10) / 10.0f; -+ org.bukkit.craftbukkit.block.CraftBlock bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(this, pos); -+ new io.papermc.paper.event.block.BlockBreakProgressUpdateEvent(bukkitBlock, progressFloat, entity.getBukkitEntity()) -+ .callEvent(); -+ } -+ // Paper end - Add BlockBreakProgressUpdateEvent -+ - while (iterator.hasNext()) { - ServerPlayer entityplayer = (ServerPlayer) iterator.next(); - -@@ -982,6 +1310,12 @@ - double d1 = (double) pos.getY() - entityplayer.getY(); - double d2 = (double) pos.getZ() - entityplayer.getZ(); - -+ // CraftBukkit start -+ if (entityhuman != null && !entityplayer.getBukkitEntity().canSee(entityhuman.getBukkitEntity())) { -+ continue; -+ } -+ // CraftBukkit end -+ - if (d0 * d0 + d1 * d1 + d2 * d2 < 1024.0D) { - entityplayer.connection.send(new ClientboundBlockDestructionPacket(entityId, pos, progress)); - } -@@ -1030,7 +1364,7 @@ - - @Override - public void levelEvent(@Nullable Player player, int eventId, BlockPos pos, int data) { -- this.server.getPlayerList().broadcast(player, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), 64.0D, this.dimension(), new ClientboundLevelEventPacket(eventId, pos, data, false)); -+ this.server.getPlayerList().broadcast(player, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), 64.0D, this.dimension(), new ClientboundLevelEventPacket(eventId, pos, data, false)); // Paper - diff on change (the 64.0 distance is used as defaults for sound ranges in spigot config for ender dragon, end portal and wither) - } - - public int getLogicalHeight() { -@@ -1039,6 +1373,11 @@ - - @Override - public void gameEvent(Holder event, Vec3 emitterPos, GameEvent.Context emitter) { -+ // Paper start - Prevent GameEvents being fired from unloaded chunks -+ if (this.getChunkIfLoadedImmediately((Mth.floor(emitterPos.x) >> 4), (Mth.floor(emitterPos.z) >> 4)) == null) { -+ return; -+ } -+ // Paper end - Prevent GameEvents being fired from unloaded chunks - this.gameEventDispatcher.post(event, emitterPos, emitter); - } - -@@ -1052,6 +1391,7 @@ - - this.getChunkSource().blockChanged(pos); - this.pathTypesByPosCache.invalidate(pos); -+ if (this.paperConfig().misc.updatePathfindingOnBlockUpdate) { // Paper - option to disable pathfinding updates - VoxelShape voxelshape = oldState.getCollisionShape(this, pos); - VoxelShape voxelshape1 = newState.getCollisionShape(this, pos); - -@@ -1060,7 +1400,18 @@ - Iterator iterator = this.navigatingMobs.iterator(); - - while (iterator.hasNext()) { -- Mob entityinsentient = (Mob) iterator.next(); -+ // CraftBukkit start - fix SPIGOT-6362 -+ Mob entityinsentient; -+ try { -+ entityinsentient = (Mob) iterator.next(); -+ } catch (java.util.ConcurrentModificationException ex) { -+ // This can happen because the pathfinder update below may trigger a chunk load, which in turn may cause more navigators to register -+ // In this case we just run the update again across all the iterators as the chunk will then be loaded -+ // As this is a relative edge case it is much faster than copying navigators (on either read or write) -+ this.sendBlockUpdated(pos, oldState, newState, flags); -+ return; -+ } -+ // CraftBukkit end - PathNavigation navigationabstract = entityinsentient.getNavigation(); - - if (navigationabstract.shouldRecomputePath(pos)) { -@@ -1082,15 +1433,18 @@ - } - - } -+ } // Paper - option to disable pathfinding updates - } - - @Override - public void updateNeighborsAt(BlockPos pos, Block block) { -+ if (captureBlockStates) { return; } // Paper - Cancel all physics during placement - this.updateNeighborsAt(pos, block, ExperimentalRedstoneUtils.initialOrientation(this, (Direction) null, (Direction) null)); - } - - @Override - public void updateNeighborsAt(BlockPos pos, Block sourceBlock, @Nullable Orientation orientation) { -+ if (captureBlockStates) { return; } // Paper - Cancel all physics during placement - this.neighborUpdater.updateNeighborsAtExceptFromFacing(pos, sourceBlock, (Direction) null, orientation); - } - -@@ -1126,9 +1480,20 @@ - - @Override - public void explode(@Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, double x, double y, double z, float power, boolean createFire, Level.ExplosionInteraction explosionSourceType, ParticleOptions smallParticle, ParticleOptions largeParticle, Holder soundEvent) { -+ // CraftBukkit start -+ this.explode0(entity, damageSource, behavior, x, y, z, power, createFire, explosionSourceType, smallParticle, largeParticle, soundEvent); -+ } -+ -+ public ServerExplosion explode0(@Nullable Entity entity, @Nullable DamageSource damagesource, @Nullable ExplosionDamageCalculator explosiondamagecalculator, double d0, double d1, double d2, float f, boolean flag, Level.ExplosionInteraction world_a, ParticleOptions particleparam, ParticleOptions particleparam1, Holder holder) { -+ // Paper start - Allow explosions to damage source -+ return this.explode0(entity, damagesource, explosiondamagecalculator, d0, d1, d2, f, flag, world_a, particleparam, particleparam1, holder, null); -+ } -+ public ServerExplosion explode0(@Nullable Entity entity, @Nullable DamageSource damagesource, @Nullable ExplosionDamageCalculator explosiondamagecalculator, double d0, double d1, double d2, float f, boolean flag, Level.ExplosionInteraction world_a, ParticleOptions particleparam, ParticleOptions particleparam1, Holder holder, java.util.function.Consumer configurator) { -+ // Paper end - Allow explosions to damage source -+ // CraftBukkit end - Explosion.BlockInteraction explosion_effect; - -- switch (explosionSourceType) { -+ switch (world_a) { - case NONE: - explosion_effect = Explosion.BlockInteraction.KEEP; - break; -@@ -1144,16 +1509,27 @@ - case TRIGGER: - explosion_effect = Explosion.BlockInteraction.TRIGGER_BLOCK; - break; -+ // CraftBukkit start - handle custom explosion type -+ case STANDARD: -+ explosion_effect = Explosion.BlockInteraction.DESTROY; -+ break; -+ // CraftBukkit end - default: - throw new MatchException((String) null, (Throwable) null); - } - - Explosion.BlockInteraction explosion_effect1 = explosion_effect; -- Vec3 vec3d = new Vec3(x, y, z); -- ServerExplosion serverexplosion = new ServerExplosion(this, entity, damageSource, behavior, vec3d, power, createFire, explosion_effect1); -+ Vec3 vec3d = new Vec3(d0, d1, d2); -+ ServerExplosion serverexplosion = new ServerExplosion(this, entity, damagesource, explosiondamagecalculator, vec3d, f, flag, explosion_effect1); -+ if (configurator != null) configurator.accept(serverexplosion);// Paper - Allow explosions to damage source - - serverexplosion.explode(); -- ParticleOptions particleparam2 = serverexplosion.isSmall() ? smallParticle : largeParticle; -+ // CraftBukkit start -+ if (serverexplosion.wasCanceled) { -+ return serverexplosion; -+ } -+ // CraftBukkit end -+ ParticleOptions particleparam2 = serverexplosion.isSmall() ? particleparam : particleparam1; - Iterator iterator = this.players.iterator(); - - while (iterator.hasNext()) { -@@ -1162,10 +1538,11 @@ - if (entityplayer.distanceToSqr(vec3d) < 4096.0D) { - Optional optional = Optional.ofNullable((Vec3) serverexplosion.getHitPlayers().get(entityplayer)); - -- entityplayer.connection.send(new ClientboundExplodePacket(vec3d, optional, particleparam2, soundEvent)); -+ entityplayer.connection.send(new ClientboundExplodePacket(vec3d, optional, particleparam2, holder)); - } - } - -+ return serverexplosion; // CraftBukkit - } - - private Explosion.BlockInteraction getDestroyType(GameRules.Key decayRule) { -@@ -1226,17 +1603,29 @@ - } - - public int sendParticles(T parameters, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double speed) { -- return this.sendParticles(parameters, false, false, x, y, z, count, offsetX, offsetY, offsetZ, speed); -+ return this.sendParticlesSource(null, parameters, false, false, x, y, z, count, offsetX, offsetY, offsetZ, speed); // CraftBukkit - visibility api support - } - - public int sendParticles(T parameters, boolean force, boolean important, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double speed) { -- ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(parameters, force, important, x, y, z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) speed, count); -- int j = 0; -+ return this.sendParticlesSource(null, parameters, force, important, x, y, z, count, offsetX, offsetY, offsetZ, speed); // CraftBukkit - visibility api support -+ } - -- for (int k = 0; k < this.players.size(); ++k) { -- ServerPlayer entityplayer = (ServerPlayer) this.players.get(k); -+ // CraftBukkit start - visibility api support -+ public int sendParticlesSource(ServerPlayer sender, T t0, boolean flag, boolean flag1, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6) { -+ // Paper start - Particle API -+ return this.sendParticlesSource(this.players, sender, t0, flag, flag1, d0, d1, d2, i, d3, d4, d5, d6); -+ } -+ public int sendParticlesSource(List receivers, @Nullable ServerPlayer sender, T t0, boolean flag, boolean flag1, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6) { -+ // Paper end - Particle API -+ // CraftBukkit end -+ ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(t0, flag, flag1, d0, d1, d2, (float) d3, (float) d4, (float) d5, (float) d6, i); -+ int j = 0; - -- if (this.sendParticles(entityplayer, force, x, y, z, packetplayoutworldparticles)) { -+ for (Player entityhuman : receivers) { // Paper - Particle API -+ ServerPlayer entityplayer = (ServerPlayer) entityhuman; // Paper - Particle API -+ if (sender != null && !entityplayer.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; // CraftBukkit -+ -+ if (this.sendParticles(entityplayer, flag, d0, d1, d2, packetplayoutworldparticles)) { - ++j; - } - } -@@ -1292,7 +1681,7 @@ - - @Nullable - public BlockPos findNearestMapStructure(TagKey structureTag, BlockPos pos, int radius, boolean skipReferencedStructures) { -- if (!this.server.getWorldData().worldGenOptions().generateStructures()) { -+ if (!this.serverLevelData.worldGenOptions().generateStructures()) { // CraftBukkit - return null; - } else { - Optional> optional = this.registryAccess().lookupOrThrow(Registries.STRUCTURE).get(structureTag); -@@ -1334,11 +1723,38 @@ - @Nullable - @Override - public MapItemSavedData getMapData(MapId id) { -- return (MapItemSavedData) this.getServer().overworld().getDataStorage().get(MapItemSavedData.factory(), id.key()); -+ // Paper start - Call missing map initialize event and set id -+ final DimensionDataStorage storage = this.getServer().overworld().getDataStorage(); -+ -+ final Optional cacheEntry = storage.cache.get(id.key()); -+ if (cacheEntry == null) { // Cache did not contain, try to load and may init -+ final MapItemSavedData worldmap = storage.get(MapItemSavedData.factory(), id.key()); // get populates the cache -+ if (worldmap != null) { // map was read, init it and return -+ worldmap.id = id; -+ new MapInitializeEvent(worldmap.mapView).callEvent(); -+ return worldmap; -+ } -+ -+ return null; // Map does not exist, reading failed. -+ } -+ -+ // Cache entry exists, update it with the id ref and return. -+ if (cacheEntry.orElse(null) instanceof final MapItemSavedData mapItemSavedData) { -+ mapItemSavedData.id = id; -+ return mapItemSavedData; -+ } -+ -+ return null; -+ // Paper end - Call missing map initialize event and set id - } - - @Override - public void setMapData(MapId id, MapItemSavedData state) { -+ // CraftBukkit start -+ state.id = id; -+ MapInitializeEvent event = new MapInitializeEvent(state.mapView); -+ Bukkit.getServer().getPluginManager().callEvent(event); -+ // CraftBukkit end - this.getServer().overworld().getDataStorage().set(id.key(), state); - } - -@@ -1352,18 +1768,28 @@ - float f1 = this.levelData.getSpawnAngle(); - - if (!blockposition1.equals(pos) || f1 != angle) { -+ org.bukkit.Location prevSpawnLoc = this.getWorld().getSpawnLocation(); // Paper - Call SpawnChangeEvent - this.levelData.setSpawn(pos, angle); -+ new org.bukkit.event.world.SpawnChangeEvent(this.getWorld(), prevSpawnLoc).callEvent(); // Paper - Call SpawnChangeEvent - this.getServer().getPlayerList().broadcastAll(new ClientboundSetDefaultSpawnPositionPacket(pos, angle)); - } - - if (this.lastSpawnChunkRadius > 1) { -- this.getChunkSource().removeRegionTicket(TicketType.START, new ChunkPos(blockposition1), this.lastSpawnChunkRadius, Unit.INSTANCE); -+ // Paper start - allow disabling gamerule limits -+ for (ChunkPos chunkPos : io.papermc.paper.util.MCUtil.getSpiralOutChunks(blockposition1, this.lastSpawnChunkRadius - 2)) { -+ this.getChunkSource().removeTicketAtLevel(TicketType.START, chunkPos, net.minecraft.server.level.ChunkLevel.ENTITY_TICKING_LEVEL, Unit.INSTANCE); -+ } -+ // Paper end - allow disabling gamerule limits - } - - int i = this.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS) + 1; - - if (i > 1) { -- this.getChunkSource().addRegionTicket(TicketType.START, new ChunkPos(pos), i, Unit.INSTANCE); -+ // Paper start - allow disabling gamerule limits -+ for (ChunkPos chunkPos : io.papermc.paper.util.MCUtil.getSpiralOutChunks(pos, i - 2)) { -+ this.getChunkSource().addTicketAtLevel(TicketType.START, chunkPos, net.minecraft.server.level.ChunkLevel.ENTITY_TICKING_LEVEL, Unit.INSTANCE); -+ } -+ // Paper end - allow disabling gamerule limits - } - - this.lastSpawnChunkRadius = i; -@@ -1419,6 +1845,11 @@ - }); - optional1.ifPresent((holder) -> { - this.getServer().execute(() -> { -+ // Paper start - Remove stale POIs -+ if (optional.isEmpty() && this.getPoiManager().exists(blockposition1, poiType -> true)) { -+ this.getPoiManager().remove(blockposition1); -+ } -+ // Paper end - Remove stale POIs - this.getPoiManager().add(blockposition1, holder); - DebugPackets.sendPoiAddedPacket(this, blockposition1); - }); -@@ -1649,6 +2080,11 @@ - @Override - public void blockUpdated(BlockPos pos, Block block) { - if (!this.isDebug()) { -+ // CraftBukkit start -+ if (this.populating) { -+ return; -+ } -+ // CraftBukkit end - this.updateNeighborsAt(pos, block); - } - -@@ -1668,12 +2104,12 @@ - } - - public boolean isFlat() { -- return this.server.getWorldData().isFlatWorld(); -+ return this.serverLevelData.isFlatWorld(); // CraftBukkit - } - - @Override - public long getSeed() { -- return this.server.getWorldData().worldGenOptions().seed(); -+ return this.serverLevelData.worldGenOptions().seed(); // CraftBukkit - } - - @Nullable -@@ -1696,7 +2132,7 @@ - private static String getTypeCount(Iterable items, Function classifier) { - try { - Object2IntOpenHashMap object2intopenhashmap = new Object2IntOpenHashMap(); -- Iterator iterator = items.iterator(); -+ Iterator iterator = items.iterator(); // CraftBukkit - decompile error - - while (iterator.hasNext()) { - T t0 = iterator.next(); -@@ -1705,7 +2141,7 @@ - object2intopenhashmap.addTo(s, 1); - } - -- return (String) object2intopenhashmap.object2IntEntrySet().stream().sorted(Comparator.comparing(Entry::getIntValue).reversed()).limit(5L).map((entry) -> { -+ return (String) object2intopenhashmap.object2IntEntrySet().stream().sorted(Comparator.comparing(Entry::getIntValue).reversed()).limit(5L).map((entry) -> { // CraftBukkit - decompile error - String s1 = (String) entry.getKey(); - - return s1 + ":" + entry.getIntValue(); -@@ -1717,6 +2153,7 @@ - - @Override - public LevelEntityGetter getEntities() { -+ org.spigotmc.AsyncCatcher.catchOp("Chunk getEntities call"); // Spigot - return this.entityManager.getEntityGetter(); - } - -@@ -1800,7 +2237,28 @@ - - public GameRules getGameRules() { - return this.serverLevelData.getGameRules(); -+ } -+ -+ // Paper start - respect global sound events gamerule -+ public List getPlayersForGlobalSoundGamerule() { -+ return this.getGameRules().getBoolean(GameRules.RULE_GLOBAL_SOUND_EVENTS) ? ((ServerLevel) this).getServer().getPlayerList().players : ((ServerLevel) this).players(); -+ } -+ -+ public double getGlobalSoundRangeSquared(java.util.function.Function rangeFunction) { -+ final double range = rangeFunction.apply(this.spigotConfig); -+ return range <= 0 ? 64.0 * 64.0 : range * range; // 64 is taken from default in ServerLevel#levelEvent -+ } -+ // Paper end - respect global sound events gamerule -+ // Paper start - notify observers even if grow failed -+ public void checkCapturedTreeStateForObserverNotify(final BlockPos pos, final org.bukkit.craftbukkit.block.CraftBlockState craftBlockState) { -+ // notify observers if the block state is the same and the Y level equals the original y level (for mega trees) -+ // blocks at the same Y level with the same state can be assumed to be saplings which trigger observers regardless of if the -+ // tree grew or not -+ if (craftBlockState.getPosition().getY() == pos.getY() && this.getBlockState(craftBlockState.getPosition()) == craftBlockState.getHandle()) { -+ this.notifyAndUpdatePhysics(craftBlockState.getPosition(), null, craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getHandle(), craftBlockState.getFlag(), 512); -+ } - } -+ // Paper end - notify observers even if grow failed - - @Override - public CrashReportCategory fillReportDetails(CrashReport report) { -@@ -1828,22 +2286,30 @@ - } - - public void onTickingStart(Entity entity) { -+ if (entity instanceof net.minecraft.world.entity.Marker && !paperConfig().entities.markers.tick) return; // Paper - Configurable marker ticking - ServerLevel.this.entityTickList.add(entity); - } - - public void onTickingEnd(Entity entity) { - ServerLevel.this.entityTickList.remove(entity); -+ // Paper start - Reset pearls when they stop being ticked -+ if (ServerLevel.this.paperConfig().fixes.disableUnloadedChunkEnderpearlExploit && ServerLevel.this.paperConfig().misc.legacyEnderPearlBehavior && entity instanceof net.minecraft.world.entity.projectile.ThrownEnderpearl pearl) { -+ pearl.cachedOwner = null; -+ pearl.ownerUUID = null; -+ } -+ // Paper end - Reset pearls when they stop being ticked - } - - public void onTrackingStart(Entity entity) { -- ServerLevel.this.getChunkSource().addEntity(entity); -+ org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot -+ // ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server; moved down below valid=true - if (entity instanceof ServerPlayer entityplayer) { - ServerLevel.this.players.add(entityplayer); - ServerLevel.this.updateSleepingPlayerList(); - } - - if (entity instanceof Mob entityinsentient) { -- if (ServerLevel.this.isUpdatingNavigations) { -+ if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning - String s = "onTrackingStart called during navigation iteration"; - - Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")); -@@ -1864,9 +2330,58 @@ - } - - entity.updateDynamicGameEventListener(DynamicGameEventListener::add); -+ entity.inWorld = true; // CraftBukkit - Mark entity as in world -+ entity.valid = true; // CraftBukkit -+ ServerLevel.this.getChunkSource().addEntity(entity); // Paper - ignore and warn about illegal addEntity calls instead of crashing server -+ // Paper start - Entity origin API -+ if (entity.getOriginVector() == null) { -+ entity.setOrigin(entity.getBukkitEntity().getLocation()); -+ } -+ // Default to current world if unknown, gross assumption but entities rarely change world -+ if (entity.getOriginWorld() == null) { -+ entity.setOrigin(entity.getOriginVector().toLocation(getWorld())); -+ } -+ // Paper end - Entity origin API -+ new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(entity.getBukkitEntity(), ServerLevel.this.getWorld()).callEvent(); // Paper - fire while valid - } - - public void onTrackingEnd(Entity entity) { -+ org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot -+ // Spigot start -+ if ( entity instanceof Player ) -+ { -+ com.google.common.collect.Streams.stream( ServerLevel.this.getServer().getAllLevels() ).map( ServerLevel::getDataStorage ).forEach( (worldData) -> -+ { -+ for (Object o : worldData.cache.values() ) -+ { -+ if ( o instanceof MapItemSavedData ) -+ { -+ MapItemSavedData map = (MapItemSavedData) o; -+ map.carriedByPlayers.remove( (Player) entity ); -+ for ( Iterator iter = (Iterator) map.carriedBy.iterator(); iter.hasNext(); ) -+ { -+ if ( iter.next().player == entity ) -+ { -+ iter.remove(); -+ } -+ } -+ } -+ } -+ } ); -+ } -+ // Spigot end -+ // Spigot Start -+ if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message -+ // Paper start - Fix merchant inventory not closing on entity removal -+ if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { -+ merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); -+ } -+ // Paper end - Fix merchant inventory not closing on entity removal -+ for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((org.bukkit.inventory.InventoryHolder) entity.getBukkitEntity()).getInventory().getViewers())) { -+ h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason -+ } -+ } -+ // Spigot End - ServerLevel.this.getChunkSource().removeEntity(entity); - if (entity instanceof ServerPlayer entityplayer) { - ServerLevel.this.players.remove(entityplayer); -@@ -1874,7 +2389,7 @@ - } - - if (entity instanceof Mob entityinsentient) { -- if (ServerLevel.this.isUpdatingNavigations) { -+ if (false && ServerLevel.this.isUpdatingNavigations) { // Paper - Remove unnecessary onTrackingStart during navigation warning - String s = "onTrackingStart called during navigation iteration"; - - Util.logAndPauseIfInIde("onTrackingStart called during navigation iteration", new IllegalStateException("onTrackingStart called during navigation iteration")); -@@ -1895,10 +2410,27 @@ - } - - entity.updateDynamicGameEventListener(DynamicGameEventListener::remove); -+ // CraftBukkit start -+ entity.valid = false; -+ if (!(entity instanceof ServerPlayer)) { -+ for (ServerPlayer player : ServerLevel.this.server.getPlayerList().players) { // Paper - call onEntityRemove for all online players -+ player.getBukkitEntity().onEntityRemove(entity); -+ } -+ } -+ // CraftBukkit end -+ new com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent(entity.getBukkitEntity(), ServerLevel.this.getWorld()).callEvent(); // Paper - fire while valid - } - - public void onSectionChange(Entity entity) { - entity.updateDynamicGameEventListener(DynamicGameEventListener::move); - } - } -+ -+ // Paper start - check global player list where appropriate -+ @Override -+ @Nullable -+ public Player getGlobalPlayerByUUID(UUID uuid) { -+ return this.server.getPlayerList().getPlayer(uuid); -+ } -+ // Paper end - check global player list where appropriate - } diff --git a/paper-server/patches/unapplied/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/unapplied/net/minecraft/server/level/ServerPlayer.java.patch deleted file mode 100644 index 9daf2af366..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/server/level/ServerPlayer.java.patch +++ /dev/null @@ -1,1891 +0,0 @@ ---- a/net/minecraft/server/level/ServerPlayer.java -+++ b/net/minecraft/server/level/ServerPlayer.java -@@ -103,10 +103,6 @@ - import net.minecraft.util.Unit; - import net.minecraft.util.profiling.Profiler; - import net.minecraft.util.profiling.ProfilerFiller; --import net.minecraft.world.Container; --import net.minecraft.world.Difficulty; --import net.minecraft.world.InteractionHand; --import net.minecraft.world.MenuProvider; - import net.minecraft.world.damagesource.DamageSource; - import net.minecraft.world.damagesource.DamageTypes; - import net.minecraft.world.effect.MobEffectInstance; -@@ -135,15 +131,16 @@ - import net.minecraft.world.entity.player.ChatVisiblity; - import net.minecraft.world.entity.player.Input; - import net.minecraft.world.entity.player.Inventory; --import net.minecraft.world.entity.player.Player; - import net.minecraft.world.entity.projectile.AbstractArrow; - import net.minecraft.world.entity.projectile.ThrownEnderpearl; - import net.minecraft.world.entity.vehicle.AbstractBoat; - import net.minecraft.world.entity.vehicle.AbstractMinecart; -+import net.minecraft.world.food.FoodData; - import net.minecraft.world.inventory.AbstractContainerMenu; - import net.minecraft.world.inventory.ContainerListener; - import net.minecraft.world.inventory.ContainerSynchronizer; - import net.minecraft.world.inventory.HorseInventoryMenu; -+import net.minecraft.world.inventory.InventoryMenu; - import net.minecraft.world.inventory.ResultSlot; - import net.minecraft.world.inventory.Slot; - import net.minecraft.world.item.Item; -@@ -154,8 +151,6 @@ - import net.minecraft.world.item.WrittenBookItem; - import net.minecraft.world.item.crafting.Recipe; - import net.minecraft.world.item.crafting.RecipeHolder; --import net.minecraft.world.item.enchantment.EnchantmentHelper; --import net.minecraft.world.item.trading.MerchantOffers; - import net.minecraft.world.level.ChunkPos; - import net.minecraft.world.level.GameRules; - import net.minecraft.world.level.GameType; -@@ -163,12 +158,14 @@ - import net.minecraft.world.level.biome.BiomeManager; - import net.minecraft.world.level.block.BedBlock; - import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.ChestBlock; - import net.minecraft.world.level.block.HorizontalDirectionalBlock; - import net.minecraft.world.level.block.RespawnAnchorBlock; - import net.minecraft.world.level.block.entity.BlockEntity; - import net.minecraft.world.level.block.entity.CommandBlockEntity; - import net.minecraft.world.level.block.entity.SignBlockEntity; - import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.dimension.LevelStem; - import net.minecraft.world.level.gameevent.GameEvent; - import net.minecraft.world.level.portal.TeleportTransition; - import net.minecraft.world.level.saveddata.maps.MapId; -@@ -179,11 +176,48 @@ - import net.minecraft.world.scores.PlayerTeam; - import net.minecraft.world.scores.ScoreAccess; - import net.minecraft.world.scores.ScoreHolder; -+import org.slf4j.Logger; -+import net.minecraft.world.Container; -+import net.minecraft.world.Difficulty; -+import net.minecraft.world.InteractionHand; -+import net.minecraft.world.MenuProvider; -+// CraftBukkit start -+import net.minecraft.world.damagesource.CombatTracker; -+import net.minecraft.world.item.enchantment.EnchantmentEffectComponents; -+import net.minecraft.world.item.enchantment.EnchantmentHelper; -+import net.minecraft.world.item.trading.MerchantOffers; -+import net.minecraft.world.scores.Scoreboard; - import net.minecraft.world.scores.Team; - import net.minecraft.world.scores.criteria.ObjectiveCriteria; --import org.slf4j.Logger; -+import io.papermc.paper.adventure.PaperAdventure; // Paper -+import org.bukkit.Bukkit; -+import org.bukkit.Location; -+import org.bukkit.WeatherType; -+import org.bukkit.command.CommandSender; -+import org.bukkit.craftbukkit.CraftWorld; -+import org.bukkit.craftbukkit.CraftWorldBorder; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.craftbukkit.event.CraftEventFactory; -+import org.bukkit.craftbukkit.event.CraftPortalEvent; -+import org.bukkit.craftbukkit.inventory.CraftItemStack; -+import org.bukkit.craftbukkit.util.CraftDimensionUtil; -+import org.bukkit.craftbukkit.util.CraftLocation; -+import org.bukkit.entity.Player; -+import org.bukkit.event.entity.EntityExhaustionEvent; -+import org.bukkit.event.player.PlayerBedLeaveEvent; -+import org.bukkit.event.player.PlayerChangedMainHandEvent; -+import org.bukkit.event.player.PlayerChangedWorldEvent; -+import org.bukkit.event.player.PlayerDropItemEvent; -+import org.bukkit.event.player.PlayerLocaleChangeEvent; -+import org.bukkit.event.player.PlayerPortalEvent; -+import org.bukkit.event.player.PlayerRespawnEvent; -+import org.bukkit.event.player.PlayerSpawnChangeEvent; -+import org.bukkit.event.player.PlayerTeleportEvent; -+import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; -+import org.bukkit.inventory.MainHand; -+// CraftBukkit end - --public class ServerPlayer extends Player { -+public class ServerPlayer extends net.minecraft.world.entity.player.Player { - - private static final Logger LOGGER = LogUtils.getLogger(); - private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32; -@@ -225,7 +259,8 @@ - private int levitationStartTime; - private boolean disconnected; - private int requestedViewDistance; -- public String language; -+ public String language = null; // CraftBukkit - default // Paper - default to null -+ public java.util.Locale adventure$locale = java.util.Locale.US; // Paper - @Nullable - private Vec3 startingToFallPosition; - @Nullable -@@ -258,7 +293,35 @@ - private final CommandSource commandSource; - private int containerCounter; - public boolean wonGame; -+ private int containerUpdateDelay; // Paper - Configurable container update tick rate -+ public long loginTime; // Paper - Replace OfflinePlayer#getLastPlayed -+ public int patrolSpawnDelay; // Paper - Pillager patrol spawn settings and per player options -+ // Paper start - cancellable death event -+ public boolean queueHealthUpdatePacket; -+ public net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; -+ // Paper end - cancellable death event - -+ // CraftBukkit start -+ public CraftPlayer.TransferCookieConnection transferCookieConnection; -+ public String displayName; -+ public net.kyori.adventure.text.Component adventure$displayName; // Paper -+ public Component listName; -+ public int listOrder = 0; -+ public org.bukkit.Location compassTarget; -+ public int newExp = 0; -+ public int newLevel = 0; -+ public int newTotalExp = 0; -+ public boolean keepLevel = false; -+ public double maxHealthCache; -+ public boolean joining = true; -+ public boolean sentListPacket = false; -+ public boolean supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready -+ // CraftBukkit end -+ public boolean isRealPlayer; // Paper -+ public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent -+ public @Nullable String clientBrandName = null; // Paper - Brand support -+ public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event -+ - public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, ClientInformation clientOptions) { - super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); - this.chatVisibility = ChatVisiblity.FULL; -@@ -266,7 +329,7 @@ - this.canChatColor = true; - this.lastActionTime = Util.getMillis(); - this.requestedViewDistance = 2; -- this.language = "en_us"; -+ this.language = null; // Paper - default to null - this.lastSectionPos = SectionPos.of(0, 0, 0); - this.chunkTrackingView = ChunkTrackingView.EMPTY; - this.respawnDimension = Level.OVERWORLD; -@@ -285,7 +348,14 @@ - - } - -+ // Paper start - Sync offhand slot in menus - @Override -+ public void sendOffHandSlotChange() { -+ ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(ServerPlayer.this.inventoryMenu.containerId, ServerPlayer.this.inventoryMenu.incrementStateId(), net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT, ServerPlayer.this.inventoryMenu.getSlot(net.minecraft.world.inventory.InventoryMenu.SHIELD_SLOT).getItem().copy())); -+ } -+ // Paper end - Sync offhand slot in menus -+ -+ @Override - public void sendSlotChange(AbstractContainerMenu handler, int slot, ItemStack stack) { - ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(handler.containerId, handler.incrementStateId(), slot, stack)); - } -@@ -316,6 +386,25 @@ - - } - } -+ // Paper start - Add PlayerInventorySlotChangeEvent -+ @Override -+ public void slotChanged(AbstractContainerMenu handler, int slotId, ItemStack oldStack, ItemStack stack) { -+ Slot slot = handler.getSlot(slotId); -+ if (!(slot instanceof ResultSlot)) { -+ if (slot.container == ServerPlayer.this.getInventory()) { -+ if (io.papermc.paper.event.player.PlayerInventorySlotChangeEvent.getHandlerList().getRegisteredListeners().length == 0) { -+ CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack); -+ return; -+ } -+ io.papermc.paper.event.player.PlayerInventorySlotChangeEvent event = new io.papermc.paper.event.player.PlayerInventorySlotChangeEvent(ServerPlayer.this.getBukkitEntity(), slotId, CraftItemStack.asBukkitCopy(oldStack), CraftItemStack.asBukkitCopy(stack)); -+ event.callEvent(); -+ if (event.shouldTriggerAdvancements()) { -+ CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack); -+ } -+ } -+ } -+ } -+ // Paper end - Add PlayerInventorySlotChangeEvent - - @Override - public void dataChanged(AbstractContainerMenu handler, int property, int value) {} -@@ -340,6 +429,13 @@ - public void sendSystemMessage(Component message) { - ServerPlayer.this.sendSystemMessage(message); - } -+ -+ // CraftBukkit start -+ @Override -+ public CommandSender getBukkitSender(CommandSourceStack wrapper) { -+ return ServerPlayer.this.getBukkitEntity(); -+ } -+ // CraftBukkit end - }; - this.textFilter = server.createTextFilterForPlayer(this); - this.gameMode = server.createGameModeForPlayer(this); -@@ -349,17 +445,72 @@ - this.server = server; - this.stats = server.getPlayerList().getPlayerStats(this); - this.advancements = server.getPlayerList().getPlayerAdvancements(this); -- this.moveTo(this.adjustSpawnLocation(world, world.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); -- this.updateOptions(clientOptions); -+ // this.moveTo(this.adjustSpawnLocation(world, world.getSharedSpawnPos()).getBottomCenter(), 0.0F, 0.0F); // Paper - Don't move existing players to world spawn -+ this.updateOptionsNoEvents(clientOptions); // Paper - don't call options events on login - this.object = null; -+ -+ // CraftBukkit start -+ this.displayName = this.getScoreboardName(); -+ this.adventure$displayName = net.kyori.adventure.text.Component.text(this.getScoreboardName()); // Paper -+ this.bukkitPickUpLoot = true; -+ this.maxHealthCache = this.getMaxHealth(); -+ } -+ -+ // Use method to resend items in hands in case of client desync, because the item use got cancelled. -+ // For example, when cancelling the leash event -+ @Deprecated // Paper - this shouldn't be used, use the regular sendAllDataToRemote call to resync all -+ public void resendItemInHands() { -+ this.containerMenu.findSlot(this.getInventory(), this.getInventory().selected).ifPresent(s -> { -+ this.containerSynchronizer.sendSlotChange(this.containerMenu, s, this.getMainHandItem()); -+ }); -+ this.containerSynchronizer.sendSlotChange(this.inventoryMenu, InventoryMenu.SHIELD_SLOT, this.getOffhandItem()); -+ } -+ -+ // Yes, this doesn't match Vanilla, but it's the best we can do for now. -+ // If this is an issue, PRs are welcome -+ public final BlockPos getSpawnPoint(ServerLevel worldserver) { -+ BlockPos blockposition = worldserver.getSharedSpawnPos(); -+ -+ if (worldserver.dimensionType().hasSkyLight() && worldserver.serverLevelData.getGameType() != GameType.ADVENTURE) { -+ int i = Math.max(0, this.server.getSpawnRadius(worldserver)); -+ int j = Mth.floor(worldserver.getWorldBorder().getDistanceToBorder((double) blockposition.getX(), (double) blockposition.getZ())); -+ -+ if (j < i) { -+ i = j; -+ } -+ -+ if (j <= 1) { -+ i = 1; -+ } -+ -+ long k = (long) (i * 2 + 1); -+ long l = k * k; -+ int i1 = l > 2147483647L ? Integer.MAX_VALUE : (int) l; -+ int j1 = this.getCoprime(i1); -+ int k1 = RandomSource.create().nextInt(i1); -+ -+ for (int l1 = 0; l1 < i1; ++l1) { -+ int i2 = (k1 + j1 * l1) % i1; -+ int j2 = i2 % (i * 2 + 1); -+ int k2 = i2 / (i * 2 + 1); -+ BlockPos blockposition1 = PlayerRespawnLogic.getOverworldRespawnPos(worldserver, blockposition.getX() + j2 - i, blockposition.getZ() + k2 - i); -+ -+ if (blockposition1 != null) { -+ return blockposition1; -+ } -+ } -+ } -+ -+ return blockposition; - } -+ // CraftBukkit end - - @Override - public BlockPos adjustSpawnLocation(ServerLevel world, BlockPos basePos) { - AABB axisalignedbb = this.getDimensions(Pose.STANDING).makeBoundingBox(Vec3.ZERO); - BlockPos blockposition1 = basePos; - -- if (world.dimensionType().hasSkyLight() && world.getServer().getWorldData().getGameType() != GameType.ADVENTURE) { -+ if (world.dimensionType().hasSkyLight() && world.serverLevelData.getGameType() != GameType.ADVENTURE) { // CraftBukkit - int i = Math.max(0, this.server.getSpawnRadius(world)); - int j = Mth.floor(world.getWorldBorder().getDistanceToBorder((double) basePos.getX(), (double) basePos.getZ())); - -@@ -395,14 +546,20 @@ - - Objects.requireNonNull(basePos); - crashreportsystemdetails.setDetail("Origin", basePos::toString); -+ // CraftBukkit start - decompile error -+ int finalI = i; - crashreportsystemdetails.setDetail("Radius", () -> { -- return Integer.toString(i); -+ return Integer.toString(finalI); -+ // CraftBukkit end - }); - crashreportsystemdetails.setDetail("Candidate", () -> { - return "[" + l2 + "," + i3 + "]"; - }); -+ // CraftBukkit start - decompile error -+ int finalL1 = l1; - crashreportsystemdetails.setDetail("Progress", () -> { -- return "" + l1 + " out of " + i1; -+ return "" + finalL1 + " out of " + i1; -+ // CraftBukkit end - }); - throw new ReportedException(crashreport); - } -@@ -440,7 +597,7 @@ - dataresult = WardenSpawnTracker.CODEC.parse(new Dynamic(NbtOps.INSTANCE, nbt.get("warden_spawn_tracker"))); - logger = ServerPlayer.LOGGER; - Objects.requireNonNull(logger); -- dataresult.resultOrPartial(logger::error).ifPresent((wardenspawntracker) -> { -+ ((DataResult) dataresult).resultOrPartial(logger::error).ifPresent((wardenspawntracker) -> { - this.wardenSpawnTracker = wardenspawntracker; - }); - } -@@ -457,17 +614,26 @@ - return this.server.getRecipeManager().byKey(resourcekey).isPresent(); - }); - } -+ this.getBukkitEntity().readExtraData(nbt); // CraftBukkit - - if (this.isSleeping()) { - this.stopSleeping(); - } - -+ // CraftBukkit start -+ String spawnWorld = nbt.getString("SpawnWorld"); -+ CraftWorld oldWorld = (CraftWorld) Bukkit.getWorld(spawnWorld); -+ if (oldWorld != null) { -+ this.respawnDimension = oldWorld.getHandle().dimension(); -+ } -+ // CraftBukkit end -+ - if (nbt.contains("SpawnX", 99) && nbt.contains("SpawnY", 99) && nbt.contains("SpawnZ", 99)) { - this.respawnPosition = new BlockPos(nbt.getInt("SpawnX"), nbt.getInt("SpawnY"), nbt.getInt("SpawnZ")); - this.respawnForced = nbt.getBoolean("SpawnForced"); - this.respawnAngle = nbt.getFloat("SpawnAngle"); - if (nbt.contains("SpawnDimension")) { -- DataResult dataresult1 = Level.RESOURCE_KEY_CODEC.parse(NbtOps.INSTANCE, nbt.get("SpawnDimension")); -+ DataResult> dataresult1 = Level.RESOURCE_KEY_CODEC.parse(NbtOps.INSTANCE, nbt.get("SpawnDimension")); // CraftBukkit - decompile error - Logger logger1 = ServerPlayer.LOGGER; - - Objects.requireNonNull(logger1); -@@ -482,7 +648,7 @@ - dataresult = BlockPos.CODEC.parse(NbtOps.INSTANCE, nbtbase); - logger = ServerPlayer.LOGGER; - Objects.requireNonNull(logger); -- dataresult.resultOrPartial(logger::error).ifPresent((blockposition) -> { -+ ((DataResult) dataresult).resultOrPartial(logger::error).ifPresent((blockposition) -> { // CraftBukkit - decompile error - this.raidOmenPosition = blockposition; - }); - } -@@ -492,7 +658,7 @@ - @Override - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); -- DataResult dataresult = WardenSpawnTracker.CODEC.encodeStart(NbtOps.INSTANCE, this.wardenSpawnTracker); -+ DataResult dataresult = WardenSpawnTracker.CODEC.encodeStart(NbtOps.INSTANCE, this.wardenSpawnTracker); // CraftBukkit - decompile error - Logger logger = ServerPlayer.LOGGER; - - Objects.requireNonNull(logger); -@@ -526,6 +692,7 @@ - nbt.put("SpawnDimension", nbtbase); - }); - } -+ this.getBukkitEntity().setExtraData(nbt); // CraftBukkit - - nbt.putBoolean("spawn_extra_particles_on_fall", this.spawnExtraParticlesOnFall); - if (this.raidOmenPosition != null) { -@@ -544,7 +711,20 @@ - Entity entity = this.getRootVehicle(); - Entity entity1 = this.getVehicle(); - -- if (entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger()) { -+ // CraftBukkit start - handle non-persistent vehicles -+ boolean persistVehicle = true; -+ if (entity1 != null) { -+ Entity vehicle; -+ for (vehicle = entity1; vehicle != null; vehicle = vehicle.getVehicle()) { -+ if (!vehicle.persist) { -+ persistVehicle = false; -+ break; -+ } -+ } -+ } -+ -+ if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger() && !entity.isRemoved()) { // Paper - Ensure valid vehicle status -+ // CraftBukkit end - CompoundTag nbttagcompound1 = new CompoundTag(); - CompoundTag nbttagcompound2 = new CompoundTag(); - -@@ -564,7 +744,7 @@ - ServerLevel worldserver = (ServerLevel) world; - CompoundTag nbttagcompound = ((CompoundTag) nbt.get()).getCompound("RootVehicle"); - Entity entity = EntityType.loadEntityRecursive(nbttagcompound.getCompound("Entity"), worldserver, EntitySpawnReason.LOAD, (entity1) -> { -- return !worldserver.addWithUUID(entity1) ? null : entity1; -+ return !worldserver.addWithUUID(entity1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.MOUNT) ? null : entity1; // CraftBukkit - decompile error // Paper - Entity#getEntitySpawnReason - }); - - if (entity == null) { -@@ -598,12 +778,12 @@ - - if (!this.isPassenger()) { - ServerPlayer.LOGGER.warn("Couldn't reattach entity to player"); -- entity.discard(); -+ entity.discard(null); // CraftBukkit - add Bukkit remove cause - iterator = entity.getIndirectPassengers().iterator(); - - while (iterator.hasNext()) { - entity1 = (Entity) iterator.next(); -- entity1.discard(); -+ entity1.discard(null); // CraftBukkit - add Bukkit remove cause - } - } - } -@@ -618,6 +798,7 @@ - - while (iterator.hasNext()) { - ThrownEnderpearl entityenderpearl = (ThrownEnderpearl) iterator.next(); -+ if (entityenderpearl.level().paperConfig().misc.legacyEnderPearlBehavior) continue; // Paper - Allow using old ender pearl behavior - - if (entityenderpearl.isRemoved()) { - ServerPlayer.LOGGER.warn("Trying to save removed ender pearl, skipping"); -@@ -625,7 +806,7 @@ - CompoundTag nbttagcompound1 = new CompoundTag(); - - entityenderpearl.save(nbttagcompound1); -- DataResult dataresult = ResourceLocation.CODEC.encodeStart(NbtOps.INSTANCE, entityenderpearl.level().dimension().location()); -+ DataResult dataresult = ResourceLocation.CODEC.encodeStart(NbtOps.INSTANCE, entityenderpearl.level().dimension().location()); // CraftBukkit - decompile error - Logger logger = ServerPlayer.LOGGER; - - Objects.requireNonNull(logger); -@@ -651,7 +832,7 @@ - nbttaglist.forEach((nbtbase1) -> { - if (nbtbase1 instanceof CompoundTag nbttagcompound) { - if (nbttagcompound.contains("ender_pearl_dimension")) { -- DataResult dataresult = Level.RESOURCE_KEY_CODEC.parse(NbtOps.INSTANCE, nbttagcompound.get("ender_pearl_dimension")); -+ DataResult> dataresult = Level.RESOURCE_KEY_CODEC.parse(NbtOps.INSTANCE, nbttagcompound.get("ender_pearl_dimension")); // CraftBukkit - decompile error - Logger logger = ServerPlayer.LOGGER; - - Objects.requireNonNull(logger); -@@ -684,7 +865,30 @@ - } - } - -+ } -+ -+ // CraftBukkit start - World fallback code, either respawn location or global spawn -+ public void spawnIn(Level world) { -+ this.setLevel(world); -+ if (world == null) { -+ this.unsetRemoved(); -+ Vec3 position = null; -+ if (this.respawnDimension != null) { -+ world = this.server.getLevel(this.respawnDimension); -+ if (world != null && this.getRespawnPosition() != null) { -+ position = ServerPlayer.findRespawnAndUseSpawnBlock((ServerLevel) world, this.getRespawnPosition(), this.getRespawnAngle(), false, false).map(ServerPlayer.RespawnPosAngle::position).orElse(null); -+ } -+ } -+ if (world == null || position == null) { -+ world = ((CraftWorld) Bukkit.getServer().getWorlds().get(0)).getHandle(); -+ position = Vec3.atCenterOf(world.getSharedSpawnPos()); -+ } -+ this.setLevel(world); -+ this.setPosRaw(position.x(), position.y(), position.z()); // Paper - don't register to chunks yet -+ } -+ this.gameMode.setLevel((ServerLevel) world); - } -+ // CraftBukkit end - - public void setExperiencePoints(int points) { - float f = (float) this.getXpNeededForNextLevel(); -@@ -744,6 +948,11 @@ - - @Override - public void tick() { -+ // CraftBukkit start -+ if (this.joining) { -+ this.joining = false; -+ } -+ // CraftBukkit end - this.tickClientLoadTimeout(); - this.gameMode.tick(); - this.wardenSpawnTracker.tick(); -@@ -751,9 +960,13 @@ - --this.invulnerableTime; - } - -- this.containerMenu.broadcastChanges(); -- if (!this.containerMenu.stillValid(this)) { -- this.closeContainer(); -+ if (--this.containerUpdateDelay <= 0) { -+ this.containerMenu.broadcastChanges(); -+ this.containerUpdateDelay = this.level().paperConfig().tickRates.containerUpdate; -+ } -+ // Paper end - Configurable container update tick rate -+ if (this.containerMenu != this.inventoryMenu && (this.isImmobile() || !this.containerMenu.stillValid(this))) { // Paper - Prevent opening inventories when frozen -+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.CANT_USE); // Paper - Inventory close reason - this.containerMenu = this.inventoryMenu; - } - -@@ -807,7 +1020,7 @@ - - public void doTick() { - try { -- if (!this.isSpectator() || !this.touchingUnloadedChunk()) { -+ if (valid && !this.isSpectator() || !this.touchingUnloadedChunk()) { // Paper - don't tick dead players that are not in the world currently (pending respawn) - super.tick(); - } - -@@ -820,7 +1033,7 @@ - } - - if (this.getHealth() != this.lastSentHealth || this.lastSentFood != this.foodData.getFoodLevel() || this.foodData.getSaturationLevel() == 0.0F != this.lastFoodSaturationZero) { -- this.connection.send(new ClientboundSetHealthPacket(this.getHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel())); -+ this.connection.send(new ClientboundSetHealthPacket(this.getBukkitEntity().getScaledHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel())); // CraftBukkit - this.lastSentHealth = this.getHealth(); - this.lastSentFood = this.foodData.getFoodLevel(); - this.lastFoodSaturationZero = this.foodData.getSaturationLevel() == 0.0F; -@@ -851,6 +1064,12 @@ - this.updateScoreForCriteria(ObjectiveCriteria.EXPERIENCE, Mth.ceil((float) this.lastRecordedExperience)); - } - -+ // CraftBukkit start - Force max health updates -+ if (this.maxHealthCache != this.getMaxHealth()) { -+ this.getBukkitEntity().updateScaledHealth(); -+ } -+ // CraftBukkit end -+ - if (this.experienceLevel != this.lastRecordedLevel) { - this.lastRecordedLevel = this.experienceLevel; - this.updateScoreForCriteria(ObjectiveCriteria.LEVEL, Mth.ceil((float) this.lastRecordedLevel)); -@@ -865,6 +1084,20 @@ - CriteriaTriggers.LOCATION.trigger(this); - } - -+ // CraftBukkit start - initialize oldLevel, fire PlayerLevelChangeEvent, and tick client-sided world border -+ if (this.oldLevel == -1) { -+ this.oldLevel = this.experienceLevel; -+ } -+ -+ if (this.oldLevel != this.experienceLevel) { -+ CraftEventFactory.callPlayerLevelChangeEvent(this.getBukkitEntity(), this.oldLevel, this.experienceLevel); -+ this.oldLevel = this.experienceLevel; -+ } -+ -+ if (this.getBukkitEntity().hasClientWorldBorder()) { -+ ((CraftWorldBorder) this.getBukkitEntity().getWorldBorder()).getHandle().tick(); -+ } -+ // CraftBukkit end - } catch (Throwable throwable) { - CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking player"); - CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Player being ticked"); -@@ -893,7 +1126,7 @@ - if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.serverLevel().getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION)) { - if (this.tickCount % 20 == 0) { - if (this.getHealth() < this.getMaxHealth()) { -- this.heal(1.0F); -+ this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit - added regain reason of "REGEN" for filtering purposes. - } - - float f = this.foodData.getSaturationLevel(); -@@ -946,19 +1179,105 @@ - } - - private void updateScoreForCriteria(ObjectiveCriteria criterion, int score) { -- this.getScoreboard().forAllObjectives(criterion, this, (scoreaccess) -> { -+ // CraftBukkit - Use our scores instead -+ this.level().getCraftServer().getScoreboardManager().forAllObjectives(criterion, this, (scoreaccess) -> { - scoreaccess.set(score); - }); - } - -+ // Paper start - PlayerDeathEvent#getItemsToKeep -+ private static void processKeep(org.bukkit.event.entity.PlayerDeathEvent event, NonNullList inv) { -+ List itemsToKeep = event.getItemsToKeep(); -+ if (inv == null) { -+ // remainder of items left in toKeep - plugin added stuff on death that wasn't in the initial loot? -+ if (!itemsToKeep.isEmpty()) { -+ for (org.bukkit.inventory.ItemStack itemStack : itemsToKeep) { -+ event.getEntity().getInventory().addItem(itemStack); -+ } -+ } -+ -+ return; -+ } -+ -+ for (int i = 0; i < inv.size(); ++i) { -+ ItemStack item = inv.get(i); -+ if (EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP) || itemsToKeep.isEmpty() || item.isEmpty()) { -+ inv.set(i, ItemStack.EMPTY); -+ continue; -+ } -+ -+ final org.bukkit.inventory.ItemStack bukkitStack = item.getBukkitStack(); -+ boolean keep = false; -+ final Iterator iterator = itemsToKeep.iterator(); -+ while (iterator.hasNext()) { -+ final org.bukkit.inventory.ItemStack itemStack = iterator.next(); -+ if (bukkitStack.equals(itemStack)) { -+ iterator.remove(); -+ keep = true; -+ break; -+ } -+ } -+ -+ if (!keep) { -+ inv.set(i, ItemStack.EMPTY); -+ } -+ } -+ } -+ // Paper end - PlayerDeathEvent#getItemsToKeep -+ - @Override - public void die(DamageSource damageSource) { -- this.gameEvent(GameEvent.ENTITY_DIE); -+ // this.gameEvent(GameEvent.ENTITY_DIE); // Paper - move below event cancellation check - boolean flag = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES); -+ // CraftBukkit start - fire PlayerDeathEvent -+ if (this.isRemoved()) { -+ return; -+ } -+ List loot = new java.util.ArrayList<>(this.getInventory().getContainerSize()); // Paper - Restore vanilla drops behavior -+ boolean keepInventory = this.serverLevel().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator(); - -- if (flag) { -- Component ichatbasecomponent = this.getCombatTracker().getDeathMessage(); -+ if (!keepInventory) { -+ for (ItemStack item : this.getInventory().getContents()) { -+ if (!item.isEmpty() && !EnchantmentHelper.has(item, EnchantmentEffectComponents.PREVENT_EQUIPMENT_DROP)) { -+ loot.add(new DefaultDrop(item, stack -> this.drop(stack, true, false, false))); // Paper - Restore vanilla drops behavior; drop function taken from Inventory#dropAll (don't fire drop event) -+ } -+ } -+ } -+ if (this.shouldDropLoot() && this.serverLevel().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - fix player loottables running when mob loot gamerule is false -+ // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule) -+ this.dropFromLootTable(this.serverLevel(), damageSource, this.lastHurtByPlayerTime > 0); -+ // Paper - Restore vanilla drops behaviour; custom death loot is a noop on server player, remove. - -+ loot.addAll(this.drops); -+ this.drops.clear(); // SPIGOT-5188: make sure to clear -+ } // Paper - fix player loottables running when mob loot gamerule is false -+ -+ Component defaultMessage = this.getCombatTracker().getDeathMessage(); -+ -+ String deathmessage = defaultMessage.getString(); -+ this.keepLevel = keepInventory; // SPIGOT-2222: pre-set keepLevel -+ org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, damageSource, loot, PaperAdventure.asAdventure(defaultMessage), keepInventory); // Paper - Adventure -+ // Paper start - cancellable death event -+ if (event.isCancelled()) { -+ // make compatible with plugins that might have already set the health in an event listener -+ if (this.getHealth() <= 0) { -+ this.setHealth((float) event.getReviveHealth()); -+ } -+ return; -+ } -+ this.gameEvent(GameEvent.ENTITY_DIE); // moved from the top of this method -+ // Paper end -+ -+ // SPIGOT-943 - only call if they have an inventory open -+ if (this.containerMenu != this.inventoryMenu) { -+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DEATH); // Paper - Inventory close reason -+ } -+ -+ net.kyori.adventure.text.Component deathMessage = event.deathMessage() != null ? event.deathMessage() : net.kyori.adventure.text.Component.empty(); // Paper - Adventure -+ -+ if (deathMessage != null && deathMessage != net.kyori.adventure.text.Component.empty() && flag) { // Paper - Adventure // TODO: allow plugins to override? -+ Component ichatbasecomponent = PaperAdventure.asVanilla(deathMessage); // Paper - Adventure -+ - this.connection.send(new ClientboundPlayerCombatKillPacket(this.getId(), ichatbasecomponent), PacketSendListener.exceptionallySend(() -> { - boolean flag1 = true; - String s = ichatbasecomponent.getString(256); -@@ -988,12 +1307,23 @@ - if (this.serverLevel().getGameRules().getBoolean(GameRules.RULE_FORGIVE_DEAD_PLAYERS)) { - this.tellNeutralMobsThatIDied(); - } -- -- if (!this.isSpectator()) { -- this.dropAllDeathLoot(this.serverLevel(), damageSource); -+ // SPIGOT-5478 must be called manually now -+ if (event.shouldDropExperience()) this.dropExperience(this.serverLevel(), damageSource.getEntity()); // Paper - tie to event -+ // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory. -+ if (!event.getKeepInventory()) { -+ // Paper start - PlayerDeathEvent#getItemsToKeep -+ for (NonNullList inv : this.getInventory().compartments) { -+ processKeep(event, inv); -+ } -+ processKeep(event, null); -+ // Paper end - PlayerDeathEvent#getItemsToKeep - } - -- this.getScoreboard().forAllObjectives(ObjectiveCriteria.DEATH_COUNT, this, ScoreAccess::increment); -+ this.setCamera(this); // Remove spectated target -+ // CraftBukkit end -+ -+ // CraftBukkit - Get our scores instead -+ this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.DEATH_COUNT, this, ScoreAccess::increment); - LivingEntity entityliving = this.getKillCredit(); - - if (entityliving != null) { -@@ -1028,10 +1358,12 @@ - public void awardKillScore(Entity entityKilled, DamageSource damageSource) { - if (entityKilled != this) { - super.awardKillScore(entityKilled, damageSource); -- this.getScoreboard().forAllObjectives(ObjectiveCriteria.KILL_COUNT_ALL, this, ScoreAccess::increment); -- if (entityKilled instanceof Player) { -+ // CraftBukkit - Get our scores instead -+ this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.KILL_COUNT_ALL, this, ScoreAccess::increment); -+ if (entityKilled instanceof net.minecraft.world.entity.player.Player) { - this.awardStat(Stats.PLAYER_KILLS); -- this.getScoreboard().forAllObjectives(ObjectiveCriteria.KILL_COUNT_PLAYERS, this, ScoreAccess::increment); -+ // CraftBukkit - Get our scores instead -+ this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.KILL_COUNT_PLAYERS, this, ScoreAccess::increment); - } else { - this.awardStat(Stats.MOB_KILLS); - } -@@ -1049,7 +1381,8 @@ - int i = scoreboardteam.getColor().getId(); - - if (i >= 0 && i < criterions.length) { -- this.getScoreboard().forAllObjectives(criterions[i], targetScoreHolder, ScoreAccess::increment); -+ // CraftBukkit - Get our scores instead -+ this.level().getCraftServer().getScoreboardManager().forAllObjectives(criterions[i], targetScoreHolder, ScoreAccess::increment); - } - } - -@@ -1062,8 +1395,8 @@ - } else { - Entity entity = source.getEntity(); - -- if (entity instanceof Player) { -- Player entityhuman = (Player) entity; -+ if (entity instanceof net.minecraft.world.entity.player.Player) { -+ net.minecraft.world.entity.player.Player entityhuman = (net.minecraft.world.entity.player.Player) entity; - - if (!this.canHarmPlayer(entityhuman)) { - return false; -@@ -1074,8 +1407,8 @@ - AbstractArrow entityarrow = (AbstractArrow) entity; - Entity entity1 = entityarrow.getOwner(); - -- if (entity1 instanceof Player) { -- Player entityhuman1 = (Player) entity1; -+ if (entity1 instanceof net.minecraft.world.entity.player.Player) { -+ net.minecraft.world.entity.player.Player entityhuman1 = (net.minecraft.world.entity.player.Player) entity1; - - if (!this.canHarmPlayer(entityhuman1)) { - return false; -@@ -1083,38 +1416,84 @@ - } - } - -- return super.hurtServer(world, source, amount); -+ // Paper start - cancellable death events -+ // return super.hurtServer(world, source, amount); -+ this.queueHealthUpdatePacket = true; -+ boolean damaged = super.hurtServer(world, source, amount); -+ this.queueHealthUpdatePacket = false; -+ if (this.queuedHealthUpdatePacket != null) { -+ this.connection.send(this.queuedHealthUpdatePacket); -+ this.queuedHealthUpdatePacket = null; -+ } -+ return damaged; -+ // Paper end - cancellable death events - } - } - - @Override -- public boolean canHarmPlayer(Player player) { -+ public boolean canHarmPlayer(net.minecraft.world.entity.player.Player player) { - return !this.isPvpAllowed() ? false : super.canHarmPlayer(player); - } - - private boolean isPvpAllowed() { -- return this.server.isPvpAllowed(); -+ // CraftBukkit - this.server.isPvpAllowed() -> this.world.pvpMode -+ return this.level().pvpMode; - } - -- public TeleportTransition findRespawnPositionAndUseSpawnBlock(boolean alive, TeleportTransition.PostTeleportTransition postDimensionTransition) { -+ // CraftBukkit start -+ public TeleportTransition findRespawnPositionAndUseSpawnBlock(boolean flag, TeleportTransition.PostTeleportTransition teleporttransition_a, PlayerRespawnEvent.RespawnReason reason) { -+ TeleportTransition teleportTransition; -+ boolean isBedSpawn = false; -+ boolean isAnchorSpawn = false; -+ // CraftBukkit end - BlockPos blockposition = this.getRespawnPosition(); - float f = this.getRespawnAngle(); - boolean flag1 = this.isRespawnForced(); - ServerLevel worldserver = this.server.getLevel(this.getRespawnDimension()); - - if (worldserver != null && blockposition != null) { -- Optional optional = ServerPlayer.findRespawnAndUseSpawnBlock(worldserver, blockposition, f, flag1, alive); -+ Optional optional = ServerPlayer.findRespawnAndUseSpawnBlock(worldserver, blockposition, f, flag1, flag); - - if (optional.isPresent()) { - ServerPlayer.RespawnPosAngle entityplayer_respawnposangle = (ServerPlayer.RespawnPosAngle) optional.get(); - -- return new TeleportTransition(worldserver, entityplayer_respawnposangle.position(), Vec3.ZERO, entityplayer_respawnposangle.yaw(), 0.0F, postDimensionTransition); -+ // CraftBukkit start -+ isBedSpawn = entityplayer_respawnposangle.isBedSpawn(); -+ isAnchorSpawn = entityplayer_respawnposangle.isAnchorSpawn(); -+ teleportTransition = new TeleportTransition(worldserver, entityplayer_respawnposangle.position(), Vec3.ZERO, entityplayer_respawnposangle.yaw(), 0.0F, teleporttransition_a); -+ // CraftBukkit end - } else { -- return TeleportTransition.missingRespawnBlock(this.server.overworld(), this, postDimensionTransition); -+ teleportTransition = TeleportTransition.missingRespawnBlock(this.server.overworld(), this, teleporttransition_a); // CraftBukkit - } - } else { -- return new TeleportTransition(this.server.overworld(), this, postDimensionTransition); -+ teleportTransition = new TeleportTransition(this.server.overworld(), this, teleporttransition_a); // CraftBukkit - } -+ // CraftBukkit start -+ if (reason == null) { -+ return teleportTransition; -+ } -+ -+ Player respawnPlayer = this.getBukkitEntity(); -+ Location location = CraftLocation.toBukkit(teleportTransition.position(), teleportTransition.newLevel().getWorld(), teleportTransition.yRot(), teleportTransition.xRot()); -+ -+ // Paper start - respawn flags -+ com.google.common.collect.ImmutableSet.Builder builder = com.google.common.collect.ImmutableSet.builder(); -+ if (reason == org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.END_PORTAL) { -+ builder.add(org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag.END_PORTAL); -+ } -+ PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn, isAnchorSpawn, reason, builder); -+ // Paper end - respawn flags -+ this.level().getCraftServer().getPluginManager().callEvent(respawnEvent); -+ // Spigot Start -+ if (this.connection.isDisconnected()) { -+ return null; -+ } -+ // Spigot End -+ -+ location = respawnEvent.getRespawnLocation(); -+ -+ return new TeleportTransition(((CraftWorld) location.getWorld()).getHandle(), CraftLocation.toVec3D(location), teleportTransition.deltaMovement(), location.getYaw(), location.getPitch(), teleportTransition.missingRespawnBlock(), teleportTransition.asPassenger(), teleportTransition.relatives(), teleportTransition.postTeleportTransition(), teleportTransition.cause()); -+ // CraftBukkit end - } - - public static Optional findRespawnAndUseSpawnBlock(ServerLevel world, BlockPos pos, float spawnAngle, boolean spawnForced, boolean alive) { -@@ -1129,11 +1508,11 @@ - } - - return optional.map((vec3d) -> { -- return ServerPlayer.RespawnPosAngle.of(vec3d, pos); -+ return ServerPlayer.RespawnPosAngle.of(vec3d, pos, false, true); // CraftBukkit - }); - } else if (block instanceof BedBlock && BedBlock.canSetSpawn(world)) { - return BedBlock.findStandUpPosition(EntityType.PLAYER, world, pos, (Direction) iblockdata.getValue(BedBlock.FACING), spawnAngle).map((vec3d) -> { -- return ServerPlayer.RespawnPosAngle.of(vec3d, pos); -+ return ServerPlayer.RespawnPosAngle.of(vec3d, pos, true, false); // CraftBukkit - }); - } else if (!spawnForced) { - return Optional.empty(); -@@ -1142,7 +1521,7 @@ - BlockState iblockdata1 = world.getBlockState(pos.above()); - boolean flag3 = iblockdata1.getBlock().isPossibleToRespawnInThis(iblockdata1); - -- return flag2 && flag3 ? Optional.of(new ServerPlayer.RespawnPosAngle(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY() + 0.1D, (double) pos.getZ() + 0.5D), spawnAngle)) : Optional.empty(); -+ return flag2 && flag3 ? Optional.of(new ServerPlayer.RespawnPosAngle(new Vec3((double) pos.getX() + 0.5D, (double) pos.getY() + 0.1D, (double) pos.getZ() + 0.5D), spawnAngle, false, false)) : Optional.empty(); // CraftBukkit - } - } - -@@ -1160,6 +1539,7 @@ - @Nullable - @Override - public ServerPlayer teleport(TeleportTransition teleportTarget) { -+ if (this.isSleeping()) return null; // CraftBukkit - SPIGOT-3154 - if (this.isRemoved()) { - return null; - } else { -@@ -1169,39 +1549,78 @@ - - ServerLevel worldserver = teleportTarget.newLevel(); - ServerLevel worldserver1 = this.serverLevel(); -- ResourceKey resourcekey = worldserver1.dimension(); -+ // CraftBukkit start -+ ResourceKey resourcekey = worldserver1.getTypeKey(); - -+ Location enter = this.getBukkitEntity().getLocation(); -+ PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); -+ Location exit = /* (worldserver == null) ? null : // Paper - always non-null */CraftLocation.toBukkit(absolutePosition.position(), worldserver.getWorld(), absolutePosition.yRot(), absolutePosition.xRot()); -+ PlayerTeleportEvent tpEvent = new PlayerTeleportEvent(this.getBukkitEntity(), enter, exit, teleportTarget.cause()); -+ // Paper start - gateway-specific teleport event -+ if (this.portalProcess != null && this.portalProcess.isSamePortal(((net.minecraft.world.level.block.EndGatewayBlock) net.minecraft.world.level.block.Blocks.END_GATEWAY)) && this.serverLevel().getBlockEntity(this.portalProcess.getEntryPosition()) instanceof net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity theEndGatewayBlockEntity) { -+ tpEvent = new com.destroystokyo.paper.event.player.PlayerTeleportEndGatewayEvent(this.getBukkitEntity(), enter, exit, new org.bukkit.craftbukkit.block.CraftEndGateway(this.serverLevel().getWorld(), theEndGatewayBlockEntity)); -+ } -+ // Paper end - gateway-specific teleport event -+ Bukkit.getServer().getPluginManager().callEvent(tpEvent); -+ Location newExit = tpEvent.getTo(); -+ if (tpEvent.isCancelled() || newExit == null) { -+ return null; -+ } -+ if (!newExit.equals(exit)) { -+ worldserver = ((CraftWorld) newExit.getWorld()).getHandle(); -+ teleportTarget = new TeleportTransition(worldserver, CraftLocation.toVec3D(newExit), Vec3.ZERO, newExit.getYaw(), newExit.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.asPassenger(), Set.of(), teleportTarget.postTeleportTransition(), teleportTarget.cause()); -+ } -+ // CraftBukkit end -+ - if (!teleportTarget.asPassenger()) { - this.stopRiding(); - } - -- if (worldserver.dimension() == resourcekey) { -- this.connection.teleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); -+ // CraftBukkit start -+ if (worldserver != null && worldserver.dimension() == worldserver1.dimension()) { -+ this.connection.internalTeleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); -+ // CraftBukkit end - this.connection.resetPosition(); - teleportTarget.postTeleportTransition().onTransition(this); - return this; - } else { -+ // CraftBukkit start -+ /* - this.isChangingDimension = true; -- LevelData worlddata = worldserver.getLevelData(); -+ WorldData worlddata = worldserver.getLevelData(); - -- this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(worldserver), (byte) 3)); -- this.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); -+ this.connection.send(new PacketPlayOutRespawn(this.createCommonSpawnInfo(worldserver), (byte) 3)); -+ this.connection.send(new PacketPlayOutServerDifficulty(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); - PlayerList playerlist = this.server.getPlayerList(); - - playerlist.sendPlayerPermissionLevel(this); - worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); - this.unsetRemoved(); -+ */ -+ // CraftBukkit end - ProfilerFiller gameprofilerfiller = Profiler.get(); - - gameprofilerfiller.push("moving"); -- if (resourcekey == Level.OVERWORLD && worldserver.dimension() == Level.NETHER) { -+ if (worldserver != null && resourcekey == LevelStem.OVERWORLD && worldserver.getTypeKey() == LevelStem.NETHER) { // CraftBukkit - empty to fall through to null to event - this.enteredNetherPosition = this.position(); - } - - gameprofilerfiller.pop(); - gameprofilerfiller.push("placing"); -+ // CraftBukkit start -+ this.isChangingDimension = true; // CraftBukkit - Set teleport invulnerability only if player changing worlds -+ LevelData worlddata = worldserver.getLevelData(); -+ -+ this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(worldserver), (byte) 3)); -+ this.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); -+ PlayerList playerlist = this.server.getPlayerList(); -+ -+ playerlist.sendPlayerPermissionLevel(this); -+ worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); -+ this.unsetRemoved(); -+ // CraftBukkit end - this.setServerLevel(worldserver); -- this.connection.teleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); -+ this.connection.internalTeleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); // CraftBukkit - use internal teleport without event - this.connection.resetPosition(); - worldserver.addDuringTeleport(this); - gameprofilerfiller.pop(); -@@ -1215,10 +1634,33 @@ - this.lastSentExp = -1; - this.lastSentHealth = -1.0F; - this.lastSentFood = -1; -+ -+ // CraftBukkit start -+ PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld()); -+ this.level().getCraftServer().getPluginManager().callEvent(changeEvent); -+ // CraftBukkit end -+ // Paper start - Reset shield blocking on dimension change -+ if (this.isBlocking()) { -+ this.stopUsingItem(); -+ } -+ // Paper end - Reset shield blocking on dimension change - return this; - } -+ } -+ } -+ -+ // CraftBukkit start -+ @Override -+ public CraftPortalEvent callPortalEvent(Entity entity, Location exit, TeleportCause cause, int searchRadius, int creationRadius) { -+ Location enter = this.getBukkitEntity().getLocation(); -+ PlayerPortalEvent event = new PlayerPortalEvent(this.getBukkitEntity(), enter, exit, cause, searchRadius, true, creationRadius); -+ Bukkit.getServer().getPluginManager().callEvent(event); -+ if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null) { -+ return null; - } -+ return new CraftPortalEvent(event); - } -+ // CraftBukkit end - - @Override - public void forceSetRotation(float yaw, float pitch) { -@@ -1228,13 +1670,27 @@ - public void triggerDimensionChangeTriggers(ServerLevel origin) { - ResourceKey resourcekey = origin.dimension(); - ResourceKey resourcekey1 = this.level().dimension(); -+ // CraftBukkit start -+ ResourceKey maindimensionkey = CraftDimensionUtil.getMainDimensionKey(origin); -+ ResourceKey maindimensionkey1 = CraftDimensionUtil.getMainDimensionKey(this.level()); - -- CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourcekey, resourcekey1); -- if (resourcekey == Level.NETHER && resourcekey1 == Level.OVERWORLD && this.enteredNetherPosition != null) { -+ // Paper start - Add option for strict advancement dimension checks -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.strictAdvancementDimensionCheck) { -+ maindimensionkey = resourcekey; -+ maindimensionkey1 = resourcekey1; -+ } -+ // Paper end - Add option for strict advancement dimension checks -+ CriteriaTriggers.CHANGED_DIMENSION.trigger(this, maindimensionkey, maindimensionkey1); -+ if (maindimensionkey != resourcekey || maindimensionkey1 != resourcekey1) { -+ CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourcekey, resourcekey1); -+ } -+ -+ if (maindimensionkey == Level.NETHER && maindimensionkey1 == Level.OVERWORLD && this.enteredNetherPosition != null) { -+ // CraftBukkit end - CriteriaTriggers.NETHER_TRAVEL.trigger(this, this.enteredNetherPosition); - } - -- if (resourcekey1 != Level.NETHER) { -+ if (maindimensionkey1 != Level.NETHER) { // CraftBukkit - this.enteredNetherPosition = null; - } - -@@ -1251,36 +1707,63 @@ - this.containerMenu.broadcastChanges(); - } - -- @Override -- public Either startSleepInBed(BlockPos pos) { -- Direction enumdirection = (Direction) this.level().getBlockState(pos).getValue(HorizontalDirectionalBlock.FACING); -- -+ // CraftBukkit start - moved bed result checks from below into separate method -+ private Either getBedResult(BlockPos blockposition, Direction enumdirection) { - if (!this.isSleeping() && this.isAlive()) { -- if (!this.level().dimensionType().natural()) { -- return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_HERE); -- } else if (!this.bedInRange(pos, enumdirection)) { -- return Either.left(Player.BedSleepingProblem.TOO_FAR_AWAY); -- } else if (this.bedBlocked(pos, enumdirection)) { -- return Either.left(Player.BedSleepingProblem.OBSTRUCTED); -+ if (!this.level().dimensionType().natural() || !this.level().dimensionType().bedWorks()) { -+ return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_POSSIBLE_HERE); -+ } else if (!this.bedInRange(blockposition, enumdirection)) { -+ return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.TOO_FAR_AWAY); -+ } else if (this.bedBlocked(blockposition, enumdirection)) { -+ return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.OBSTRUCTED); - } else { -- this.setRespawnPosition(this.level().dimension(), pos, this.getYRot(), false, true); -+ this.setRespawnPosition(this.level().dimension(), blockposition, this.getYRot(), false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.BED); // Paper - Add PlayerSetSpawnEvent - if (this.level().isDay()) { -- return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_NOW); -+ return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_POSSIBLE_NOW); - } else { - if (!this.isCreative()) { - double d0 = 8.0D; - double d1 = 5.0D; -- Vec3 vec3d = Vec3.atBottomCenterOf(pos); -+ Vec3 vec3d = Vec3.atBottomCenterOf(blockposition); - List list = this.level().getEntitiesOfClass(Monster.class, new AABB(vec3d.x() - 8.0D, vec3d.y() - 5.0D, vec3d.z() - 8.0D, vec3d.x() + 8.0D, vec3d.y() + 5.0D, vec3d.z() + 8.0D), (entitymonster) -> { - return entitymonster.isPreventingPlayerRest(this.serverLevel(), this); - }); - - if (!list.isEmpty()) { -- return Either.left(Player.BedSleepingProblem.NOT_SAFE); -+ return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_SAFE); - } - } - -- Either either = super.startSleepInBed(pos).ifRight((unit) -> { -+ return Either.right(Unit.INSTANCE); -+ } -+ } -+ } else { -+ return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.OTHER_PROBLEM); -+ } -+ } -+ -+ @Override -+ public Either startSleepInBed(BlockPos blockposition, boolean force) { -+ Direction enumdirection = (Direction) this.level().getBlockState(blockposition).getValue(HorizontalDirectionalBlock.FACING); -+ Either bedResult = this.getBedResult(blockposition, enumdirection); -+ -+ if (bedResult.left().orElse(null) == net.minecraft.world.entity.player.Player.BedSleepingProblem.OTHER_PROBLEM) { -+ return bedResult; // return immediately if the result is not bypassable by plugins -+ } -+ -+ if (force) { -+ bedResult = Either.right(Unit.INSTANCE); -+ } -+ -+ bedResult = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBedEnterEvent(this, blockposition, bedResult); -+ if (bedResult.left().isPresent()) { -+ return bedResult; -+ } -+ -+ { -+ { -+ { -+ Either either = super.startSleepInBed(blockposition, force).ifRight((unit) -> { - this.awardStat(Stats.SLEEP_IN_BED); - CriteriaTriggers.SLEPT_IN_BED.trigger(this); - }); -@@ -1293,9 +1776,8 @@ - return either; - } - } -- } else { -- return Either.left(Player.BedSleepingProblem.OTHER_PROBLEM); - } -+ // CraftBukkit end - } - - @Override -@@ -1322,13 +1804,31 @@ - - @Override - public void stopSleepInBed(boolean skipSleepTimer, boolean updateSleepingPlayers) { -+ if (!this.isSleeping()) return; // CraftBukkit - Can't leave bed if not in one! -+ // CraftBukkit start - fire PlayerBedLeaveEvent -+ CraftPlayer player = this.getBukkitEntity(); -+ BlockPos bedPosition = this.getSleepingPos().orElse(null); -+ -+ org.bukkit.block.Block bed; -+ if (bedPosition != null) { -+ bed = this.level().getWorld().getBlockAt(bedPosition.getX(), bedPosition.getY(), bedPosition.getZ()); -+ } else { -+ bed = this.level().getWorld().getBlockAt(player.getLocation()); -+ } -+ -+ PlayerBedLeaveEvent event = new PlayerBedLeaveEvent(player, bed, true); -+ this.level().getCraftServer().getPluginManager().callEvent(event); -+ if (event.isCancelled()) { -+ return; -+ } -+ // CraftBukkit end - if (this.isSleeping()) { - this.serverLevel().getChunkSource().broadcastAndSend(this, new ClientboundAnimatePacket(this, 2)); - } - - super.stopSleepInBed(skipSleepTimer, updateSleepingPlayers); - if (this.connection != null) { -- this.connection.teleport(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); -+ this.connection.teleport(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot(), TeleportCause.EXIT_BED); // CraftBukkit - } - - } -@@ -1341,7 +1841,7 @@ - - @Override - public boolean isInvulnerableTo(ServerLevel world, DamageSource source) { -- return super.isInvulnerableTo(world, source) || this.isChangingDimension() && !source.is(DamageTypes.ENDER_PEARL) || !this.hasClientLoaded(); -+ return (super.isInvulnerableTo(world, source) || this.isChangingDimension() && !source.is(DamageTypes.ENDER_PEARL) || !this.hasClientLoaded()) || (!this.level().paperConfig().collisions.allowPlayerCrammingDamage && source.is(DamageTypes.CRAMMING)); // Paper - disable player cramming - } - - @Override -@@ -1387,8 +1887,9 @@ - this.connection.send(new ClientboundOpenSignEditorPacket(sign.getBlockPos(), front)); - } - -- public void nextContainerCounter() { -+ public int nextContainerCounter() { // CraftBukkit - void -> int - this.containerCounter = this.containerCounter % 100 + 1; -+ return this.containerCounter; // CraftBukkit - } - - @Override -@@ -1396,13 +1897,44 @@ - if (factory == null) { - return OptionalInt.empty(); - } else { -+ // CraftBukkit start - SPIGOT-6552: Handle inventory closing in CraftEventFactory#callInventoryOpenEvent(...) -+ /* - if (this.containerMenu != this.inventoryMenu) { - this.closeContainer(); - } -+ */ -+ // CraftBukkit end - - this.nextContainerCounter(); - AbstractContainerMenu container = factory.createMenu(this.containerCounter, this.getInventory(), this); - -+ Component title = null; // Paper - Add titleOverride to InventoryOpenEvent -+ // CraftBukkit start - Inventory open hook -+ if (container != null) { -+ container.setTitle(factory.getDisplayName()); -+ -+ boolean cancelled = false; -+ // Paper start - Add titleOverride to InventoryOpenEvent -+ final com.mojang.datafixers.util.Pair result = CraftEventFactory.callInventoryOpenEventWithTitle(this, container, cancelled); -+ container = result.getSecond(); -+ title = PaperAdventure.asVanilla(result.getFirst()); -+ // Paper end - Add titleOverride to InventoryOpenEvent -+ if (container == null && !cancelled) { // Let pre-cancelled events fall through -+ // SPIGOT-5263 - close chest if cancelled -+ if (factory instanceof Container) { -+ ((Container) factory).stopOpen(this); -+ } else if (factory instanceof ChestBlock.DoubleInventory) { -+ // SPIGOT-5355 - double chests too :( -+ ((ChestBlock.DoubleInventory) factory).inventorylargechest.stopOpen(this); -+ // Paper start - Fix InventoryOpenEvent cancellation -+ } else if (!this.enderChestInventory.isActiveChest(null)) { -+ this.enderChestInventory.stopOpen(this); -+ // Paper end - Fix InventoryOpenEvent cancellation -+ } -+ return OptionalInt.empty(); -+ } -+ } -+ // CraftBukkit end - if (container == null) { - if (this.isSpectator()) { - this.displayClientMessage(Component.translatable("container.spectatorCantOpen").withStyle(ChatFormatting.RED), true); -@@ -1410,9 +1942,11 @@ - - return OptionalInt.empty(); - } else { -- this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), factory.getDisplayName())); -- this.initMenu(container); -+ // CraftBukkit start - this.containerMenu = container; -+ if (!this.isImmobile()) this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), Objects.requireNonNullElseGet(title, container::getTitle))); // Paper - Add titleOverride to InventoryOpenEvent -+ // CraftBukkit end -+ this.initMenu(container); - return OptionalInt.of(this.containerCounter); - } - } -@@ -1425,15 +1959,26 @@ - - @Override - public void openHorseInventory(AbstractHorse horse, Container inventory) { -+ // CraftBukkit start - Inventory open hook -+ this.nextContainerCounter(); -+ AbstractContainerMenu container = new HorseInventoryMenu(this.containerCounter, this.getInventory(), inventory, horse, horse.getInventoryColumns()); -+ container.setTitle(horse.getDisplayName()); -+ container = CraftEventFactory.callInventoryOpenEvent(this, container); -+ -+ if (container == null) { -+ inventory.stopOpen(this); -+ return; -+ } -+ // CraftBukkit end - if (this.containerMenu != this.inventoryMenu) { -- this.closeContainer(); -+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.OPEN_NEW); // Paper - Inventory close reason - } - -- this.nextContainerCounter(); -+ // this.nextContainerCounter(); // CraftBukkit - moved up - int i = horse.getInventoryColumns(); - - this.connection.send(new ClientboundHorseScreenOpenPacket(this.containerCounter, i, horse.getId())); -- this.containerMenu = new HorseInventoryMenu(this.containerCounter, this.getInventory(), inventory, horse, i); -+ this.containerMenu = container; // CraftBukkit - this.initMenu(this.containerMenu); - } - -@@ -1456,9 +2001,28 @@ - - @Override - public void closeContainer() { -+ // Paper start - Inventory close reason -+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNKNOWN); -+ } -+ @Override -+ public void closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit -+ // Paper end - Inventory close reason - this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); - this.doCloseContainer(); - } -+ // Paper start - special close for unloaded inventory -+ @Override -+ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ // copied from above -+ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit -+ // Paper end -+ // copied from below -+ this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); -+ this.containerMenu = this.inventoryMenu; -+ // do not run close logic -+ } -+ // Paper end - special close for unloaded inventory - - @Override - public void doCloseContainer() { -@@ -1485,19 +2049,19 @@ - i = Math.round((float) Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ) * 100.0F); - if (i > 0) { - this.awardStat(Stats.SWIM_ONE_CM, i); -- this.causeFoodExhaustion(0.01F * (float) i * 0.01F); -+ this.causeFoodExhaustion(this.level().spigotConfig.swimMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.SWIM); // CraftBukkit - EntityExhaustionEvent // Spigot - } - } else if (this.isEyeInFluid(FluidTags.WATER)) { - i = Math.round((float) Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ) * 100.0F); - if (i > 0) { - this.awardStat(Stats.WALK_UNDER_WATER_ONE_CM, i); -- this.causeFoodExhaustion(0.01F * (float) i * 0.01F); -+ this.causeFoodExhaustion(this.level().spigotConfig.swimMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK_UNDERWATER); // CraftBukkit - EntityExhaustionEvent // Spigot - } - } else if (this.isInWater()) { - i = Math.round((float) Math.sqrt(deltaX * deltaX + deltaZ * deltaZ) * 100.0F); - if (i > 0) { - this.awardStat(Stats.WALK_ON_WATER_ONE_CM, i); -- this.causeFoodExhaustion(0.01F * (float) i * 0.01F); -+ this.causeFoodExhaustion(this.level().spigotConfig.swimMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK_ON_WATER); // CraftBukkit - EntityExhaustionEvent // Spigot - } - } else if (this.onClimbable()) { - if (deltaY > 0.0D) { -@@ -1508,13 +2072,13 @@ - if (i > 0) { - if (this.isSprinting()) { - this.awardStat(Stats.SPRINT_ONE_CM, i); -- this.causeFoodExhaustion(0.1F * (float) i * 0.01F); -+ this.causeFoodExhaustion(this.level().spigotConfig.sprintMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.SPRINT); // CraftBukkit - EntityExhaustionEvent // Spigot - } else if (this.isCrouching()) { - this.awardStat(Stats.CROUCH_ONE_CM, i); -- this.causeFoodExhaustion(0.0F * (float) i * 0.01F); -+ this.causeFoodExhaustion(this.level().spigotConfig.otherMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.CROUCH); // CraftBukkit - EntityExhaustionEvent // Spigot - } else { - this.awardStat(Stats.WALK_ONE_CM, i); -- this.causeFoodExhaustion(0.0F * (float) i * 0.01F); -+ this.causeFoodExhaustion(this.level().spigotConfig.otherMultiplier * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK); // CraftBukkit - EntityExhaustionEvent // Spigot - } - } - } else if (this.isFallFlying()) { -@@ -1557,7 +2121,7 @@ - @Override - public void awardStat(Stat stat, int amount) { - this.stats.increment(this, stat, amount); -- this.getScoreboard().forAllObjectives(stat, this, (scoreaccess) -> { -+ this.level().getCraftServer().getScoreboardManager().forAllObjectives(stat, this, (scoreaccess) -> { - scoreaccess.add(amount); - }); - } -@@ -1565,7 +2129,7 @@ - @Override - public void resetStat(Stat stat) { - this.stats.setValue(this, stat, 0); -- this.getScoreboard().forAllObjectives(stat, this, ScoreAccess::reset); -+ this.level().getCraftServer().getScoreboardManager().forAllObjectives(stat, this, ScoreAccess::reset); // CraftBukkit - Get our scores instead - } - - @Override -@@ -1597,9 +2161,9 @@ - super.jumpFromGround(); - this.awardStat(Stats.JUMP); - if (this.isSprinting()) { -- this.causeFoodExhaustion(0.2F); -+ this.causeFoodExhaustion(this.level().spigotConfig.jumpSprintExhaustion, EntityExhaustionEvent.ExhaustionReason.JUMP_SPRINT); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value - } else { -- this.causeFoodExhaustion(0.05F); -+ this.causeFoodExhaustion(this.level().spigotConfig.jumpWalkExhaustion, EntityExhaustionEvent.ExhaustionReason.JUMP); // CraftBukkit - EntityExhaustionEvent // Spigot - Change to use configurable value - } - - } -@@ -1613,6 +2177,13 @@ - public void disconnect() { - this.disconnected = true; - this.ejectPassengers(); -+ -+ // Paper start - Workaround vehicle not tracking the passenger disconnection dismount -+ if (this.isPassenger() && this.getVehicle() instanceof ServerPlayer) { -+ this.stopRiding(); -+ } -+ // Paper end - Workaround vehicle not tracking the passenger disconnection dismount -+ - if (this.isSleeping()) { - this.stopSleepInBed(true, false); - } -@@ -1625,6 +2196,7 @@ - - public void resetSentInfo() { - this.lastSentHealth = -1.0E8F; -+ this.lastSentExp = -1; // CraftBukkit - Added to reset - } - - @Override -@@ -1661,7 +2233,7 @@ - this.onUpdateAbilities(); - if (alive) { - this.getAttributes().assignBaseValues(oldPlayer.getAttributes()); -- this.getAttributes().assignPermanentModifiers(oldPlayer.getAttributes()); -+ // this.getAttributes().assignPermanentModifiers(entityplayer.getAttributes()); // CraftBukkit - this.setHealth(oldPlayer.getHealth()); - this.foodData = oldPlayer.foodData; - Iterator iterator = oldPlayer.getActiveEffects().iterator(); -@@ -1669,7 +2241,7 @@ - while (iterator.hasNext()) { - MobEffectInstance mobeffect = (MobEffectInstance) iterator.next(); - -- this.addEffect(new MobEffectInstance(mobeffect)); -+ // this.addEffect(new MobEffect(mobeffect)); // CraftBukkit - } - - this.getInventory().replaceWith(oldPlayer.getInventory()); -@@ -1680,7 +2252,7 @@ - this.portalProcess = oldPlayer.portalProcess; - } else { - this.getAttributes().assignBaseValues(oldPlayer.getAttributes()); -- this.setHealth(this.getMaxHealth()); -+ // this.setHealth(this.getMaxHealth()); // CraftBukkit - if (this.serverLevel().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || oldPlayer.isSpectator()) { - this.getInventory().replaceWith(oldPlayer.getInventory()); - this.experienceLevel = oldPlayer.experienceLevel; -@@ -1696,7 +2268,7 @@ - this.lastSentExp = -1; - this.lastSentHealth = -1.0F; - this.lastSentFood = -1; -- this.recipeBook.copyOverData(oldPlayer.recipeBook); -+ // this.recipeBook.copyOverData(entityplayer.recipeBook); // CraftBukkit - this.seenCredits = oldPlayer.seenCredits; - this.enteredNetherPosition = oldPlayer.enteredNetherPosition; - this.chunkTrackingView = oldPlayer.chunkTrackingView; -@@ -1752,19 +2324,19 @@ - } - - @Override -- public boolean teleportTo(ServerLevel world, double destX, double destY, double destZ, Set flags, float yaw, float pitch, boolean resetCamera) { -+ public boolean teleportTo(ServerLevel worldserver, double d0, double d1, double d2, Set set, float f, float f1, boolean flag, TeleportCause cause) { // CraftBukkit - if (this.isSleeping()) { - this.stopSleepInBed(true, true); - } - -- if (resetCamera) { -+ if (flag) { - this.setCamera(this); - } - -- boolean flag1 = super.teleportTo(world, destX, destY, destZ, flags, yaw, pitch, resetCamera); -+ boolean flag1 = super.teleportTo(worldserver, d0, d1, d2, set, f, f1, flag, cause); // CraftBukkit - - if (flag1) { -- this.setYHeadRot(flags.contains(Relative.Y_ROT) ? this.getYHeadRot() + yaw : yaw); -+ this.setYHeadRot(set.contains(Relative.Y_ROT) ? this.getYHeadRot() + f : f); - } - - return flag1; -@@ -1799,10 +2371,18 @@ - } - - public boolean setGameMode(GameType gameMode) { -+ // Paper start - Expand PlayerGameModeChangeEvent -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null); -+ return event == null ? false : event.isCancelled(); -+ } -+ @Nullable -+ public org.bukkit.event.player.PlayerGameModeChangeEvent setGameMode(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component message) { - boolean flag = this.isSpectator(); - -- if (!this.gameMode.changeGameModeForPlayer(gameMode)) { -- return false; -+ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.gameMode.changeGameModeForPlayer(gameMode, cause, message); -+ if (event == null || event.isCancelled()) { -+ return null; -+ // Paper end - Expand PlayerGameModeChangeEvent - } else { - this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, (float) gameMode.getId())); - if (gameMode == GameType.SPECTATOR) { -@@ -1818,7 +2398,7 @@ - - this.onUpdateAbilities(); - this.updateEffectVisibility(); -- return true; -+ return event; // Paper - Expand PlayerGameModeChangeEvent - } - } - -@@ -1861,8 +2441,13 @@ - } - - public void sendChatMessage(OutgoingChatMessage message, boolean filterMaskEnabled, ChatType.Bound params) { -+ // Paper start -+ this.sendChatMessage(message, filterMaskEnabled, params, null); -+ } -+ public void sendChatMessage(OutgoingChatMessage message, boolean filterMaskEnabled, ChatType.Bound params, @Nullable Component unsigned) { -+ // Paper end - if (this.acceptsChatMessages()) { -- message.sendToPlayer(this, filterMaskEnabled, params); -+ message.sendToPlayer(this, filterMaskEnabled, params, unsigned); // Paper - } - - } -@@ -1878,7 +2463,36 @@ - } - - public void updateOptions(ClientInformation clientOptions) { -+ // Paper start - settings event -+ new com.destroystokyo.paper.event.player.PlayerClientOptionsChangeEvent(this.getBukkitEntity(), Util.make(new java.util.IdentityHashMap<>(), map -> { -+ map.put(com.destroystokyo.paper.ClientOption.LOCALE, clientOptions.language()); -+ map.put(com.destroystokyo.paper.ClientOption.VIEW_DISTANCE, clientOptions.viewDistance()); -+ map.put(com.destroystokyo.paper.ClientOption.CHAT_VISIBILITY, com.destroystokyo.paper.ClientOption.ChatVisibility.valueOf(clientOptions.chatVisibility().name())); -+ map.put(com.destroystokyo.paper.ClientOption.CHAT_COLORS_ENABLED, clientOptions.chatColors()); -+ map.put(com.destroystokyo.paper.ClientOption.SKIN_PARTS, new com.destroystokyo.paper.PaperSkinParts(clientOptions.modelCustomisation())); -+ map.put(com.destroystokyo.paper.ClientOption.MAIN_HAND, clientOptions.mainHand() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT); -+ map.put(com.destroystokyo.paper.ClientOption.TEXT_FILTERING_ENABLED, clientOptions.textFilteringEnabled()); -+ map.put(com.destroystokyo.paper.ClientOption.ALLOW_SERVER_LISTINGS, clientOptions.allowsListing()); -+ map.put(com.destroystokyo.paper.ClientOption.PARTICLE_VISIBILITY, com.destroystokyo.paper.ClientOption.ParticleVisibility.valueOf(clientOptions.particleStatus().name())); -+ })).callEvent(); -+ // Paper end - settings event -+ // CraftBukkit start -+ if (this.getMainArm() != clientOptions.mainHand()) { -+ PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(this.getBukkitEntity(), this.getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT); -+ this.server.server.getPluginManager().callEvent(event); -+ } -+ if (this.language == null || !this.language.equals(clientOptions.language())) { // Paper -+ PlayerLocaleChangeEvent event = new PlayerLocaleChangeEvent(this.getBukkitEntity(), clientOptions.language()); -+ this.server.server.getPluginManager().callEvent(event); -+ } -+ // CraftBukkit end -+ // Paper start - don't call options events on login -+ this.updateOptionsNoEvents(clientOptions); -+ } -+ public void updateOptionsNoEvents(ClientInformation clientOptions) { -+ // Paper end - this.language = clientOptions.language(); -+ this.adventure$locale = java.util.Objects.requireNonNullElse(net.kyori.adventure.translation.Translator.parseLocale(this.language), java.util.Locale.US); // Paper - this.requestedViewDistance = clientOptions.viewDistance(); - this.chatVisibility = clientOptions.chatVisibility(); - this.canChatColor = clientOptions.chatColors(); -@@ -1957,12 +2571,27 @@ - - this.camera = (Entity) (entity == null ? this : entity); - if (entity1 != this.camera) { -+ // Paper start - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity -+ if (this.camera == this) { -+ com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent playerStopSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStopSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity()); -+ if (!playerStopSpectatingEntityEvent.callEvent()) { -+ this.camera = entity1; // rollback camera entity again -+ return; -+ } -+ } else { -+ com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent playerStartSpectatingEntityEvent = new com.destroystokyo.paper.event.player.PlayerStartSpectatingEntityEvent(this.getBukkitEntity(), entity1.getBukkitEntity(), entity.getBukkitEntity()); -+ if (!playerStartSpectatingEntityEvent.callEvent()) { -+ this.camera = entity1; // rollback camera entity again -+ return; -+ } -+ } -+ // Paper end - Add PlayerStartSpectatingEntityEvent and PlayerStopSpectatingEntity - Level world = this.camera.level(); - - if (world instanceof ServerLevel) { - ServerLevel worldserver = (ServerLevel) world; - -- this.teleportTo(worldserver, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), false); -+ this.teleportTo(worldserver, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), false, TeleportCause.SPECTATE); // CraftBukkit - } - - if (entity != null) { -@@ -1999,11 +2628,11 @@ - - @Nullable - public Component getTabListDisplayName() { -- return null; -+ return this.listName; // CraftBukkit - } - - public int getTabListOrder() { -- return 0; -+ return this.listOrder; // CraftBukkit - } - - @Override -@@ -2045,12 +2674,44 @@ - this.setRespawnPosition(player.getRespawnDimension(), player.getRespawnPosition(), player.getRespawnAngle(), player.isRespawnForced(), false); - } - -+ @Deprecated // Paper - Add PlayerSetSpawnEvent - public void setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage) { -+ // Paper start - Add PlayerSetSpawnEvent -+ this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.UNKNOWN); -+ } -+ @Deprecated -+ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, PlayerSpawnChangeEvent.Cause cause) { -+ return this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, cause == PlayerSpawnChangeEvent.Cause.RESET ? -+ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN : com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.valueOf(cause.name())); -+ } -+ public boolean setRespawnPosition(ResourceKey dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause cause) { -+ Location spawnLoc = null; -+ boolean willNotify = false; - if (pos != null) { - boolean flag2 = pos.equals(this.respawnPosition) && dimension.equals(this.respawnDimension); -+ spawnLoc = io.papermc.paper.util.MCUtil.toLocation(this.getServer().getLevel(dimension), pos); -+ spawnLoc.setYaw(angle); -+ willNotify = sendMessage && !flag2; -+ } - -- if (sendMessage && !flag2) { -- this.sendSystemMessage(Component.translatable("block.minecraft.set_spawn")); -+ PlayerSpawnChangeEvent dumbEvent = new PlayerSpawnChangeEvent(this.getBukkitEntity(), spawnLoc, forced, -+ cause == com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN ? PlayerSpawnChangeEvent.Cause.RESET : PlayerSpawnChangeEvent.Cause.valueOf(cause.name())); -+ dumbEvent.callEvent(); -+ -+ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent event = new com.destroystokyo.paper.event.player.PlayerSetSpawnEvent(this.getBukkitEntity(), cause, dumbEvent.getNewSpawn(), dumbEvent.isForced(), willNotify, willNotify ? net.kyori.adventure.text.Component.translatable("block.minecraft.set_spawn") : null); -+ event.setCancelled(dumbEvent.isCancelled()); -+ if (!event.callEvent()) { -+ return false; -+ } -+ if (event.getLocation() != null) { -+ dimension = event.getLocation().getWorld() != null ? ((CraftWorld) event.getLocation().getWorld()).getHandle().dimension() : dimension; -+ pos = io.papermc.paper.util.MCUtil.toBlockPosition(event.getLocation()); -+ angle = event.getLocation().getYaw(); -+ forced = event.isForced(); -+ // Paper end - Add PlayerSetSpawnEvent -+ -+ if (event.willNotifyPlayer() && event.getNotification() != null) { // Paper - Add PlayerSetSpawnEvent -+ this.sendSystemMessage(PaperAdventure.asVanilla(event.getNotification())); // Paper - Add PlayerSetSpawnEvent - } - - this.respawnPosition = pos; -@@ -2064,6 +2725,7 @@ - this.respawnForced = false; - } - -+ return true; // Paper - Add PlayerSetSpawnEvent - } - - public SectionPos getLastSectionPos() { -@@ -2088,18 +2750,44 @@ - } - - @Override -- public ItemEntity drop(ItemStack stack, boolean throwRandomly, boolean retainOwnership) { -- ItemEntity entityitem = this.createItemStackToDrop(stack, throwRandomly, retainOwnership); -+ public ItemEntity drop(ItemStack itemstack, boolean flag, boolean flag1, boolean callEvent) { // CraftBukkit - SPIGOT-2942: Add boolean to call event -+ ItemEntity entityitem = this.createItemStackToDrop(itemstack, flag, flag1); - - if (entityitem == null) { - return null; - } else { -+ // CraftBukkit start - fire PlayerDropItemEvent -+ if (callEvent) { -+ Player player = (Player) this.getBukkitEntity(); -+ org.bukkit.entity.Item drop = (org.bukkit.entity.Item) entityitem.getBukkitEntity(); -+ -+ PlayerDropItemEvent event = new PlayerDropItemEvent(player, drop); -+ this.level().getCraftServer().getPluginManager().callEvent(event); -+ -+ if (event.isCancelled()) { -+ org.bukkit.inventory.ItemStack cur = player.getInventory().getItemInHand(); -+ if (flag1 && (cur == null || cur.getAmount() == 0)) { -+ // The complete stack was dropped -+ player.getInventory().setItemInHand(drop.getItemStack()); -+ } else if (flag1 && cur.isSimilar(drop.getItemStack()) && cur.getAmount() < cur.getMaxStackSize() && drop.getItemStack().getAmount() == 1) { -+ // Only one item is dropped -+ cur.setAmount(cur.getAmount() + 1); -+ player.getInventory().setItemInHand(cur); -+ } else { -+ // Fallback -+ player.getInventory().addItem(drop.getItemStack()); -+ } -+ return null; -+ } -+ } -+ // CraftBukkit end -+ - this.level().addFreshEntity(entityitem); - ItemStack itemstack1 = entityitem.getItem(); - -- if (retainOwnership) { -+ if (flag1) { - if (!itemstack1.isEmpty()) { -- this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), stack.getCount()); -+ this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), itemstack1.getCount()); // Paper - Fix PlayerDropItemEvent using wrong item - } - - this.awardStat(Stats.DROP); -@@ -2115,6 +2803,11 @@ - return null; - } else { - double d0 = this.getEyeY() - 0.30000001192092896D; -+ // Paper start -+ ItemStack tmp = stack.copy(); -+ stack.setCount(0); -+ stack = tmp; -+ // Paper end - ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), d0, this.getZ(), stack); - - entityitem.setPickUpDelay(40); -@@ -2166,6 +2859,16 @@ - } - - public void loadGameTypes(@Nullable CompoundTag nbt) { -+ // Paper start - Expand PlayerGameModeChangeEvent -+ if (this.server.getForcedGameType() != null && this.server.getForcedGameType() != ServerPlayer.readPlayerMode(nbt, "playerGameType")) { -+ if (new org.bukkit.event.player.PlayerGameModeChangeEvent(this.getBukkitEntity(), org.bukkit.GameMode.getByValue(this.server.getDefaultGameType().getId()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null).callEvent()) { -+ this.gameMode.setGameModeForPlayer(this.server.getForcedGameType(), GameType.DEFAULT_MODE); -+ } else { -+ this.gameMode.setGameModeForPlayer(ServerPlayer.readPlayerMode(nbt,"playerGameType"), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); -+ } -+ return; -+ } -+ // Paper end - Expand PlayerGameModeChangeEvent - this.gameMode.setGameModeForPlayer(this.calculateGameModeForNewPlayer(ServerPlayer.readPlayerMode(nbt, "playerGameType")), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType")); - } - -@@ -2275,9 +2978,15 @@ - - @Override - public void stopRiding() { -+ // Paper start - Force entity dismount during teleportation -+ this.stopRiding(false); -+ } -+ @Override -+ public void stopRiding(boolean suppressCancellation) { -+ // Paper end - Force entity dismount during teleportation - Entity entity = this.getVehicle(); - -- super.stopRiding(); -+ super.stopRiding(suppressCancellation); // Paper - Force entity dismount during teleportation - if (entity instanceof LivingEntity entityliving) { - Iterator iterator = entityliving.getActiveEffects().iterator(); - -@@ -2371,20 +3080,165 @@ - } - - public static long placeEnderPearlTicket(ServerLevel world, ChunkPos chunkPos) { -- world.getChunkSource().addRegionTicket(TicketType.ENDER_PEARL, chunkPos, 2, chunkPos); -+ if (!world.paperConfig().misc.legacyEnderPearlBehavior) world.getChunkSource().addRegionTicket(TicketType.ENDER_PEARL, chunkPos, 2, chunkPos); // Paper - Allow using old ender pearl behavior - return TicketType.ENDER_PEARL.timeout(); - } - -- public static record RespawnPosAngle(Vec3 position, float yaw) { -+ // CraftBukkit start -+ public static record RespawnPosAngle(Vec3 position, float yaw, boolean isBedSpawn, boolean isAnchorSpawn) { - -- public static ServerPlayer.RespawnPosAngle of(Vec3 respawnPos, BlockPos currentPos) { -- return new ServerPlayer.RespawnPosAngle(respawnPos, calculateLookAtYaw(respawnPos, currentPos)); -+ public static ServerPlayer.RespawnPosAngle of(Vec3 vec3d, BlockPos blockposition, boolean isBedSpawn, boolean isAnchorSpawn) { -+ return new ServerPlayer.RespawnPosAngle(vec3d, calculateLookAtYaw(vec3d, blockposition), isBedSpawn, isAnchorSpawn); -+ // CraftBukkit end - } - - private static float calculateLookAtYaw(Vec3 respawnPos, BlockPos currentPos) { - Vec3 vec3d1 = Vec3.atBottomCenterOf(currentPos).subtract(respawnPos).normalize(); - - return (float) Mth.wrapDegrees(Mth.atan2(vec3d1.z, vec3d1.x) * 57.2957763671875D - 90.0D); -+ } -+ } -+ -+ // CraftBukkit start - Add per-player time and weather. -+ public long timeOffset = 0; -+ public boolean relativeTime = true; -+ -+ public long getPlayerTime() { -+ if (this.relativeTime) { -+ // Adds timeOffset to the current server time. -+ return this.level().getDayTime() + this.timeOffset; -+ } else { -+ // Adds timeOffset to the beginning of this day. -+ return this.level().getDayTime() - (this.level().getDayTime() % 24000) + this.timeOffset; - } - } -+ -+ public WeatherType weather = null; -+ -+ public WeatherType getPlayerWeather() { -+ return this.weather; -+ } -+ -+ public void setPlayerWeather(WeatherType type, boolean plugin) { -+ if (!plugin && this.weather != null) { -+ return; -+ } -+ -+ if (plugin) { -+ this.weather = type; -+ } -+ -+ if (type == WeatherType.DOWNFALL) { -+ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.STOP_RAINING, 0)); -+ } else { -+ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0)); -+ } -+ } -+ -+ private float pluginRainPosition; -+ private float pluginRainPositionPrevious; -+ -+ public void updateWeather(float oldRain, float newRain, float oldThunder, float newThunder) { -+ if (this.weather == null) { -+ // Vanilla -+ if (oldRain != newRain) { -+ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, newRain)); -+ } -+ } else { -+ // Plugin -+ if (this.pluginRainPositionPrevious != this.pluginRainPosition) { -+ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.pluginRainPosition)); -+ } -+ } -+ -+ if (oldThunder != newThunder) { -+ if (this.weather == WeatherType.DOWNFALL || this.weather == null) { -+ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, newThunder)); -+ } else { -+ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 0)); -+ } -+ } -+ } -+ -+ public void tickWeather() { -+ if (this.weather == null) return; -+ -+ this.pluginRainPositionPrevious = this.pluginRainPosition; -+ if (this.weather == WeatherType.DOWNFALL) { -+ this.pluginRainPosition += 0.01; -+ } else { -+ this.pluginRainPosition -= 0.01; -+ } -+ -+ this.pluginRainPosition = Mth.clamp(this.pluginRainPosition, 0.0F, 1.0F); -+ } -+ -+ public void resetPlayerWeather() { -+ this.weather = null; -+ this.setPlayerWeather(this.level().getLevelData().isRaining() ? WeatherType.DOWNFALL : WeatherType.CLEAR, false); -+ } -+ -+ @Override -+ public String toString() { -+ return super.toString() + "(" + this.getScoreboardName() + " at " + this.getX() + "," + this.getY() + "," + this.getZ() + ")"; -+ } -+ -+ // SPIGOT-1903, MC-98153 -+ public void forceSetPositionRotation(double x, double y, double z, float yaw, float pitch) { -+ this.moveTo(x, y, z, yaw, pitch); -+ this.connection.resetPosition(); -+ } -+ -+ @Override -+ public boolean isImmobile() { -+ return super.isImmobile() || (this.connection != null && this.connection.isDisconnected()); // Paper - Fix duplication bugs -+ } -+ -+ @Override -+ public Scoreboard getScoreboard() { -+ return this.getBukkitEntity().getScoreboard().getHandle(); -+ } -+ -+ public void reset() { -+ float exp = 0; -+ -+ if (this.keepLevel) { // CraftBukkit - SPIGOT-6687: Only use keepLevel (was pre-set with RULE_KEEPINVENTORY value in PlayerDeathEvent) -+ exp = this.experienceProgress; -+ this.newTotalExp = this.totalExperience; -+ this.newLevel = this.experienceLevel; -+ } -+ -+ this.setHealth(this.getMaxHealth()); -+ this.stopUsingItem(); // CraftBukkit - SPIGOT-6682: Clear active item on reset -+ this.setAirSupply(this.getMaxAirSupply()); // Paper - Reset players airTicks on respawn -+ this.setRemainingFireTicks(0); -+ this.fallDistance = 0; -+ this.foodData = new FoodData(); -+ this.experienceLevel = this.newLevel; -+ this.totalExperience = this.newTotalExp; -+ this.experienceProgress = 0; -+ this.deathTime = 0; -+ this.setArrowCount(0, true); // CraftBukkit - ArrowBodyCountChangeEvent -+ this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DEATH); -+ this.effectsDirty = true; -+ this.containerMenu = this.inventoryMenu; -+ this.lastHurtByPlayer = null; -+ this.lastHurtByMob = null; -+ this.combatTracker = new CombatTracker(this); -+ this.lastSentExp = -1; -+ if (this.keepLevel) { // CraftBukkit - SPIGOT-6687: Only use keepLevel (was pre-set with RULE_KEEPINVENTORY value in PlayerDeathEvent) -+ this.experienceProgress = exp; -+ } else { -+ this.giveExperiencePoints(this.newExp); -+ } -+ this.keepLevel = false; -+ this.setDeltaMovement(0, 0, 0); // CraftBukkit - SPIGOT-6948: Reset velocity on death -+ this.skipDropExperience = false; // CraftBukkit - SPIGOT-7462: Reset experience drop skip, so that further deaths drop xp -+ } -+ -+ @Override -+ public CraftPlayer getBukkitEntity() { -+ return (CraftPlayer) super.getBukkitEntity(); -+ } -+ // CraftBukkit end - } diff --git a/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java b/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java index 8cf720f085..109e569b7b 100644 --- a/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java +++ b/paper-server/src/main/java/io/papermc/paper/configuration/Configurations.java @@ -275,7 +275,7 @@ public abstract class Configurations { } public Path getWorldConfigFile(ServerLevel level) { - return level.convertable.levelDirectory.path().resolve(this.worldConfigFileName); + return level.levelStorageAccess.levelDirectory.path().resolve(this.worldConfigFileName); } public static class ContextMap { diff --git a/paper-server/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/paper-server/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java index c5644d8d64..098ab351de 100644 --- a/paper-server/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +++ b/paper-server/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java @@ -327,7 +327,7 @@ public class PaperConfigurations extends Configurations