From 8d0f37228de630696169f2861357352fc43ab96f Mon Sep 17 00:00:00 2001 From: blablubbabc Date: Thu, 16 May 2019 01:11:20 +0200 Subject: [PATCH] SPIGOT-4752: Fixed inconsistency between isChunkLoaded and chunk load/unload events --- nms-patches/Chunk.patch | 55 +++++------ nms-patches/ChunkProviderServer.patch | 50 +++++++++- nms-patches/Entity.patch | 74 ++++++++------- nms-patches/PlayerChunk.patch | 93 +++++++++++++++---- nms-patches/PlayerChunkMap.patch | 67 +++---------- .../org/bukkit/craftbukkit/CraftChunk.java | 6 ++ .../org/bukkit/craftbukkit/CraftWorld.java | 5 +- .../craftbukkit/entity/CraftEntity.java | 2 +- 8 files changed, 209 insertions(+), 143 deletions(-) diff --git a/nms-patches/Chunk.patch b/nms-patches/Chunk.patch index 923879ef94..a617cae68f 100644 --- a/nms-patches/Chunk.patch +++ b/nms-patches/Chunk.patch @@ -1,32 +1,18 @@ --- a/net/minecraft/server/Chunk.java +++ b/net/minecraft/server/Chunk.java -@@ -22,6 +22,13 @@ - import org.apache.logging.log4j.LogManager; - import org.apache.logging.log4j.Logger; - -+// CraftBukkit start -+import com.google.common.collect.Lists; -+import java.util.LinkedList; -+import org.bukkit.craftbukkit.event.CraftEventFactory; -+import org.bukkit.event.entity.CreatureSpawnEvent; -+// CraftBukkit end -+ - public class Chunk implements IChunkAccess { - - private static final Logger LOGGER = LogManager.getLogger(); -@@ -95,8 +102,19 @@ +@@ -95,8 +95,19 @@ } } + // CraftBukkit start + this.bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this); -+ } -+ + } + + public org.bukkit.Chunk bukkitChunk; + public org.bukkit.Chunk getBukkitChunk() { + return bukkitChunk; - } - ++ } ++ + public boolean mustNotSave; + public boolean needsDecoration; + // CraftBukkit end @@ -34,7 +20,7 @@ public Chunk(World world, ProtoChunk protochunk) { this(world, protochunk.getPos(), protochunk.getBiomeIndex(), protochunk.p(), protochunk.n(), protochunk.o(), protochunk.q(), protochunk.getSections(), (Consumer) null); Iterator iterator = protochunk.y().iterator(); -@@ -138,6 +156,7 @@ +@@ -138,6 +149,7 @@ this.b(protochunk.r()); this.s = true; @@ -42,7 +28,7 @@ } @Override -@@ -228,9 +247,16 @@ +@@ -228,9 +240,16 @@ } } @@ -59,7 +45,7 @@ int i = blockposition.getX() & 15; int j = blockposition.getY(); int k = blockposition.getZ() & 15; -@@ -282,7 +308,8 @@ +@@ -282,7 +301,8 @@ } } @@ -69,7 +55,7 @@ iblockdata.onPlace(this.world, blockposition, iblockdata1, flag); } -@@ -382,7 +409,12 @@ +@@ -382,7 +402,12 @@ @Nullable public TileEntity a(BlockPosition blockposition, Chunk.EnumTileEntityState chunk_enumtileentitystate) { @@ -83,7 +69,7 @@ if (tileentity == null) { NBTTagCompound nbttagcompound = (NBTTagCompound) this.e.remove(blockposition); -@@ -429,6 +461,13 @@ +@@ -429,6 +454,13 @@ tileentity1.m(); } @@ -97,7 +83,7 @@ } } -@@ -457,6 +496,41 @@ +@@ -457,6 +489,50 @@ } @@ -113,6 +99,7 @@ + server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(this.bukkitChunk, this.needsDecoration)); + + if (this.needsDecoration) { ++ this.needsDecoration = false; + java.util.Random random = new java.util.Random(); + random.setSeed(world.getSeed()); + long xRand = random.nextLong() / 2L * 2L + 1L; @@ -134,12 +121,20 @@ + } + } + } ++ ++ public void unloadCallback() { ++ org.bukkit.Server server = this.world.getServer(); ++ org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(this.bukkitChunk, this.isNeedsSaving()); ++ server.getPluginManager().callEvent(unloadEvent); ++ // note: saving can be prevented, but not forced if no saving is actually required ++ this.mustNotSave = !unloadEvent.isSaveChunk(); ++ } + // CraftBukkit end + public void markDirty() { this.s = true; } -@@ -531,7 +605,7 @@ +@@ -531,7 +607,7 @@ Iterator iterator = this.entitySlices[k].a(oclass).iterator(); while (iterator.hasNext()) { @@ -148,7 +143,7 @@ if (t0.getBoundingBox().c(axisalignedbb) && (predicate == null || predicate.test(t0))) { list.add(t0); -@@ -605,7 +679,7 @@ +@@ -605,7 +681,7 @@ @Override public boolean isNeedsSaving() { @@ -157,7 +152,7 @@ } public void d(boolean flag) { -@@ -746,7 +820,7 @@ +@@ -746,7 +822,7 @@ public void B() { if (this.o instanceof ProtoChunkTickList) { @@ -166,7 +161,7 @@ return this.getType(blockposition).getBlock(); }); this.o = TickListEmpty.a(); -@@ -756,7 +830,7 @@ +@@ -756,7 +832,7 @@ } if (this.p instanceof ProtoChunkTickList) { @@ -175,7 +170,7 @@ return this.getFluid(blockposition).getType(); }); this.p = TickListEmpty.a(); -@@ -768,12 +842,12 @@ +@@ -768,12 +844,12 @@ } public void a(WorldServer worldserver) { diff --git a/nms-patches/ChunkProviderServer.patch b/nms-patches/ChunkProviderServer.patch index 02e71aa065..0123c9e34b 100644 --- a/nms-patches/ChunkProviderServer.patch +++ b/nms-patches/ChunkProviderServer.patch @@ -1,6 +1,49 @@ --- a/net/minecraft/server/ChunkProviderServer.java +++ b/net/minecraft/server/ChunkProviderServer.java -@@ -241,6 +241,17 @@ +@@ -81,7 +81,7 @@ + for (int l = 0; l < 4; ++l) { + if (k == this.n[l] && chunkstatus == this.o[l]) { + ichunkaccess = this.p[l]; +- if (ichunkaccess != null || !flag) { ++ 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; + } + } +@@ -125,7 +125,15 @@ + int l = 33 + ChunkStatus.a(chunkstatus); + PlayerChunk playerchunk = this.getChunk(k); + +- if (flag) { ++ // CraftBukkit start - don't add new ticket for currently unloading chunk ++ boolean currentlyUnloading = false; ++ if (playerchunk != null) { ++ PlayerChunk.State oldChunkState = PlayerChunk.c(playerchunk.oldTicketLevel); // PAIL getChunkState ++ PlayerChunk.State currentChunkState = PlayerChunk.c(playerchunk.getTicketLevel()); // PAIL getChunkState ++ currentlyUnloading = (oldChunkState.a(PlayerChunk.State.BORDER) && !currentChunkState.a(PlayerChunk.State.BORDER)); // PAIL isAtLeast ++ } ++ if (flag && !currentlyUnloading) { ++ // CraftBukkit end + this.chunkMapDistance.a(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); + if (this.a(playerchunk, l)) { + GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler(); +@@ -144,14 +152,14 @@ + } + + private boolean a(@Nullable PlayerChunk playerchunk, int i) { +- return playerchunk == null || playerchunk.getTicketLevel() > i; ++ return playerchunk == null || playerchunk.oldTicketLevel > i; // CraftBukkit using oldTicketLevel for isLoaded checks + } + + public boolean isLoaded(int i, int j) { + PlayerChunk playerchunk = this.getChunk((new ChunkCoordIntPair(i, j)).pair()); + int k = 33 + ChunkStatus.a(ChunkStatus.FULL); + +- return playerchunk != null && playerchunk.getTicketLevel() <= k ? ((Either) playerchunk.getStatusFuture(ChunkStatus.FULL).getNow(PlayerChunk.UNLOADED_CHUNK_ACCESS)).left().isPresent() : false; ++ return playerchunk != null && playerchunk.oldTicketLevel <= k ? ((Either) playerchunk.getStatusFuture(ChunkStatus.FULL).getNow(PlayerChunk.UNLOADED_CHUNK_ACCESS)).left().isPresent() : false; // CraftBukkit using oldTicketLevel for isLoaded checks + } + + @Override +@@ -241,6 +249,18 @@ this.playerChunkMap.close(); } @@ -12,13 +55,14 @@ + this.world.getMethodProfiler().exitEnter("unload"); + this.playerChunkMap.unloadChunks(() -> true); + this.world.getMethodProfiler().exit(); ++ this.l(); // PAIL clearCache + } + // CraftBukkit end + public void tick(BooleanSupplier booleansupplier) { this.world.getMethodProfiler().enter("purge"); this.chunkMapDistance.purgeTickets(); -@@ -260,13 +271,13 @@ +@@ -260,13 +280,13 @@ this.lastTickTime = i; WorldData worlddata = this.world.getWorldData(); boolean flag = worlddata.getType() == WorldType.DEBUG_ALL_BLOCK_STATES; @@ -34,7 +78,7 @@ this.world.getMethodProfiler().enter("naturalSpawnCount"); int l = this.chunkMapDistance.b(); -@@ -299,8 +310,30 @@ +@@ -299,8 +319,30 @@ for (int j1 = 0; j1 < i1; ++j1) { EnumCreatureType enumcreaturetype = aenumcreaturetype1[j1]; diff --git a/nms-patches/Entity.patch b/nms-patches/Entity.patch index 8691e74483..60a11e7a3b 100644 --- a/nms-patches/Entity.patch +++ b/nms-patches/Entity.patch @@ -58,7 +58,7 @@ protected static final Logger LOGGER = LogManager.getLogger(); private static final AtomicInteger entityCount = new AtomicInteger(); private static final List c = Collections.emptyList(); -@@ -106,6 +155,16 @@ +@@ -106,6 +155,20 @@ private long aH; private EntitySize size; private float headHeight; @@ -71,11 +71,15 @@ + public float getBukkitYaw() { + return this.yaw; + } ++ ++ public boolean isChunkLoaded() { ++ return world.isChunkLoaded((int) Math.floor(this.locX) >> 4, (int) Math.floor(this.locZ) >> 4); ++ } + // CraftBukkit end public Entity(EntityTypes entitytypes, World world) { this.id = Entity.entityCount.incrementAndGet(); -@@ -204,6 +263,12 @@ +@@ -204,6 +267,12 @@ } protected void setPose(EntityPose entitypose) { @@ -88,7 +92,7 @@ this.datawatcher.set(Entity.POSE, entitypose); } -@@ -212,6 +277,33 @@ +@@ -212,6 +281,33 @@ } protected void setYawPitch(float f, float f1) { @@ -122,7 +126,7 @@ this.yaw = f % 360.0F; this.pitch = f1 % 360.0F; } -@@ -224,6 +316,7 @@ +@@ -224,6 +320,7 @@ float f1 = this.size.height; this.a(new AxisAlignedBB(d0 - (double) f, d1, d2 - (double) f, d0 + (double) f, d1 + (double) f1, d2 + (double) f)); @@ -130,7 +134,7 @@ } public void tick() { -@@ -234,6 +327,15 @@ +@@ -234,6 +331,15 @@ this.entityBaseTick(); } @@ -146,7 +150,7 @@ public void entityBaseTick() { this.world.getMethodProfiler().enter("entityBaseTick"); if (this.isPassenger() && this.getVehicle().dead) { -@@ -250,7 +352,7 @@ +@@ -250,7 +356,7 @@ this.lastZ = this.locZ; this.lastPitch = this.pitch; this.lastYaw = this.yaw; @@ -155,7 +159,7 @@ this.az(); this.m(); if (this.world.isClientSide) { -@@ -300,12 +402,44 @@ +@@ -300,12 +406,44 @@ protected void burnFromLava() { if (!this.isFireProof()) { @@ -201,7 +205,7 @@ int j = i * 20; if (this instanceof EntityLiving) { -@@ -401,6 +535,28 @@ +@@ -401,6 +539,28 @@ block1.a((IBlockAccess) this.world, this); } @@ -230,7 +234,7 @@ if (this.playStepSound() && (!this.onGround || !this.isSneaking() || !(this instanceof EntityHuman)) && !this.isPassenger()) { double d0 = vec3d1.x; double d1 = vec3d1.y; -@@ -454,7 +610,14 @@ +@@ -454,7 +614,14 @@ if (!flag) { ++this.fireTicks; if (this.fireTicks == 0) { @@ -246,7 +250,7 @@ } } -@@ -565,7 +728,7 @@ +@@ -565,7 +732,7 @@ VoxelShape voxelshape = this.world.getWorldBorder().a(); Stream stream = VoxelShapes.c(voxelshape, VoxelShapes.a(axisalignedbb.shrink(1.0E-7D)), OperatorBoolean.AND) ? Stream.empty() : Stream.of(voxelshape); AxisAlignedBB axisalignedbb1 = axisalignedbb.a(vec3d).g(1.0E-7D); @@ -255,7 +259,7 @@ return !this.x(entity); }).flatMap((entity) -> { return Stream.of(entity.ap(), this.j(entity)); -@@ -649,6 +812,7 @@ +@@ -649,6 +816,7 @@ this.locX = (axisalignedbb.minX + axisalignedbb.maxX) / 2.0D; this.locY = axisalignedbb.minY; this.locZ = (axisalignedbb.minZ + axisalignedbb.maxZ) / 2.0D; @@ -263,7 +267,7 @@ } protected SoundEffect getSoundSwim() { -@@ -820,7 +984,7 @@ +@@ -820,7 +988,7 @@ return null; } @@ -272,7 +276,7 @@ if (!this.isFireProof()) { this.damageEntity(DamageSource.FIRE, (float) i); } -@@ -1053,6 +1217,13 @@ +@@ -1053,6 +1221,13 @@ } public void spawnIn(World world) { @@ -286,7 +290,7 @@ this.world = world; } -@@ -1078,6 +1249,7 @@ +@@ -1078,6 +1253,7 @@ this.lastYaw -= 360.0F; } @@ -294,7 +298,7 @@ this.setPosition(this.locX, this.locY, this.locZ); this.setYawPitch(f, f1); } -@@ -1246,7 +1418,7 @@ +@@ -1246,7 +1422,7 @@ public boolean c(NBTTagCompound nbttagcompound) { String s = this.getSaveID(); @@ -303,7 +307,7 @@ nbttagcompound.setString("id", s); this.save(nbttagcompound); return true; -@@ -1265,15 +1437,33 @@ +@@ -1265,15 +1441,33 @@ Vec3D vec3d = this.getMot(); nbttagcompound.set("Motion", this.a(vec3d.x, vec3d.y, vec3d.z)); @@ -338,7 +342,7 @@ IChatBaseComponent ichatbasecomponent = this.getCustomName(); if (ichatbasecomponent != null) { -@@ -1331,6 +1521,11 @@ +@@ -1331,6 +1525,11 @@ } } @@ -350,7 +354,7 @@ return nbttagcompound; } catch (Throwable throwable) { CrashReport crashreport = CrashReport.a(throwable, "Saving entity NBT"); -@@ -1371,7 +1566,7 @@ +@@ -1371,7 +1570,7 @@ this.setAirTicks(nbttagcompound.getShort("Air")); this.onGround = nbttagcompound.getBoolean("OnGround"); if (nbttagcompound.hasKey("Dimension")) { @@ -359,7 +363,7 @@ } this.invulnerable = nbttagcompound.getBoolean("Invulnerable"); -@@ -1414,6 +1609,42 @@ +@@ -1414,6 +1613,42 @@ } else { throw new IllegalStateException("Entity has invalid position"); } @@ -402,7 +406,7 @@ } catch (Throwable throwable) { CrashReport crashreport = CrashReport.a(throwable, "Loading entity NBT"); CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Entity being loaded"); -@@ -1489,9 +1720,22 @@ +@@ -1489,9 +1724,22 @@ } else if (this.world.isClientSide) { return null; } else { @@ -425,7 +429,7 @@ this.world.addEntity(entityitem); return entityitem; } -@@ -1595,7 +1839,7 @@ +@@ -1595,7 +1843,7 @@ } this.vehicle = entity; @@ -434,7 +438,7 @@ return true; } } -@@ -1620,15 +1864,36 @@ +@@ -1620,15 +1868,36 @@ Entity entity = this.vehicle; this.vehicle = null; @@ -473,7 +477,7 @@ if (!this.world.isClientSide && entity instanceof EntityHuman && !(this.getRidingPassenger() instanceof EntityHuman)) { this.passengers.add(0, entity); } else { -@@ -1636,15 +1901,33 @@ +@@ -1636,15 +1905,33 @@ } } @@ -508,7 +512,7 @@ } protected boolean q(Entity entity) { -@@ -1687,11 +1970,17 @@ +@@ -1687,11 +1974,17 @@ int i = this.ab(); if (this.ai) { @@ -528,7 +532,7 @@ this.world.getMethodProfiler().exit(); } -@@ -1771,6 +2060,13 @@ +@@ -1771,6 +2064,13 @@ } public void setSwimming(boolean flag) { @@ -542,7 +546,7 @@ this.setFlag(4, flag); } -@@ -1831,16 +2127,56 @@ +@@ -1831,16 +2131,56 @@ } public void setAirTicks(int i) { @@ -577,8 +581,9 @@ + this.setOnFire(entityCombustEvent.getDuration(), false); + } + // CraftBukkit end -+ } -+ + } + +- this.damageEntity(DamageSource.LIGHTNING, 5.0F); + // CraftBukkit start + if (thisBukkitEntity instanceof Hanging) { + HangingBreakByEntityEvent hangingEvent = new HangingBreakByEntityEvent((Hanging) thisBukkitEntity, stormBukkitEntity); @@ -587,9 +592,8 @@ + if (hangingEvent.isCancelled()) { + return; + } - } - -- this.damageEntity(DamageSource.LIGHTNING, 5.0F); ++ } ++ + if (this.isFireProof()) { + return; + } @@ -602,7 +606,7 @@ } public void j(boolean flag) { -@@ -1988,20 +2324,33 @@ +@@ -1988,20 +2328,33 @@ @Nullable public Entity a(DimensionManager dimensionmanager) { @@ -639,7 +643,7 @@ if (dimensionmanager1 == DimensionManager.THE_END && dimensionmanager == DimensionManager.OVERWORLD) { blockposition = worldserver1.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING_NO_LEAVES, worldserver1.getSpawn()); } else if (dimensionmanager == DimensionManager.THE_END) { -@@ -2039,6 +2388,25 @@ +@@ -2039,6 +2392,25 @@ vec3d = shapedetector_c.b; f = (float) shapedetector_c.c; } @@ -665,7 +669,7 @@ this.world.getMethodProfiler().exitEnter("reloading"); Entity entity = this.getEntityType().a((World) worldserver1); -@@ -2048,6 +2416,14 @@ +@@ -2048,6 +2420,14 @@ entity.setPositionRotation(blockposition, entity.yaw + f, entity.pitch); entity.setMot(vec3d); worldserver1.addEntityTeleport(entity); @@ -680,7 +684,7 @@ } this.dead = true; -@@ -2239,7 +2615,26 @@ +@@ -2239,7 +2619,26 @@ } public void a(AxisAlignedBB axisalignedbb) { diff --git a/nms-patches/PlayerChunk.patch b/nms-patches/PlayerChunk.patch index 27a5ae70b5..e34a359bb2 100644 --- a/nms-patches/PlayerChunk.patch +++ b/nms-patches/PlayerChunk.patch @@ -1,5 +1,14 @@ --- a/net/minecraft/server/PlayerChunk.java +++ b/net/minecraft/server/PlayerChunk.java +@@ -23,7 +23,7 @@ + private volatile CompletableFuture> tickingFuture; + private volatile CompletableFuture> entityTickingFuture; + private CompletableFuture chunkSave; +- private int oldTicketLevel; ++ public int oldTicketLevel; // CraftBukkit - public + private int ticketLevel; + private int n; + private final ChunkCoordIntPair location; @@ -43,7 +43,7 @@ this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; this.tickingFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; @@ -9,7 +18,31 @@ this.dirtyBlocks = new short[64]; this.location = chunkcoordintpair; this.lightEngine = lightengine; -@@ -76,9 +76,9 @@ +@@ -55,6 +55,14 @@ + this.a(i); + } + ++ // CraftBukkit start ++ public Chunk getFullChunk() { ++ CompletableFuture> statusFuture = this.getStatusFuture(ChunkStatus.FULL); ++ Either either = (Either) statusFuture.getNow(null); ++ return either == null ? null : (Chunk) either.left().orElse(null); ++ } ++ // CraftBukkit end ++ + public CompletableFuture> getStatusFutureUnchecked(ChunkStatus chunkstatus) { + CompletableFuture> completablefuture = (CompletableFuture) this.statusFutures.get(chunkstatus.c()); + +@@ -62,7 +70,7 @@ + } + + public CompletableFuture> getStatusFuture(ChunkStatus chunkstatus) { +- return b(this.ticketLevel).b(chunkstatus) ? this.getStatusFutureUnchecked(chunkstatus) : PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE; ++ return b(this.oldTicketLevel).b(chunkstatus) ? this.getStatusFutureUnchecked(chunkstatus) : PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE; // CraftBukkit using oldTicketLevel for isLoaded checks + } + + public CompletableFuture> a() { +@@ -76,9 +84,9 @@ @Nullable public Chunk getChunk() { CompletableFuture> completablefuture = this.a(); @@ -21,7 +54,7 @@ } public CompletableFuture getChunkSave() { -@@ -201,7 +201,7 @@ +@@ -201,7 +209,7 @@ CompletableFuture> completablefuture = (CompletableFuture) this.statusFutures.get(i); if (completablefuture != null) { @@ -30,23 +63,29 @@ if (either == null || either.left().isPresent()) { return completablefuture; -@@ -213,6 +213,15 @@ - - this.a(completablefuture1); - this.statusFutures.set(i, completablefuture1); -+ // CraftBukkit start -+ if (chunkstatus == ChunkStatus.FULL) { -+ completablefuture1.thenAccept((either) -> { -+ Chunk chunk = (Chunk) either.left().get(); -+ -+ chunk.loadCallback(); +@@ -256,6 +264,21 @@ + boolean flag1 = this.ticketLevel <= PlayerChunkMap.GOLDEN_TICKET; + PlayerChunk.State playerchunk_state = c(this.oldTicketLevel); + PlayerChunk.State playerchunk_state1 = c(this.ticketLevel); ++ // CraftBukkit start ++ // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. ++ if (playerchunk_state.a(PlayerChunk.State.BORDER) && !playerchunk_state1.a(PlayerChunk.State.BORDER)) { // PAIL oldChunkState, newChunkState, isAtLeast ++ this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> { ++ either.ifLeft((chunkAccess) -> { ++ Chunk chunk = (Chunk) chunkAccess; ++ // 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. ++ chunk.setNeedsSaving(true); ++ chunk.unloadCallback(); + }); -+ } -+ // CraftBukkit end - return completablefuture1; - } else { - return completablefuture == null ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : completablefuture; -@@ -294,7 +303,7 @@ ++ }); ++ } ++ // CraftBukkit end + + if (flag1) { + for (int i = flag ? chunkstatus.c() + 1 : 0; i <= chunkstatus1.c(); ++i) { +@@ -294,7 +317,7 @@ if (flag2 && !flag3) { completablefuture = this.fullChunkFuture; this.fullChunkFuture = PlayerChunk.UNLOADED_CHUNK_FUTURE; @@ -55,3 +94,21 @@ playerchunkmap.getClass(); return either1.ifLeft(playerchunkmap::a); })); +@@ -332,6 +355,17 @@ + + this.w.a(this.location, this::j, this.ticketLevel, this::d); + 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 (!playerchunk_state.a(PlayerChunk.State.BORDER) && playerchunk_state1.a(PlayerChunk.State.BORDER)) { // PAIL oldChunkState, newChunkState, isAtLeast ++ this.getStatusFutureUnchecked(ChunkStatus.FULL).thenAccept((either) -> { ++ either.ifLeft((chunkAccess) -> { ++ Chunk chunk = (Chunk) chunkAccess; ++ chunk.loadCallback(); ++ }); ++ }); ++ } ++ // CraftBukkit end + } + + public static ChunkStatus b(int i) { diff --git a/nms-patches/PlayerChunkMap.patch b/nms-patches/PlayerChunkMap.patch index 753e103c6e..0700775c92 100644 --- a/nms-patches/PlayerChunkMap.patch +++ b/nms-patches/PlayerChunkMap.patch @@ -1,17 +1,14 @@ --- a/net/minecraft/server/PlayerChunkMap.java +++ b/net/minecraft/server/PlayerChunkMap.java -@@ -35,6 +35,10 @@ +@@ -35,6 +35,7 @@ import org.apache.commons.lang3.mutable.MutableBoolean; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -+// CraftBukkit start -+import org.bukkit.entity.Player; -+import org.bukkit.event.world.ChunkUnloadEvent; -+// CraftBukkit end ++import org.bukkit.entity.Player; // CraftBukkit public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { -@@ -181,9 +185,12 @@ +@@ -181,9 +182,12 @@ return completablefuture1.thenApply((list1) -> { List list2 = Lists.newArrayList(); @@ -26,7 +23,7 @@ final Either either = (Either) iterator.next(); Optional optional = either.left(); -@@ -279,7 +286,7 @@ +@@ -279,7 +283,7 @@ PlayerChunkMap.LOGGER.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", this.x.getName()); } else { this.visibleChunks.values().stream().filter(PlayerChunk::hasBeenLoaded).forEach((playerchunk) -> { @@ -35,7 +32,7 @@ if (ichunkaccess instanceof ProtoChunkExtension || ichunkaccess instanceof Chunk) { this.saveChunk(ichunkaccess); -@@ -290,7 +297,6 @@ +@@ -290,7 +294,6 @@ } } @@ -43,27 +40,7 @@ protected void unloadChunks(BooleanSupplier booleansupplier) { GameProfilerFiller gameprofilerfiller = this.world.getMethodProfiler(); -@@ -330,9 +336,19 @@ - if (this.loadedChunks.remove(i) && ichunkaccess instanceof Chunk) { - Chunk chunk = (Chunk) ichunkaccess; - -+ // CraftBukkit start -+ ChunkUnloadEvent event = new ChunkUnloadEvent(chunk.bukkitChunk, chunk.isNeedsSaving()); -+ this.world.getServer().getPluginManager().callEvent(event); -+ this.saveChunk(ichunkaccess, event.isSaveChunk()); -+ // CraftBukkit end -+ - chunk.setLoaded(false); - this.world.unloadChunk(chunk); -+ // CraftBukkit start -+ } else { -+ this.saveChunk(ichunkaccess); - } -+ // CraftBukkit end - - this.lightEngine.a(ichunkaccess.getPos()); - this.lightEngine.queueUpdate(); -@@ -416,7 +432,7 @@ +@@ -416,7 +419,7 @@ return CompletableFuture.completedFuture(Either.right(playerchunk_failure)); }); }, (runnable) -> { @@ -72,7 +49,7 @@ }); } } -@@ -498,7 +514,7 @@ +@@ -498,7 +501,7 @@ long i = playerchunk.h().pair(); playerchunk.getClass(); @@ -81,7 +58,7 @@ }); } -@@ -515,7 +531,7 @@ +@@ -515,7 +518,7 @@ return Either.left(chunk); }); }, (runnable) -> { @@ -90,7 +67,7 @@ }); completablefuture1.thenAcceptAsync((either) -> { -@@ -529,7 +545,7 @@ +@@ -529,7 +532,7 @@ return Either.left(chunk); }); }, (runnable) -> { @@ -99,7 +76,7 @@ }); return completablefuture1; } -@@ -543,7 +559,7 @@ +@@ -543,7 +546,7 @@ return chunk; }); }, (runnable) -> { @@ -108,23 +85,7 @@ }); } -@@ -552,8 +568,14 @@ - } - - public boolean saveChunk(IChunkAccess ichunkaccess) { -+ // CraftBukkit start -+ return this.saveChunk(ichunkaccess, ichunkaccess.isNeedsSaving()); -+ } -+ -+ public boolean saveChunk(IChunkAccess ichunkaccess, boolean save) { -+ // CraftBukkit end - this.n.a(ichunkaccess.getPos()); -- if (!ichunkaccess.isNeedsSaving()) { -+ if (!save) { // CraftBukkit - return false; - } else { - try { -@@ -607,9 +629,10 @@ +@@ -607,9 +610,10 @@ ChunkCoordIntPair chunkcoordintpair = playerchunk.h(); Packet[] apacket = new Packet[2]; @@ -136,7 +97,7 @@ boolean flag1 = i1 <= this.viewDistance; this.sendChunk(entityplayer, chunkcoordintpair, apacket, flag, flag1); -@@ -664,7 +687,7 @@ +@@ -664,7 +668,7 @@ private NBTTagCompound readChunkData(ChunkCoordIntPair chunkcoordintpair) throws IOException { NBTTagCompound nbttagcompound = this.read(chunkcoordintpair); @@ -145,7 +106,7 @@ } boolean d(ChunkCoordIntPair chunkcoordintpair) { -@@ -984,7 +1007,7 @@ +@@ -984,7 +988,7 @@ public final Set trackedPlayers = Sets.newHashSet(); public EntityTracker(Entity entity, int i, int j, boolean flag) { @@ -154,7 +115,7 @@ this.tracker = entity; this.trackingDistance = i; this.e = SectionPosition.a(entity); -@@ -1053,6 +1076,17 @@ +@@ -1053,6 +1057,17 @@ } } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java index 404f51e140..f4dfbf534d 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java @@ -93,6 +93,9 @@ public class CraftChunk implements Chunk { @Override public Entity[] getEntities() { + if (!isLoaded()) { + getWorld().getChunkAt(x, z); // Transient load for this tick + } int count = 0, index = 0; net.minecraft.server.Chunk chunk = getHandle(); @@ -118,6 +121,9 @@ public class CraftChunk implements Chunk { @Override public BlockState[] getTileEntities() { + if (!isLoaded()) { + getWorld().getChunkAt(x, z); // Transient load for this tick + } int index = 0; net.minecraft.server.Chunk chunk = getHandle(); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 950fe8b99f..8b95301c3e 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -330,7 +330,7 @@ public class CraftWorld implements World { @Override public boolean isChunkLoaded(int x, int z) { net.minecraft.server.Chunk chunk = world.getChunkProvider().getChunkAt(x, z, false); - return chunk != null && chunk.loaded; + return chunk != null; } @Override @@ -345,8 +345,7 @@ public class CraftWorld implements World { @Override public Chunk[] getLoadedChunks() { Long2ObjectLinkedOpenHashMap chunks = world.getChunkProvider().playerChunkMap.visibleChunks; - - return chunks.values().stream().map(PlayerChunk::getChunk).filter(Objects::nonNull).filter((chunk) -> chunk.loaded).map(net.minecraft.server.Chunk::getBukkitChunk).toArray(Chunk[]::new); + return chunks.values().stream().map(PlayerChunk::getFullChunk).filter(Objects::nonNull).map(net.minecraft.server.Chunk::getBukkitChunk).toArray(Chunk[]::new); } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java index ba50e98462..be8b862d9a 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -530,7 +530,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { @Override public boolean isValid() { - return entity.isAlive() && entity.valid; + return entity.isAlive() && entity.valid && entity.isChunkLoaded(); } @Override