Fix non-player entity freezing on chunk unload in 1.14+ (fixes #203)

This commit is contained in:
Jonas Herzig
2020-07-13 15:54:42 +02:00
parent dc31605b29
commit f1885ece41

View File

@@ -23,6 +23,7 @@ import net.minecraft.client.network.OtherClientPlayerEntity;
import net.minecraft.client.gui.screen.DownloadingTerrainScreen;
import net.minecraft.client.gui.screen.NoticeScreen;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.NetworkState;
import net.minecraft.network.Packet;
@@ -52,8 +53,7 @@ import net.minecraft.network.packet.s2c.play.PlayerSpawnS2CPacket;
import net.minecraft.network.packet.s2c.play.SignEditorOpenS2CPacket;
import net.minecraft.network.packet.s2c.play.StatisticsS2CPacket;
import net.minecraft.text.Text;
import net.minecraft.world.World;
import net.minecraft.world.chunk.ChunkProvider;
import net.minecraft.util.math.MathHelper;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
@@ -77,16 +77,16 @@ import net.minecraft.network.packet.s2c.play.OpenContainerS2CPacket;
import net.minecraft.network.packet.s2c.play.OpenWrittenBookS2CPacket;
import net.minecraft.entity.EntityType;
import net.minecraft.text.TranslatableText;
import net.minecraft.world.chunk.ChunkManager;
import net.minecraft.world.chunk.WorldChunk;
import net.minecraft.world.chunk.light.LightingProvider;
//#else
//$$ import net.minecraft.client.resources.I18n;
//$$ import net.minecraft.entity.Entity;
//$$ import net.minecraft.util.math.MathHelper;
//$$ import net.minecraft.world.EnumDifficulty;
//$$ import net.minecraft.world.World;
//$$ import net.minecraft.world.WorldType;
//$$ import net.minecraft.world.chunk.Chunk;
//$$ import java.util.ArrayList;
//$$ import java.util.Collection;
//$$ import net.minecraft.world.chunk.IChunkProvider;
//$$ import java.util.Iterator;
//#endif
@@ -125,7 +125,9 @@ import net.minecraft.network.NetworkSide;
//#endif
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -1150,81 +1152,105 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend
// Note: This is only half of the truth. Entities may be removed by chunk-unloading, see else-case below.
// To make things worse, it seems like players were never supposed to be unloaded this way because
// they will remain glitched in the World#playerEntities list.
World world = mc.world;
ChunkProvider chunkProvider = world.getChunkManager();
// 1.14+: The update issue remains but only for non-players and the unloading list bug appears to have been
// fixed (chunk unloading no longer removes the entities).
// Get the chunk that will be unloaded
//#if MC>=11400
// FIXME fabric
//#else
//#if MC>=11400
//$$ Chunk chunk = chunkProvider.provideChunk(x, z, false, false);
ClientWorld world = mc.world;
ChunkManager chunkProvider = world.getChunkManager();
WorldChunk chunk = chunkProvider.getWorldChunk(x, z
//#if MC<11500
//$$ , false
//#endif
);
if (chunk != null) {
//#else
//$$ World world = mc.world;
//$$ IChunkProvider chunkProvider = world.getChunkProvider();
//$$ Chunk chunk = chunkProvider.provideChunk(x, z);
//#endif
//$$ if (!chunk.isEmpty()) {
//$$ List<Entity> entitiesInChunk = new ArrayList<>();
//$$ // Gather all entities in that chunk
//$$ for (Collection<Entity> entityList : getEntityLists(chunk)) {
//$$ entitiesInChunk.addAll(entityList);
//$$ }
//$$ for (Entity entity : entitiesInChunk) {
//$$ // Skip interpolation of position updates coming from server
//$$ // (See: newX in EntityLivingBase or otherPlayerMPX in EntityOtherPlayerMP)
//$$ // Needs to be called at least 4 times thanks to
//$$ // EntityOtherPlayerMP#otherPlayerMPPosRotationIncrements (max vanilla value is 3)
//$$ for (int i = 0; i < 4; i++) {
//#endif
List<Entity> entitiesInChunk = new ArrayList<>();
// Gather all entities in that chunk
for (Collection<Entity> entityList : getEntityLists(chunk)) {
entitiesInChunk.addAll(entityList);
}
for (Entity entity : entitiesInChunk) {
// Skip interpolation of position updates coming from server
// (See: newX in EntityLivingBase or otherPlayerMPX in EntityOtherPlayerMP)
// Needs to be called at least 4 times thanks to
// EntityOtherPlayerMP#otherPlayerMPPosRotationIncrements (max vanilla value is 3)
for (int i = 0; i < 4; i++) {
//#if MC>=11400
//$$ entity.tick();
entity.tick();
//#else
//$$ entity.onUpdate();
//#endif
//$$ }
//$$
//$$ // Check whether the entity has left the chunk
//$$ int chunkX = MathHelper.floor(entity.posX / 16);
//$$ int chunkZ = MathHelper.floor(entity.posZ / 16);
//$$ if (entity.chunkCoordX != chunkX || entity.chunkCoordZ != chunkZ) {
//$$ // Entity has left the chunk
//$$ chunk.removeEntityAtIndex(entity, entity.chunkCoordY);
//#if MC>=11400
//$$ Chunk newChunk = chunkProvider.provideChunk(chunkX, chunkZ, false, false);
//#else
}
// Check whether the entity has left the chunk
//#if MC>=11404
int chunkX = MathHelper.floor(Entity_getX(entity) / 16);
int chunkY = MathHelper.floor(Entity_getY(entity) / 16);
int chunkZ = MathHelper.floor(Entity_getZ(entity) / 16);
if (entity.chunkX != chunkX || entity.chunkY != chunkY || entity.chunkZ != chunkZ) {
if (entity.updateNeeded) {
// Entity has left the chunk
chunk.remove(entity, entity.chunkY);
}
WorldChunk newChunk = chunkProvider.getWorldChunk(chunkX, chunkZ
//#if MC<11500
//$$ , false
//#endif
);
if (newChunk != null) {
newChunk.addEntity(entity);
} else {
// Entity has left all loaded chunks
entity.updateNeeded = false;
}
}
//#else
//$$ int chunkX = MathHelper.floor(entity.posX / 16);
//$$ int chunkZ = MathHelper.floor(entity.posZ / 16);
//$$ if (entity.chunkCoordX != chunkX || entity.chunkCoordZ != chunkZ) {
//$$ // Entity has left the chunk
//$$ chunk.removeEntityAtIndex(entity, entity.chunkCoordY);
//#if MC>=10904
//$$ Chunk newChunk = chunkProvider.getLoadedChunk(chunkX, chunkZ);
//#else
//$$ Chunk newChunk = chunkProvider.chunkExists(chunkX, chunkZ)
//$$ ? chunkProvider.provideChunk(chunkX, chunkZ) : null;
//#endif
//#endif
//$$ if (newChunk != null) {
//$$ newChunk.addEntity(entity);
//$$ } else {
//$$ // Entity has left all loaded chunks
//$$ entity.addedToChunk = false;
//$$ }
//$$ } else {
//$$ // When entities remain in a chunk that's to be unloaded, they'll only be added to a unload
//$$ // queue and remain loaded as before until the next tick (which during jumping is way off).
//$$ // So, if they are re-spawned with the same entity id, MC actually cleans up the old entity and
//$$ // then adds the new one but leaves the unload queue as is.
//$$ // Finally, on the next tick the legitimate entity will be unloaded because it's part of the
//$$ // unload queue (entities .equals based purely on their id). However, the old entity object
//$$ // is used to determine the chunk the entity is removed from and in this case that'll allow the
//$$ // legitimate entity to remain registered in a loaded chunk, causing them to still be rendered.
//$$ //
//$$ // The usual removal-due-to-chunk-unload process will, without touching the entityList, call
//$$ // onEntityRemoved. In that method WorldClient checks to see whether the entity is still in the
//$$ // entityList (which it is) and then adds it to the entitySpawnQueue.
//$$ // As the final result the entity will remain loaded.
//$$ // To get the same result without ticking, we just remove the entity from the to-be-unloaded
//$$ // chunk but keep it loaded otherwise. They won't be rendered because they're not part of any
//$$ // chunk and will be removed properly if the server decides to re-spawn the entity.
//$$ chunk.removeEntityAtIndex(entity, entity.chunkCoordY);
//$$ entity.addedToChunk = false;
//$$ }
//$$ }
//$$ }
//#endif
//$$ if (newChunk != null) {
//$$ newChunk.addEntity(entity);
//$$ } else {
//$$ // Entity has left all loaded chunks
//$$ entity.addedToChunk = false;
//$$ }
//$$ } else {
//$$ // When entities remain in a chunk that's to be unloaded, they'll only be added to a unload
//$$ // queue and remain loaded as before until the next tick (which during jumping is way off).
//$$ // So, if they are re-spawned with the same entity id, MC actually cleans up the old entity and
//$$ // then adds the new one but leaves the unload queue as is.
//$$ // Finally, on the next tick the legitimate entity will be unloaded because it's part of the
//$$ // unload queue (entities .equals based purely on their id). However, the old entity object
//$$ // is used to determine the chunk the entity is removed from and in this case that'll allow the
//$$ // legitimate entity to remain registered in a loaded chunk, causing them to still be rendered.
//$$ //
//$$ // The usual removal-due-to-chunk-unload process will, without touching the entityList, call
//$$ // onEntityRemoved. In that method WorldClient checks to see whether the entity is still in the
//$$ // entityList (which it is) and then adds it to the entitySpawnQueue.
//$$ // As the final result the entity will remain loaded.
//$$ // To get the same result without ticking, we just remove the entity from the to-be-unloaded
//$$ // chunk but keep it loaded otherwise. They won't be rendered because they're not part of any
//$$ // chunk and will be removed properly if the server decides to re-spawn the entity.
//$$ chunk.removeEntityAtIndex(entity, entity.chunkCoordY);
//$$ entity.addedToChunk = false;
//$$ }
//#endif
}
}
}
return p; // During synchronous playback everything is sent normally
}