Commits

DerFrZocker authored and md_5 committed 1eeba6a0ec9
SPIGOT-6891: Crash when importing 1.16 chunks with entities above the world, when a BlockPopulator is active
No tags

src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java

Modified
31 31
32 32 public class CraftLimitedRegion extends CraftRegionAccessor implements LimitedRegion {
33 33
34 34 private final WeakReference<GeneratorAccessSeed> weakAccess;
35 35 private final int centerChunkX;
36 36 private final int centerChunkZ;
37 37 // Buffer is one chunk (16 blocks), can be seen in ChunkStatus#q
38 38 // there the order is {..., FEATURES, LIQUID_CARVERS, STRUCTURE_STARTS, ...}
39 39 private final int buffer = 16;
40 40 private final BoundingBox region;
41 + boolean entitiesLoaded = false;
41 42 // Minecraft saves the entities as NBTTagCompound during chunk generation. This causes that
42 43 // changes made to the returned bukkit entity are not saved. To combat this we keep them and
43 44 // save them when the population is finished.
44 45 private final List<net.minecraft.world.entity.Entity> entities = new ArrayList<>();
46 + // SPIGOT-6891: Save outside Entities extra, since they are not part of the region.
47 + // Prevents crash for chunks which are converting from 1.17 to 1.18
48 + private final List<net.minecraft.world.entity.Entity> outsideEntities = new ArrayList<>();
45 49
46 50 public CraftLimitedRegion(GeneratorAccessSeed access, ChunkCoordIntPair center) {
47 51 this.weakAccess = new WeakReference<>(access);
48 52 centerChunkX = center.x;
49 53 centerChunkZ = center.z;
50 54
51 - // load entities which are already present
52 - for (int x = -(buffer >> 4); x <= (buffer >> 4); x++) {
53 - for (int z = -(buffer >> 4); z <= (buffer >> 4); z++) {
54 - ProtoChunk chunk = (ProtoChunk) access.getChunk(centerChunkX + x, centerChunkZ + z);
55 - for (NBTTagCompound compound : chunk.getEntities()) {
56 - EntityTypes.loadEntityRecursive(compound, access.getMinecraftWorld(), (entity) -> {
57 - entity.generation = true;
58 - entities.add(entity);
59 - return entity;
60 - });
61 - }
62 - }
63 - }
64 -
65 55 World world = access.getMinecraftWorld().getWorld();
66 56 int xCenter = centerChunkX << 4;
67 57 int zCenter = centerChunkZ << 4;
68 58 int xMin = xCenter - getBuffer();
69 59 int zMin = zCenter - getBuffer();
70 60 int xMax = xCenter + getBuffer() + 16;
71 61 int zMax = zCenter + getBuffer() + 16;
72 62
73 63 this.region = new BoundingBox(xMin, world.getMinHeight(), zMin, xMax, world.getMaxHeight(), zMax);
74 64 }
75 65
76 66 public GeneratorAccessSeed getHandle() {
77 67 GeneratorAccessSeed handle = weakAccess.get();
78 68
79 69 if (handle == null) {
80 70 throw new IllegalStateException("GeneratorAccessSeed no longer present, are you using it in a different tick?");
81 71 }
82 72
83 73 return handle;
84 74 }
85 75
86 - public void saveEntities() {
76 + public void loadEntities() {
77 + if (entitiesLoaded) {
78 + return;
79 + }
80 +
87 81 GeneratorAccessSeed access = getHandle();
82 + // load entities which are already present
88 83 for (int x = -(buffer >> 4); x <= (buffer >> 4); x++) {
89 84 for (int z = -(buffer >> 4); z <= (buffer >> 4); z++) {
90 85 ProtoChunk chunk = (ProtoChunk) access.getChunk(centerChunkX + x, centerChunkZ + z);
91 - chunk.getEntities().clear();
86 + for (NBTTagCompound compound : chunk.getEntities()) {
87 + EntityTypes.loadEntityRecursive(compound, access.getMinecraftWorld(), (entity) -> {
88 + if (region.contains(entity.getX(), entity.getY(), entity.getZ())) {
89 + entity.generation = true;
90 + entities.add(entity);
91 + } else {
92 + outsideEntities.add(entity);
93 + }
94 + return entity;
95 + });
96 + }
97 + }
98 + }
99 +
100 + entitiesLoaded = true;
101 + }
102 +
103 + public void saveEntities() {
104 + GeneratorAccessSeed access = getHandle();
105 + // We don't clear existing entities when they are not loaded and therefore not modified
106 + if (entitiesLoaded) {
107 + for (int x = -(buffer >> 4); x <= (buffer >> 4); x++) {
108 + for (int z = -(buffer >> 4); z <= (buffer >> 4); z++) {
109 + ProtoChunk chunk = (ProtoChunk) access.getChunk(centerChunkX + x, centerChunkZ + z);
110 + chunk.getEntities().clear();
111 + }
92 112 }
93 113 }
94 114
95 115 for (net.minecraft.world.entity.Entity entity : entities) {
96 116 if (entity.isAlive()) {
97 117 // check if entity is still in region or if it got teleported outside it
98 118 Preconditions.checkState(region.contains(entity.getX(), entity.getY(), entity.getZ()), "Entity %s is not in the region", entity);
99 119 access.addFreshEntity(entity);
100 120 }
101 121 }
122 +
123 + for (net.minecraft.world.entity.Entity entity : outsideEntities) {
124 + access.addFreshEntity(entity);
125 + }
102 126 }
103 127
104 128 public void breakLink() {
105 129 weakAccess.clear();
106 130 }
107 131
108 132 @Override
109 133 public int getBuffer() {
110 134 return buffer;
111 135 }
180 204 }
181 205
182 206 @Override
183 207 public boolean generateTree(Location location, Random random, TreeType treeType, Consumer<BlockState> consumer) {
184 208 Preconditions.checkArgument(isInRegion(location), "Coordinates %s, %s, %s are not in the region", location.getBlockX(), location.getBlockY(), location.getBlockZ());
185 209 return super.generateTree(location, random, treeType, consumer);
186 210 }
187 211
188 212 @Override
189 213 public Collection<net.minecraft.world.entity.Entity> getNMSEntities() {
214 + // Only load entities if we need them
215 + loadEntities();
190 216 return new ArrayList<>(entities);
191 217 }
192 218
193 219 @Override
194 220 public <T extends Entity> T spawn(Location location, Class<T> clazz, Consumer<T> function, CreatureSpawnEvent.SpawnReason reason) throws IllegalArgumentException {
195 221 Preconditions.checkArgument(isInRegion(location), "Coordinates %s, %s, %s are not in the region", location.getBlockX(), location.getBlockY(), location.getBlockZ());
196 222 return super.spawn(location, clazz, function, reason);
197 223 }
198 224
199 225 @Override

Everything looks good. We'll let you know here if there's anything you should know about.

Add shortcut