Move patches to unapplied

This commit is contained in:
Nassim Jahnke
2024-12-12 12:22:12 +01:00
parent ff75689b08
commit 45ddf764d9
821 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
--- a/net/minecraft/world/level/chunk/ChunkAccess.java
+++ b/net/minecraft/world/level/chunk/ChunkAccess.java
@@ -65,7 +65,7 @@
protected final ShortList[] postProcessing;
private volatile boolean unsaved;
private volatile boolean isLightCorrect;
- protected final ChunkPos chunkPos;
+ protected final ChunkPos chunkPos; public final long coordinateKey; public final int locX; public final int locZ; // Paper - cache coordinate key
private long inhabitedTime;
/** @deprecated */
@Nullable
@@ -85,8 +85,14 @@
protected final LevelHeightAccessor levelHeightAccessor;
protected final LevelChunkSection[] sections;
+ // CraftBukkit start - SPIGOT-6814: move to IChunkAccess to account for 1.17 to 1.18 chunk upgrading.
+ private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry();
+ public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(ChunkAccess.DATA_TYPE_REGISTRY);
+ // CraftBukkit end
+
public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry<Biome> biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sectionArray, @Nullable BlendingData blendingData) {
- this.chunkPos = pos;
+ this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups
+ this.chunkPos = pos; this.coordinateKey = ChunkPos.asLong(locX, locZ); // Paper - cache long key
this.upgradeData = upgradeData;
this.levelHeightAccessor = heightLimitView;
this.sections = new LevelChunkSection[heightLimitView.getSectionsCount()];
@@ -103,7 +109,11 @@
}
ChunkAccess.replaceMissingSections(biomeRegistry, this.sections);
+ // CraftBukkit start
+ this.biomeRegistry = biomeRegistry;
}
+ public final Registry<Biome> biomeRegistry;
+ // CraftBukkit end
private static void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sectionArray) {
for (int i = 0; i < sectionArray.length; ++i) {
@@ -275,6 +285,7 @@
public boolean tryMarkSaved() {
if (this.unsaved) {
this.unsaved = false;
+ this.persistentDataContainer.dirty(false); // CraftBukkit - SPIGOT-6814: chunk was saved, pdc is no longer dirty
return true;
} else {
return false;
@@ -282,7 +293,7 @@
}
public boolean isUnsaved() {
- return this.unsaved;
+ return this.unsaved || this.persistentDataContainer.dirty(); // CraftBukkit - SPIGOT-6814: chunk is unsaved if pdc was mutated
}
public abstract ChunkStatus getPersistedStatus();
@@ -458,10 +469,31 @@
crashreportsystemdetails.setDetail("Location", () -> {
return CrashReportCategory.formatLocation(this, biomeX, biomeY, biomeZ);
+ });
+ throw new ReportedException(crashreport);
+ }
+ }
+
+ // CraftBukkit start
+ public void setBiome(int i, int j, int k, Holder<Biome> biome) {
+ try {
+ int l = QuartPos.fromBlock(this.getMinY());
+ int i1 = l + QuartPos.fromBlock(this.getHeight()) - 1;
+ int j1 = Mth.clamp(j, l, i1);
+ int k1 = this.getSectionIndex(QuartPos.toBlock(j1));
+
+ this.sections[k1].setBiome(i & 3, j1 & 3, k & 3, biome);
+ } catch (Throwable throwable) {
+ CrashReport crashreport = CrashReport.forThrowable(throwable, "Setting biome");
+ CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Biome being set");
+
+ crashreportsystemdetails.setDetail("Location", () -> {
+ return CrashReportCategory.formatLocation(this, i, j, k);
});
throw new ReportedException(crashreport);
}
}
+ // CraftBukkit end
public void fillBiomesFromNoise(BiomeResolver biomeSupplier, Climate.Sampler sampler) {
ChunkPos chunkcoordintpair = this.getPos();

View File

@@ -0,0 +1,224 @@
--- a/net/minecraft/world/level/chunk/ChunkGenerator.java
+++ b/net/minecraft/world/level/chunk/ChunkGenerator.java
@@ -108,8 +108,8 @@
protected abstract MapCodec<? extends ChunkGenerator> codec();
- public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> structureSetRegistry, RandomState noiseConfig, long seed) {
- return ChunkGeneratorStructureState.createForNormal(noiseConfig, seed, this.biomeSource, structureSetRegistry);
+ public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> holderlookup, RandomState randomstate, long i, org.spigotmc.SpigotWorldConfig conf) { // Spigot
+ return ChunkGeneratorStructureState.createForNormal(randomstate, i, this.biomeSource, holderlookup, conf); // Spigot
}
public Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> getTypeNameForDataFixer() {
@@ -127,6 +127,24 @@
@Nullable
public Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel world, HolderSet<Structure> structures, BlockPos center, int radius, boolean skipReferencedStructures) {
+ // Paper start - StructuresLocateEvent
+ final org.bukkit.World bukkitWorld = world.getWorld();
+ final org.bukkit.Location origin = io.papermc.paper.util.MCUtil.toLocation(world, center);
+ final List<org.bukkit.generator.structure.Structure> apiStructures = structures.stream().map(Holder::value).map(nms -> org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(nms)).toList();
+ if (!apiStructures.isEmpty()) {
+ final io.papermc.paper.event.world.StructuresLocateEvent event = new io.papermc.paper.event.world.StructuresLocateEvent(bukkitWorld, origin, apiStructures, radius, skipReferencedStructures);
+ if (!event.callEvent()) {
+ return null;
+ }
+ if (event.getResult() != null) {
+ return Pair.of(io.papermc.paper.util.MCUtil.toBlockPos(event.getResult().pos()), world.registryAccess().lookupOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(event.getResult().structure())));
+ }
+ center = io.papermc.paper.util.MCUtil.toBlockPosition(event.getOrigin());
+ radius = event.getRadius();
+ skipReferencedStructures = event.shouldFindUnexplored();
+ structures = HolderSet.direct(api -> world.registryAccess().lookupOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(api)), event.getStructures());
+ }
+ // Paper end
ChunkGeneratorStructureState chunkgeneratorstructurestate = world.getChunkSource().getGeneratorState();
Map<StructurePlacement, Set<Holder<Structure>>> map = new Object2ObjectArrayMap();
Iterator iterator = structures.iterator();
@@ -223,6 +241,7 @@
while (iterator.hasNext()) {
ChunkPos chunkcoordintpair = (ChunkPos) iterator.next();
+ if (!world.paperConfig().environment.locateStructuresOutsideWorldBorder && !world.getWorldBorder().isChunkInBounds(chunkcoordintpair.x, chunkcoordintpair.z)) { continue; } // Paper - Bound treasure maps to world border
blockposition_mutableblockposition.set(SectionPos.sectionToBlockCoord(chunkcoordintpair.x, 8), 32, SectionPos.sectionToBlockCoord(chunkcoordintpair.z, 8));
double d1 = blockposition_mutableblockposition.distSqr(center);
@@ -247,12 +266,15 @@
int i1 = placement.spacing();
for (int j1 = -radius; j1 <= radius; ++j1) {
- boolean flag1 = j1 == -radius || j1 == radius;
+ // Paper start - Perf: iterate over border chunks instead of entire square chunk area
+ boolean flag1 = j1 == -radius || j1 == radius; final boolean onBorderAlongZAxis = flag1; // Paper - OBFHELPER
- for (int k1 = -radius; k1 <= radius; ++k1) {
- boolean flag2 = k1 == -radius || k1 == radius;
+ for (int k1 = -radius; k1 <= radius; k1 += onBorderAlongZAxis ? 1 : radius * 2) {
+ // boolean flag2 = k1 == -radius || k1 == radius;
- if (flag1 || flag2) {
+ // if (flag1 || flag2) {
+ if (true) {
+ // Paper end - Perf: iterate over border chunks instead of entire square chunk area
int l1 = centerChunkX + i1 * j1;
int i2 = centerChunkZ + i1 * k1;
ChunkPos chunkcoordintpair = placement.getPotentialStructureChunk(seed, l1, i2);
@@ -312,29 +334,29 @@
}
}
- public void applyBiomeDecoration(WorldGenLevel world, ChunkAccess chunk, StructureManager structureAccessor) {
- ChunkPos chunkcoordintpair = chunk.getPos();
+ public void addVanillaDecorations(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { // CraftBukkit
+ ChunkPos chunkcoordintpair = ichunkaccess.getPos();
if (!SharedConstants.debugVoidTerrain(chunkcoordintpair)) {
- SectionPos sectionposition = SectionPos.of(chunkcoordintpair, world.getMinSectionY());
+ SectionPos sectionposition = SectionPos.of(chunkcoordintpair, generatoraccessseed.getMinSectionY());
BlockPos blockposition = sectionposition.origin();
- Registry<Structure> iregistry = world.registryAccess().lookupOrThrow(Registries.STRUCTURE);
+ Registry<Structure> iregistry = generatoraccessseed.registryAccess().lookupOrThrow(Registries.STRUCTURE);
Map<Integer, List<Structure>> map = (Map) iregistry.stream().collect(Collectors.groupingBy((structure) -> {
return structure.step().ordinal();
}));
List<FeatureSorter.StepFeatureData> list = (List) this.featuresPerStep.get();
WorldgenRandom seededrandom = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
- long i = seededrandom.setDecorationSeed(world.getSeed(), blockposition.getX(), blockposition.getZ());
+ long i = seededrandom.setDecorationSeed(generatoraccessseed.getSeed(), blockposition.getX(), blockposition.getZ());
Set<Holder<Biome>> set = new ObjectArraySet();
ChunkPos.rangeClosed(sectionposition.chunk(), 1).forEach((chunkcoordintpair1) -> {
- ChunkAccess ichunkaccess1 = world.getChunk(chunkcoordintpair1.x, chunkcoordintpair1.z);
+ ChunkAccess ichunkaccess1 = generatoraccessseed.getChunk(chunkcoordintpair1.x, chunkcoordintpair1.z);
LevelChunkSection[] achunksection = ichunkaccess1.getSections();
int j = achunksection.length;
for (int k = 0; k < j; ++k) {
LevelChunkSection chunksection = achunksection[k];
- PalettedContainerRO palettedcontainerro = chunksection.getBiomes();
+ PalettedContainerRO<Holder<Biome>> palettedcontainerro = chunksection.getBiomes(); // CraftBukkit - decompile error
Objects.requireNonNull(set);
palettedcontainerro.getAll(set::add);
@@ -345,7 +367,7 @@
int j = list.size();
try {
- Registry<PlacedFeature> iregistry1 = world.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE);
+ Registry<PlacedFeature> iregistry1 = generatoraccessseed.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE);
int k = Math.max(GenerationStep.Decoration.values().length, j);
for (int l = 0; l < k; ++l) {
@@ -353,7 +375,7 @@
Iterator iterator;
CrashReportCategory crashreportsystemdetails;
- if (structureAccessor.shouldGenerateStructures()) {
+ if (structuremanager.shouldGenerateStructures()) {
List<Structure> list1 = (List) map.getOrDefault(l, Collections.emptyList());
for (iterator = list1.iterator(); iterator.hasNext(); ++i1) {
@@ -368,9 +390,9 @@
};
try {
- world.setCurrentlyGenerating(supplier);
- structureAccessor.startsForStructure(sectionposition, structure).forEach((structurestart) -> {
- structurestart.placeInChunk(world, structureAccessor, this, seededrandom, ChunkGenerator.getWritableArea(chunk), chunkcoordintpair);
+ generatoraccessseed.setCurrentlyGenerating(supplier);
+ structuremanager.startsForStructure(sectionposition, structure).forEach((structurestart) -> {
+ structurestart.placeInChunk(generatoraccessseed, structuremanager, this, seededrandom, ChunkGenerator.getWritableArea(ichunkaccess), chunkcoordintpair);
});
} catch (Exception exception) {
CrashReport crashreport = CrashReport.forThrowable(exception, "Feature placement");
@@ -418,11 +440,18 @@
return (String) optional.orElseGet(placedfeature::toString);
};
- seededrandom.setFeatureSeed(i, l1, l);
+ // Paper start - Configurable feature seeds; change populationSeed used in random
+ long featurePopulationSeed = i;
+ final long configFeatureSeed = generatoraccessseed.getMinecraftWorld().paperConfig().featureSeeds.features.getLong(placedfeature.feature());
+ if (configFeatureSeed != -1) {
+ featurePopulationSeed = seededrandom.setDecorationSeed(configFeatureSeed, blockposition.getX(), blockposition.getZ()); // See seededrandom.setDecorationSeed from above
+ }
+ seededrandom.setFeatureSeed(featurePopulationSeed, l1, l);
+ // Paper end - Configurable feature seeds
try {
- world.setCurrentlyGenerating(supplier1);
- placedfeature.placeWithBiomeCheck(world, this, seededrandom, blockposition);
+ generatoraccessseed.setCurrentlyGenerating(supplier1);
+ placedfeature.placeWithBiomeCheck(generatoraccessseed, this, seededrandom, blockposition);
} catch (Exception exception1) {
CrashReport crashreport1 = CrashReport.forThrowable(exception1, "Feature placement");
@@ -435,15 +464,42 @@
}
}
- world.setCurrentlyGenerating((Supplier) null);
+ generatoraccessseed.setCurrentlyGenerating((Supplier) null);
} catch (Exception exception2) {
CrashReport crashreport2 = CrashReport.forThrowable(exception2, "Biome decoration");
crashreport2.addCategory("Generation").setDetail("CenterX", (Object) chunkcoordintpair.x).setDetail("CenterZ", (Object) chunkcoordintpair.z).setDetail("Decoration Seed", (Object) i);
throw new ReportedException(crashreport2);
+ }
+ }
+ }
+
+ // CraftBukkit start
+ public void applyBiomeDecoration(WorldGenLevel world, ChunkAccess chunk, StructureManager structureAccessor) {
+ this.applyBiomeDecoration(world, chunk, structureAccessor, true);
+ }
+
+ public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
+ if (vanilla) {
+ this.addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
+ }
+
+ org.bukkit.World world = generatoraccessseed.getMinecraftWorld().getWorld();
+ // only call when a populator is present (prevents unnecessary entity conversion)
+ if (!world.getPopulators().isEmpty()) {
+ org.bukkit.craftbukkit.generator.CraftLimitedRegion limitedRegion = new org.bukkit.craftbukkit.generator.CraftLimitedRegion(generatoraccessseed, ichunkaccess.getPos());
+ int x = ichunkaccess.getPos().x;
+ int z = ichunkaccess.getPos().z;
+ for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) {
+ WorldgenRandom seededrandom = new WorldgenRandom(new net.minecraft.world.level.levelgen.LegacyRandomSource(generatoraccessseed.getSeed()));
+ seededrandom.setDecorationSeed(generatoraccessseed.getSeed(), x, z);
+ populator.populate(world, new org.bukkit.craftbukkit.util.RandomSourceWrapper.RandomWrapper(seededrandom), x, z, limitedRegion);
}
+ limitedRegion.saveEntities();
+ limitedRegion.breakLink();
}
}
+ // CraftBukkit end
private static BoundingBox getWritableArea(ChunkAccess chunk) {
ChunkPos chunkcoordintpair = chunk.getPos();
@@ -521,7 +577,7 @@
}
}
- if (structureplacement.isStructureChunk(placementCalculator, chunkcoordintpair.x, chunkcoordintpair.z)) {
+ if (structureplacement.isStructureChunk(placementCalculator, chunkcoordintpair.x, chunkcoordintpair.z, structureplacement instanceof net.minecraft.world.level.chunk.ChunkGeneratorStructureState.KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs
if (list.size() == 1) {
this.tryGenerateStructure((StructureSet.StructureSelectionEntry) list.get(0), structureAccessor, registryManager, randomstate, structureTemplateManager, placementCalculator.getLevelSeed(), chunk, chunkcoordintpair, sectionposition, dimension);
} else {
@@ -582,6 +638,14 @@
StructureStart structurestart = structure.generate(weightedEntry.structure(), dimension, dynamicRegistryManager, this, this.biomeSource, noiseConfig, structureManager, seed, pos, j, chunk, predicate);
if (structurestart.isValid()) {
+ // CraftBukkit start
+ BoundingBox box = structurestart.getBoundingBox();
+ org.bukkit.event.world.AsyncStructureSpawnEvent event = new org.bukkit.event.world.AsyncStructureSpawnEvent(structureAccessor.level.getMinecraftWorld().getWorld(), org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(structure), new org.bukkit.util.BoundingBox(box.minX(), box.minY(), box.minZ(), box.maxX(), box.maxY(), box.maxZ()), pos.x, pos.z);
+ org.bukkit.Bukkit.getPluginManager().callEvent(event);
+ if (event.isCancelled()) {
+ return true;
+ }
+ // CraftBukkit end
structureAccessor.setStartForStructure(sectionPos, structure, structurestart, chunk);
return true;
} else {

View File

@@ -0,0 +1,175 @@
--- a/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java
+++ b/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java
@@ -1,3 +1,4 @@
+// mc-dev import
package net.minecraft.world.level.chunk;
import com.google.common.base.Stopwatch;
@@ -33,6 +34,11 @@
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
import org.slf4j.Logger;
+// Spigot start
+import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
+import org.spigotmc.SpigotWorldConfig;
+// Spigot end
+
public class ChunkGeneratorStructureState {
private static final Logger LOGGER = LogUtils.getLogger();
@@ -44,22 +50,109 @@
private final Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> ringPositions = new Object2ObjectArrayMap();
private boolean hasGeneratedPositions;
private final List<Holder<StructureSet>> possibleStructureSets;
+ public final SpigotWorldConfig conf; // Paper - Add missing structure set seed configs
- public static ChunkGeneratorStructureState createForFlat(RandomState noiseConfig, long seed, BiomeSource biomeSource, Stream<Holder<StructureSet>> structureSets) {
- List<Holder<StructureSet>> list = structureSets.filter((holder) -> {
- return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder.value(), biomeSource);
+ public static ChunkGeneratorStructureState createForFlat(RandomState randomstate, long i, BiomeSource worldchunkmanager, Stream<Holder<StructureSet>> stream, SpigotWorldConfig conf) { // Spigot
+ List<Holder<StructureSet>> list = stream.filter((holder) -> {
+ return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder.value(), worldchunkmanager);
}).toList();
- return new ChunkGeneratorStructureState(noiseConfig, biomeSource, seed, 0L, list);
+ return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, 0L, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot
}
- public static ChunkGeneratorStructureState createForNormal(RandomState noiseConfig, long seed, BiomeSource biomeSource, HolderLookup<StructureSet> structureSetRegistry) {
- List<Holder<StructureSet>> list = (List) structureSetRegistry.listElements().filter((holder_c) -> {
- return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder_c.value(), biomeSource);
+ public static ChunkGeneratorStructureState createForNormal(RandomState randomstate, long i, BiomeSource worldchunkmanager, HolderLookup<StructureSet> holderlookup, SpigotWorldConfig conf) { // Spigot
+ List<Holder<StructureSet>> list = (List) holderlookup.listElements().filter((holder_c) -> {
+ return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder_c.value(), worldchunkmanager);
}).collect(Collectors.toUnmodifiableList());
- return new ChunkGeneratorStructureState(noiseConfig, biomeSource, seed, seed, list);
+ return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, i, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot
+ }
+ // Paper start - Add missing structure set seed configs; horrible hack because spigot creates a ton of direct Holders which lose track of the identifying key
+ public static final class KeyedRandomSpreadStructurePlacement extends RandomSpreadStructurePlacement {
+ public final net.minecraft.resources.ResourceKey<StructureSet> key;
+ public KeyedRandomSpreadStructurePlacement(net.minecraft.resources.ResourceKey<StructureSet> key, net.minecraft.core.Vec3i locateOffset, FrequencyReductionMethod frequencyReductionMethod, float frequency, int salt, java.util.Optional<StructurePlacement.ExclusionZone> exclusionZone, int spacing, int separation, net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType spreadType) {
+ super(locateOffset, frequencyReductionMethod, frequency, salt, exclusionZone, spacing, separation, spreadType);
+ this.key = key;
+ }
+ }
+ // Paper end - Add missing structure set seed configs
+
+ // Spigot start
+ private static List<Holder<StructureSet>> injectSpigot(List<Holder<StructureSet>> list, SpigotWorldConfig conf) {
+ return list.stream().map((holder) -> {
+ StructureSet structureset = holder.value();
+ final Holder<StructureSet> newHolder; // Paper - Add missing structure set seed configs
+ if (structureset.placement() instanceof RandomSpreadStructurePlacement randomConfig && holder.unwrapKey().orElseThrow().location().getNamespace().equals(net.minecraft.resources.ResourceLocation.DEFAULT_NAMESPACE)) { // Paper - Add missing structure set seed configs; check namespace cause datapacks could add structure sets with the same path
+ String name = holder.unwrapKey().orElseThrow().location().getPath();
+ int seed = randomConfig.salt;
+
+ switch (name) {
+ case "desert_pyramids":
+ seed = conf.desertSeed;
+ break;
+ case "end_cities":
+ seed = conf.endCitySeed;
+ break;
+ case "nether_complexes":
+ seed = conf.netherSeed;
+ break;
+ case "igloos":
+ seed = conf.iglooSeed;
+ break;
+ case "jungle_temples":
+ seed = conf.jungleSeed;
+ break;
+ case "woodland_mansions":
+ seed = conf.mansionSeed;
+ break;
+ case "ocean_monuments":
+ seed = conf.monumentSeed;
+ break;
+ case "nether_fossils":
+ seed = conf.fossilSeed;
+ break;
+ case "ocean_ruins":
+ seed = conf.oceanSeed;
+ break;
+ case "pillager_outposts":
+ seed = conf.outpostSeed;
+ break;
+ case "ruined_portals":
+ seed = conf.portalSeed;
+ break;
+ case "shipwrecks":
+ seed = conf.shipwreckSeed;
+ break;
+ case "swamp_huts":
+ seed = conf.swampSeed;
+ break;
+ case "villages":
+ seed = conf.villageSeed;
+ break;
+ // Paper start - Add missing structure set seed configs
+ case "ancient_cities":
+ seed = conf.ancientCitySeed;
+ break;
+ case "trail_ruins":
+ seed = conf.trailRuinsSeed;
+ break;
+ case "trial_chambers":
+ seed = conf.trialChambersSeed;
+ break;
+ // Paper end - Add missing structure set seed configs
+ }
+
+ // Paper start - Add missing structure set seed configs
+ structureset = new StructureSet(structureset.structures(), new KeyedRandomSpreadStructurePlacement(holder.unwrapKey().orElseThrow(), randomConfig.locateOffset, randomConfig.frequencyReductionMethod, randomConfig.frequency, seed, randomConfig.exclusionZone, randomConfig.spacing(), randomConfig.separation(), randomConfig.spreadType()));
+ newHolder = Holder.direct(structureset); // I really wish we didn't have to do this here
+ } else {
+ newHolder = holder;
+ }
+ return newHolder;
+ // Paper end - Add missing structure set seed configs
+ }).collect(Collectors.toUnmodifiableList());
}
+ // Spigot end
private static boolean hasBiomesForStructureSet(StructureSet structureSet, BiomeSource biomeSource) {
Stream<Holder<Biome>> stream = structureSet.structures().stream().flatMap((structureset_a) -> {
@@ -73,12 +166,13 @@
return stream.anyMatch(set::contains);
}
- private ChunkGeneratorStructureState(RandomState noiseConfig, BiomeSource biomeSource, long structureSeed, long concentricRingSeed, List<Holder<StructureSet>> structureSets) {
+ private ChunkGeneratorStructureState(RandomState noiseConfig, BiomeSource biomeSource, long structureSeed, long concentricRingSeed, List<Holder<StructureSet>> structureSets, SpigotWorldConfig conf) { // Paper - Add missing structure set seed configs
this.randomState = noiseConfig;
this.levelSeed = structureSeed;
this.biomeSource = biomeSource;
this.concentricRingsSeed = concentricRingSeed;
this.possibleStructureSets = structureSets;
+ this.conf = conf; // Paper - Add missing structure set seed configs
}
public List<Holder<StructureSet>> possibleStructureSets() {
@@ -132,7 +226,13 @@
HolderSet<Biome> holderset = placement.preferredBiomes();
RandomSource randomsource = RandomSource.create();
+ // Paper start - Add missing structure set seed configs
+ if (this.conf.strongholdSeed != null && structureSetEntry.is(net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS)) {
+ randomsource.setSeed(this.conf.strongholdSeed);
+ } else {
+ // Paper end - Add missing structure set seed configs
randomsource.setSeed(this.concentricRingsSeed);
+ } // Paper - Add missing structure set seed configs
double d0 = randomsource.nextDouble() * Math.PI * 2.0D;
int l = 0;
int i1 = 0;
@@ -209,7 +309,7 @@
for (int l = centerChunkX - chunkCount; l <= centerChunkX + chunkCount; ++l) {
for (int i1 = centerChunkZ - chunkCount; i1 <= centerChunkZ + chunkCount; ++i1) {
- if (structureplacement.isStructureChunk(this, l, i1)) {
+ if (structureplacement.isStructureChunk(this, l, i1, structureplacement instanceof KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs
return true;
}
}

View File

@@ -0,0 +1,7 @@
--- a/net/minecraft/world/level/chunk/DataLayer.java
+++ b/net/minecraft/world/level/chunk/DataLayer.java
@@ -1,3 +1,4 @@
+// mc-dev import
package net.minecraft.world.level.chunk;
import java.util.Arrays;

View File

@@ -0,0 +1,15 @@
--- a/net/minecraft/world/level/chunk/EmptyLevelChunk.java
+++ b/net/minecraft/world/level/chunk/EmptyLevelChunk.java
@@ -25,6 +25,12 @@
public BlockState getBlockState(BlockPos pos) {
return Blocks.VOID_AIR.defaultBlockState();
}
+ // Paper start
+ @Override
+ public BlockState getBlockState(final int x, final int y, final int z) {
+ return Blocks.VOID_AIR.defaultBlockState();
+ }
+ // Paper end
@Nullable
@Override

View File

@@ -0,0 +1,30 @@
--- a/net/minecraft/world/level/chunk/HashMapPalette.java
+++ b/net/minecraft/world/level/chunk/HashMapPalette.java
@@ -20,7 +20,7 @@
}
public HashMapPalette(IdMap<T> idList, int indexBits, PaletteResize<T> listener) {
- this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create(1 << indexBits));
+ this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create((1 << indexBits) + 1)); // Paper - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap
}
private HashMapPalette(IdMap<T> idList, int indexBits, PaletteResize<T> listener, CrudeIncrementalIntIdentityHashBiMap<T> map) {
@@ -38,10 +38,16 @@
public int idFor(T object) {
int i = this.values.getId(object);
if (i == -1) {
- i = this.values.add(object);
- if (i >= 1 << this.bits) {
+ // Paper start - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize
+ // We use size() instead of the result from add(K)
+ // This avoids adding another object unnecessarily
+ // Without this change, + 2 would be required in the constructor
+ if (this.values.size() >= 1 << this.bits) {
i = this.resizeHandler.onResize(this.bits + 1, object);
+ } else {
+ i = this.values.add(object);
}
+ // Paper end - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize
}
return i;

View File

@@ -0,0 +1,409 @@
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -79,7 +79,7 @@
};
private final Map<BlockPos, LevelChunk.RebindableTickingBlockEntityWrapper> tickersInLevel;
public boolean loaded;
- public final Level level;
+ public final ServerLevel level; // CraftBukkit - type
@Nullable
private Supplier<FullChunkStatus> fullStatus;
@Nullable
@@ -98,7 +98,7 @@
this.tickersInLevel = Maps.newHashMap();
this.unsavedListener = (chunkcoordintpair1) -> {
};
- this.level = world;
+ this.level = (ServerLevel) world; // CraftBukkit - type
this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap();
Heightmap.Types[] aheightmap_type = Heightmap.Types.values();
int j = aheightmap_type.length;
@@ -116,6 +116,15 @@
this.fluidTicks = fluidTickScheduler;
}
+ // CraftBukkit start
+ public boolean mustNotSave;
+ public boolean needsDecoration;
+ // CraftBukkit end
+
+ // Paper start
+ boolean loadedTicketLevel;
+ // Paper end
+
public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) {
this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData());
if (!Collections.disjoint(protoChunk.pendingBlockEntities.keySet(), protoChunk.blockEntities.keySet())) {
@@ -151,6 +160,10 @@
this.skyLightSources = protoChunk.skyLightSources;
this.setLightCorrect(protoChunk.isLightCorrect());
this.markUnsaved();
+ this.needsDecoration = true; // CraftBukkit
+ // CraftBukkit start
+ this.persistentDataContainer = protoChunk.persistentDataContainer; // SPIGOT-6814: copy PDC to account for 1.17 to 1.18 chunk upgrading.
+ // CraftBukkit end
}
public void setUnsavedListener(LevelChunk.UnsavedListener unsavedListener) {
@@ -187,7 +200,14 @@
return new ChunkAccess.PackedTicks(this.blockTicks.pack(time), this.fluidTicks.pack(time));
}
+ // Paper start
@Override
+ public long getInhabitedTime() {
+ return this.level.paperConfig().chunks.fixedChunkInhabitedTime < 0 ? super.getInhabitedTime() : this.level.paperConfig().chunks.fixedChunkInhabitedTime;
+ }
+ // Paper end
+
+ @Override
public GameEventListenerRegistry getListenerRegistry(int ySectionCoord) {
Level world = this.level;
@@ -200,8 +220,25 @@
}
}
+ // Paper start - Perf: Reduce instructions and provide final method
+ public BlockState getBlockState(final int x, final int y, final int z) {
+ return this.getBlockStateFinal(x, y, z);
+ }
+ public BlockState getBlockStateFinal(final int x, final int y, final int z) {
+ // Copied and modified from below
+ final int sectionIndex = this.getSectionIndex(y);
+ if (sectionIndex < 0 || sectionIndex >= this.sections.length
+ || this.sections[sectionIndex].nonEmptyBlockCount == 0) {
+ return Blocks.AIR.defaultBlockState();
+ }
+ return this.sections[sectionIndex].states.get((y & 15) << 8 | (z & 15) << 4 | x & 15);
+ }
@Override
public BlockState getBlockState(BlockPos pos) {
+ if (true) {
+ return this.getBlockStateFinal(pos.getX(), pos.getY(), pos.getZ());
+ }
+ // Paper end - Perf: Reduce instructions and provide final method
int i = pos.getX();
int j = pos.getY();
int k = pos.getZ();
@@ -243,24 +280,38 @@
}
}
+ // Paper start - If loaded util
@Override
+ public final FluidState getFluidIfLoaded(BlockPos blockposition) {
+ return this.getFluidState(blockposition);
+ }
+
+ @Override
+ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
+ return this.getBlockState(blockposition);
+ }
+ // Paper end
+
+ @Override
public FluidState getFluidState(BlockPos pos) {
return this.getFluidState(pos.getX(), pos.getY(), pos.getZ());
}
public FluidState getFluidState(int x, int y, int z) {
- try {
- int l = this.getSectionIndex(y);
+ // Paper start - Perf: Optimise Chunk#getFluid
+ // try { // Remove try catch
+ int index = this.getSectionIndex(y);
+ if (index >= 0 && index < this.sections.length) {
+ LevelChunkSection chunksection = this.sections[index];
- if (l >= 0 && l < this.sections.length) {
- LevelChunkSection chunksection = this.sections[l];
-
if (!chunksection.hasOnlyAir()) {
- return chunksection.getFluidState(x & 15, y & 15, z & 15);
+ return chunksection.states.get((y & 15) << 8 | (z & 15) << 4 | x & 15).getFluidState();
+ // Paper end - Perf: Optimise Chunk#getFluid
}
}
return Fluids.EMPTY.defaultFluidState();
+ /* // Paper - Perf: Optimise Chunk#getFluid
} catch (Throwable throwable) {
CrashReport crashreport = CrashReport.forThrowable(throwable, "Getting fluid state");
CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being got");
@@ -270,80 +321,89 @@
});
throw new ReportedException(crashreport);
}
+ */ // Paper - Perf: Optimise Chunk#getFluid
}
+ // CraftBukkit start
@Nullable
@Override
public BlockState setBlockState(BlockPos pos, BlockState state, boolean moved) {
- int i = pos.getY();
+ return this.setBlockState(pos, state, moved, true);
+ }
+
+ @Nullable
+ public BlockState setBlockState(BlockPos blockposition, BlockState iblockdata, boolean flag, boolean doPlace) {
+ // CraftBukkit end
+ int i = blockposition.getY();
LevelChunkSection chunksection = this.getSection(this.getSectionIndex(i));
boolean flag1 = chunksection.hasOnlyAir();
- if (flag1 && state.isAir()) {
+ if (flag1 && iblockdata.isAir()) {
return null;
} else {
- int j = pos.getX() & 15;
+ int j = blockposition.getX() & 15;
int k = i & 15;
- int l = pos.getZ() & 15;
- BlockState iblockdata1 = chunksection.setBlockState(j, k, l, state);
+ int l = blockposition.getZ() & 15;
+ BlockState iblockdata1 = chunksection.setBlockState(j, k, l, iblockdata);
- if (iblockdata1 == state) {
+ if (iblockdata1 == iblockdata) {
return null;
} else {
- Block block = state.getBlock();
+ Block block = iblockdata.getBlock();
- ((Heightmap) this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING)).update(j, i, l, state);
- ((Heightmap) this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES)).update(j, i, l, state);
- ((Heightmap) this.heightmaps.get(Heightmap.Types.OCEAN_FLOOR)).update(j, i, l, state);
- ((Heightmap) this.heightmaps.get(Heightmap.Types.WORLD_SURFACE)).update(j, i, l, state);
+ ((Heightmap) this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING)).update(j, i, l, iblockdata);
+ ((Heightmap) this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES)).update(j, i, l, iblockdata);
+ ((Heightmap) this.heightmaps.get(Heightmap.Types.OCEAN_FLOOR)).update(j, i, l, iblockdata);
+ ((Heightmap) this.heightmaps.get(Heightmap.Types.WORLD_SURFACE)).update(j, i, l, iblockdata);
boolean flag2 = chunksection.hasOnlyAir();
if (flag1 != flag2) {
- this.level.getChunkSource().getLightEngine().updateSectionStatus(pos, flag2);
+ this.level.getChunkSource().getLightEngine().updateSectionStatus(blockposition, flag2);
this.level.getChunkSource().onSectionEmptinessChanged(this.chunkPos.x, SectionPos.blockToSectionCoord(i), this.chunkPos.z, flag2);
}
- if (LightEngine.hasDifferentLightProperties(iblockdata1, state)) {
+ if (LightEngine.hasDifferentLightProperties(iblockdata1, iblockdata)) {
ProfilerFiller gameprofilerfiller = Profiler.get();
gameprofilerfiller.push("updateSkyLightSources");
this.skyLightSources.update(this, j, i, l);
gameprofilerfiller.popPush("queueCheckLight");
- this.level.getChunkSource().getLightEngine().checkBlock(pos);
+ this.level.getChunkSource().getLightEngine().checkBlock(blockposition);
gameprofilerfiller.pop();
}
boolean flag3 = iblockdata1.hasBlockEntity();
- if (!this.level.isClientSide) {
- iblockdata1.onRemove(this.level, pos, state, moved);
+ if (!this.level.isClientSide && !this.level.isBlockPlaceCancelled) { // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent
+ iblockdata1.onRemove(this.level, blockposition, iblockdata, flag);
} else if (!iblockdata1.is(block) && flag3) {
- this.removeBlockEntity(pos);
+ this.removeBlockEntity(blockposition);
}
if (!chunksection.getBlockState(j, k, l).is(block)) {
return null;
} else {
- if (!this.level.isClientSide) {
- state.onPlace(this.level, pos, iblockdata1, moved);
+ // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled.
+ if (!this.level.isClientSide && doPlace && (!this.level.captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) {
+ iblockdata.onPlace(this.level, blockposition, iblockdata1, flag);
}
- if (state.hasBlockEntity()) {
- BlockEntity tileentity = this.getBlockEntity(pos, LevelChunk.EntityCreationType.CHECK);
+ if (iblockdata.hasBlockEntity()) {
+ BlockEntity tileentity = this.getBlockEntity(blockposition, LevelChunk.EntityCreationType.CHECK);
- if (tileentity != null && !tileentity.isValidBlockState(state)) {
- LevelChunk.LOGGER.warn("Found mismatched block entity @ {}: type = {}, state = {}", new Object[]{pos, tileentity.getType().builtInRegistryHolder().key().location(), state});
- this.removeBlockEntity(pos);
+ if (tileentity != null && !tileentity.isValidBlockState(iblockdata)) {
+ LevelChunk.LOGGER.warn("Found mismatched block entity @ {}: type = {}, state = {}", new Object[]{blockposition, tileentity.getType().builtInRegistryHolder().key().location(), iblockdata});
+ this.removeBlockEntity(blockposition);
tileentity = null;
}
if (tileentity == null) {
- tileentity = ((EntityBlock) block).newBlockEntity(pos, state);
+ tileentity = ((EntityBlock) block).newBlockEntity(blockposition, iblockdata);
if (tileentity != null) {
this.addAndRegisterBlockEntity(tileentity);
}
} else {
- tileentity.setBlockState(state);
+ tileentity.setBlockState(iblockdata);
this.updateBlockEntityTicker(tileentity);
}
}
@@ -375,7 +435,12 @@
@Nullable
public BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType) {
- BlockEntity tileentity = (BlockEntity) this.blockEntities.get(pos);
+ // CraftBukkit start
+ BlockEntity tileentity = this.level.capturedTileEntities.get(pos);
+ if (tileentity == null) {
+ tileentity = (BlockEntity) this.blockEntities.get(pos);
+ }
+ // CraftBukkit end
if (tileentity == null) {
CompoundTag nbttagcompound = (CompoundTag) this.pendingBlockEntities.remove(pos);
@@ -446,7 +511,13 @@
BlockState iblockdata = this.getBlockState(blockposition);
if (!iblockdata.hasBlockEntity()) {
- LevelChunk.LOGGER.warn("Trying to set block entity {} at position {}, but state {} does not allow it", new Object[]{blockEntity, blockposition, iblockdata});
+ // Paper start - ServerExceptionEvent
+ com.destroystokyo.paper.exception.ServerInternalException e = new com.destroystokyo.paper.exception.ServerInternalException(
+ "Trying to set block entity %s at position %s, but state %s does not allow it".formatted(blockEntity, blockposition, iblockdata)
+ );
+ e.printStackTrace();
+ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(e);
+ // Paper end - ServerExceptionEvent
} else {
BlockState iblockdata1 = blockEntity.getBlockState();
@@ -500,6 +571,12 @@
if (this.isInLevel()) {
BlockEntity tileentity = (BlockEntity) this.blockEntities.remove(pos);
+ // CraftBukkit start - SPIGOT-5561: Also remove from pending map
+ if (!this.pendingBlockEntities.isEmpty()) {
+ this.pendingBlockEntities.remove(pos);
+ }
+ // CraftBukkit end
+
if (tileentity != null) {
Level world = this.level;
@@ -553,6 +630,65 @@
}
+ // CraftBukkit start
+ public void loadCallback() {
+ // Paper start
+ this.loadedTicketLevel = true;
+ // Paper end
+ org.bukkit.Server server = this.level.getCraftServer();
+ this.level.getChunkSource().addLoadedChunk(this); // Paper
+ if (server != null) {
+ /*
+ * If it's a new world, the first few chunks are generated inside
+ * the World constructor. We can't reliably alter that, so we have
+ * no way of creating a CraftWorld/CraftServer at that point.
+ */
+ org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
+ server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration));
+
+ if (this.needsDecoration) {
+ this.needsDecoration = false;
+ java.util.Random random = new java.util.Random();
+ random.setSeed(this.level.getSeed());
+ long xRand = random.nextLong() / 2L * 2L + 1L;
+ long zRand = random.nextLong() / 2L * 2L + 1L;
+ random.setSeed((long) this.chunkPos.x * xRand + (long) this.chunkPos.z * zRand ^ this.level.getSeed());
+
+ org.bukkit.World world = this.level.getWorld();
+ if (world != null) {
+ this.level.populating = true;
+ try {
+ for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) {
+ populator.populate(world, random, bukkitChunk);
+ }
+ } finally {
+ this.level.populating = false;
+ }
+ }
+ server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk));
+ }
+ }
+ }
+
+ public void unloadCallback() {
+ org.bukkit.Server server = this.level.getCraftServer();
+ org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
+ org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, this.isUnsaved());
+ server.getPluginManager().callEvent(unloadEvent);
+ // note: saving can be prevented, but not forced if no saving is actually required
+ this.mustNotSave = !unloadEvent.isSaveChunk();
+ this.level.getChunkSource().removeLoadedChunk(this); // Paper
+ // Paper start
+ this.loadedTicketLevel = false;
+ // Paper end
+ }
+
+ @Override
+ public boolean isUnsaved() {
+ return super.isUnsaved() && !this.mustNotSave;
+ }
+ // CraftBukkit end
+
public boolean isEmpty() {
return false;
}
@@ -750,7 +886,7 @@
private <T extends BlockEntity> void updateBlockEntityTicker(T blockEntity) {
BlockState iblockdata = blockEntity.getBlockState();
- BlockEntityTicker<T> blockentityticker = iblockdata.getTicker(this.level, blockEntity.getType());
+ BlockEntityTicker<T> blockentityticker = iblockdata.getTicker(this.level, (BlockEntityType<T>) blockEntity.getType()); // CraftBukkit - decompile error
if (blockentityticker == null) {
this.removeBlockEntityTicker(blockEntity.getBlockPos());
@@ -841,7 +977,7 @@
private boolean loggedInvalidBlockState;
BoundTickingBlockEntity(final BlockEntity tileentity, final BlockEntityTicker blockentityticker) {
- this.blockEntity = tileentity;
+ this.blockEntity = (T) tileentity; // CraftBukkit - decompile error
this.ticker = blockentityticker;
}
@@ -860,18 +996,25 @@
if (this.blockEntity.getType().isValid(iblockdata)) {
this.ticker.tick(LevelChunk.this.level, this.blockEntity.getBlockPos(), iblockdata, this.blockEntity);
this.loggedInvalidBlockState = false;
- } else if (!this.loggedInvalidBlockState) {
- this.loggedInvalidBlockState = true;
- LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), iblockdata});
+ // Paper start - Remove the Block Entity if it's invalid
+ } else {
+ LevelChunk.this.removeBlockEntity(this.getPos());
+ if (!this.loggedInvalidBlockState) {
+ this.loggedInvalidBlockState = true;
+ LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), iblockdata});
+ }
+ // Paper end - Remove the Block Entity if it's invalid
}
gameprofilerfiller.pop();
} catch (Throwable throwable) {
- CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking block entity");
- CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block entity being ticked");
-
- this.blockEntity.fillCrashReportCategory(crashreportsystemdetails);
- throw new ReportedException(crashreport);
+ // Paper start - Prevent block entity and entity crashes
+ final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ());
+ net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable);
+ net.minecraft.world.level.chunk.LevelChunk.this.level.getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); // Paper - ServerExceptionEvent
+ LevelChunk.this.removeBlockEntity(this.getPos());
+ // Paper end - Prevent block entity and entity crashes
+ // Spigot start
}
}
}

View File

@@ -0,0 +1,51 @@
--- a/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -19,11 +19,11 @@
public static final int SECTION_HEIGHT = 16;
public static final int SECTION_SIZE = 4096;
public static final int BIOME_CONTAINER_BITS = 2;
- private short nonEmptyBlockCount;
+ short nonEmptyBlockCount; // Paper - package private
private short tickingBlockCount;
private short tickingFluidCount;
public final PalettedContainer<BlockState> states;
- private PalettedContainerRO<Holder<Biome>> biomes;
+ private PalettedContainer<Holder<Biome>> biomes; // CraftBukkit - read/write
private LevelChunkSection(LevelChunkSection section) {
this.nonEmptyBlockCount = section.nonEmptyBlockCount;
@@ -33,9 +33,9 @@
this.biomes = section.biomes.copy();
}
- public LevelChunkSection(PalettedContainer<BlockState> blockStateContainer, PalettedContainerRO<Holder<Biome>> biomeContainer) {
- this.states = blockStateContainer;
- this.biomes = biomeContainer;
+ public LevelChunkSection(PalettedContainer<BlockState> datapaletteblock, PalettedContainer<Holder<Biome>> palettedcontainerro) { // CraftBukkit - read/write
+ this.states = datapaletteblock;
+ this.biomes = palettedcontainerro;
this.recalcBlockCounts();
}
@@ -49,7 +49,7 @@
}
public FluidState getFluidState(int x, int y, int z) {
- return ((BlockState) this.states.get(x, y, z)).getFluidState();
+ return this.states.get(x, y, z).getFluidState(); // Paper - Perf: Optimise Chunk#getFluid; diff on change - we expect this to be effectively just getType(x, y, z).getFluid(). If this changes we need to check other patches that use IBlockData#getFluid.
}
public void acquire() {
@@ -196,6 +196,12 @@
return (Holder) this.biomes.get(x, y, z);
}
+ // CraftBukkit start
+ public void setBiome(int i, int j, int k, Holder<Biome> biome) {
+ this.biomes.set(i, j, k, biome);
+ }
+ // CraftBukkit end
+
public void fillBiomesFromNoise(BiomeResolver biomeSupplier, Climate.Sampler sampler, int x, int y, int z) {
PalettedContainer<Holder<Biome>> datapaletteblock = this.biomes.recreate();
boolean flag = true;

View File

@@ -0,0 +1,74 @@
--- a/net/minecraft/world/level/chunk/PalettedContainer.java
+++ b/net/minecraft/world/level/chunk/PalettedContainer.java
@@ -30,14 +30,14 @@
public final IdMap<T> registry;
private volatile PalettedContainer.Data<T> data;
private final PalettedContainer.Strategy strategy;
- private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer");
+ // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused
public void acquire() {
- this.threadingDetector.checkAndLock();
+ // this.threadingDetector.checkAndLock(); // Paper - disable this - use proper synchronization
}
public void release() {
- this.threadingDetector.checkAndUnlock();
+ // this.threadingDetector.checkAndUnlock(); // Paper - disable this
}
public static <T> Codec<PalettedContainer<T>> codecRW(IdMap<T> idList, Codec<T> entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) {
@@ -110,7 +110,7 @@
}
@Override
- public int onResize(int newBits, T object) {
+ public synchronized int onResize(int newBits, T object) { // Paper - synchronize
PalettedContainer.Data<T> data = this.data;
PalettedContainer.Data<T> data2 = this.createOrReuseData(data, newBits);
data2.copyFrom(data.palette, data.storage);
@@ -135,7 +135,7 @@
return this.getAndSet(this.strategy.getIndex(x, y, z), value);
}
- private T getAndSet(int index, T value) {
+ private synchronized T getAndSet(int index, T value) { // Paper - synchronize
int i = this.data.palette.idFor(value);
int j = this.data.storage.getAndSet(index, i);
return this.data.palette.valueFor(j);
@@ -151,7 +151,7 @@
}
}
- private void set(int index, T value) {
+ private synchronized void set(int index, T value) { // Paper - synchronize
int i = this.data.palette.idFor(value);
this.data.storage.set(index, i);
}
@@ -174,7 +174,7 @@
intSet.forEach(id -> action.accept(palette.valueFor(id)));
}
- public void read(FriendlyByteBuf buf) {
+ public synchronized void read(FriendlyByteBuf buf) { // Paper - synchronize
this.acquire();
try {
@@ -189,7 +189,7 @@
}
@Override
- public void write(FriendlyByteBuf buf) {
+ public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize
this.acquire();
try {
@@ -237,7 +237,7 @@
}
@Override
- public PalettedContainerRO.PackedData<T> pack(IdMap<T> idList, PalettedContainer.Strategy paletteProvider) {
+ public synchronized PalettedContainerRO.PackedData<T> pack(IdMap<T> idList, PalettedContainer.Strategy paletteProvider) { // Paper - synchronize
this.acquire();
PalettedContainerRO.PackedData var12;

View File

@@ -0,0 +1,22 @@
--- a/net/minecraft/world/level/chunk/ProtoChunk.java
+++ b/net/minecraft/world/level/chunk/ProtoChunk.java
@@ -81,7 +81,19 @@
@Override
public ChunkAccess.PackedTicks getTicksForSerialization(long time) {
return new ChunkAccess.PackedTicks(this.blockTicks.pack(time), this.fluidTicks.pack(time));
+ }
+
+ // Paper start - If loaded util
+ @Override
+ public final FluidState getFluidIfLoaded(BlockPos blockposition) {
+ return this.getFluidState(blockposition);
+ }
+
+ @Override
+ public final BlockState getBlockStateIfLoaded(BlockPos blockposition) {
+ return this.getBlockState(blockposition);
}
+ // Paper end
@Override
public BlockState getBlockState(BlockPos pos) {

View File

@@ -0,0 +1,47 @@
--- a/net/minecraft/world/level/chunk/UpgradeData.java
+++ b/net/minecraft/world/level/chunk/UpgradeData.java
@@ -113,12 +113,36 @@
}
}
+ // Paper start - filter out relocated neighbour ticks
+ // The lists are only supposed to contain ticks for the 1 radius neighbours of the chunk
+ private static <T> void filterTickList(int chunkX, int chunkZ, List<SavedTick<T>> ticks) {
+ for (java.util.Iterator<SavedTick<T>> iterator = ticks.iterator(); iterator.hasNext();) {
+ SavedTick<T> tick = iterator.next();
+ BlockPos tickPos = tick.pos();
+ int tickCX = tickPos.getX() >> 4;
+ int tickCZ = tickPos.getZ() >> 4;
+
+ int dist = Math.max(Math.abs(chunkX - tickCX), Math.abs(chunkZ - tickCZ));
+
+ if (dist != 1) {
+ LOGGER.warn("Neighbour tick '" + tick + "' serialized in chunk (" + chunkX + "," + chunkZ + ") is too far (" + tickCX + "," + tickCZ + ")");
+ iterator.remove();
+ }
+ }
+ }
+ // Paper end - filter out relocated neighbour ticks
+
public void upgrade(LevelChunk chunk) {
this.upgradeInside(chunk);
for (Direction8 direction8 : DIRECTIONS) {
upgradeSides(chunk, direction8);
}
+
+ // Paper start - filter out relocated neighbour ticks
+ filterTickList(chunk.locX, chunk.locZ, this.neighborBlockTicks);
+ filterTickList(chunk.locX, chunk.locZ, this.neighborFluidTicks);
+ // Paper end - filter out relocated neighbour ticks
Level level = chunk.getLevel();
this.neighborBlockTicks.forEach(tick -> {
@@ -129,6 +153,7 @@
Fluid fluid = tick.type() == Fluids.EMPTY ? level.getFluidState(tick.pos()).getType() : tick.type();
level.scheduleTick(tick.pos(), fluid, tick.delay(), tick.priority());
});
+ UpgradeData.BlockFixers.values(); // Paper - force the class init so that we don't access CHUNKY_FIXERS before all BlockFixers are initialised
CHUNKY_FIXERS.forEach(logic -> logic.processChunk(level));
}

View File

@@ -0,0 +1,84 @@
--- a/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
+++ b/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
@@ -36,7 +36,7 @@
static CompletableFuture<ChunkAccess> generateStructureStarts(WorldGenContext context, ChunkStep step, StaticCache2D<GenerationChunkHolder> chunks, ChunkAccess chunk) {
ServerLevel worldserver = context.level();
- if (worldserver.getServer().getWorldData().worldGenOptions().generateStructures()) {
+ if (worldserver.serverLevelData.worldGenOptions().generateStructures()) { // CraftBukkit
context.generator().createStructures(worldserver.registryAccess(), worldserver.getChunkSource().getGeneratorState(), worldserver.structureManager(), chunk, context.structureManager(), worldserver.dimension());
}
@@ -151,7 +151,7 @@
if (protochunk instanceof ImposterProtoChunk protochunkextension) {
chunk1 = protochunkextension.getWrapped();
} else {
- chunk1 = new LevelChunk(worldserver, protochunk, (chunk1) -> {
+ chunk1 = new LevelChunk(worldserver, protochunk, ($) -> { // Paper - decompile fix
ChunkStatusTasks.postLoadProtoChunk(worldserver, protochunk.getEntities());
});
generationchunkholder.replaceProtoChunk(new ImposterProtoChunk(chunk1, false));
@@ -168,10 +168,61 @@
}, context.mainThreadExecutor());
}
- private static void postLoadProtoChunk(ServerLevel world, List<CompoundTag> entities) {
+ public static void postLoadProtoChunk(ServerLevel world, List<CompoundTag> entities) { // Paper - public
if (!entities.isEmpty()) {
- world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entities, world, EntitySpawnReason.LOAD));
+ // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities
+ world.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(entities, world, EntitySpawnReason.LOAD).filter((entity) -> {
+ boolean needsRemoval = false;
+ net.minecraft.server.dedicated.DedicatedServer server = world.getCraftServer().getServer();
+ if (!world.getChunkSource().spawnFriendlies && (entity instanceof net.minecraft.world.entity.animal.Animal || entity instanceof net.minecraft.world.entity.animal.WaterAnimal)) {
+ entity.discard(null); // CraftBukkit - add Bukkit remove cause
+ needsRemoval = true;
+ }
+ checkDupeUUID(world, entity); // Paper - duplicate uuid resolving
+ return !needsRemoval;
+ }));
+ // CraftBukkit end
}
}
+
+ // Paper start - duplicate uuid resolving
+ // rets true if to prevent the entity from being added
+ public static boolean checkDupeUUID(ServerLevel level, net.minecraft.world.entity.Entity entity) {
+ io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode mode = level.paperConfig().entities.spawning.duplicateUuid.mode;
+ if (mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.WARN
+ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.DELETE
+ && mode != io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN) {
+ return false;
+ }
+ net.minecraft.world.entity.Entity other = level.getEntity(entity.getUUID());
+
+ if (other == null || other == entity) {
+ return false;
+ }
+
+ if (mode == io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DuplicateUUID.DuplicateUUIDMode.SAFE_REGEN && other != null && !other.isRemoved()
+ && Objects.equals(other.getEncodeId(), entity.getEncodeId())
+ && entity.getBukkitEntity().getLocation().distance(other.getBukkitEntity().getLocation()) < level.paperConfig().entities.spawning.duplicateUuid.safeRegenDeleteRange
+ ) {
+ entity.discard(null);
+ return true;
+ }
+ if (!other.isRemoved()) {
+ switch (mode) {
+ case SAFE_REGEN: {
+ entity.setUUID(java.util.UUID.randomUUID());
+ break;
+ }
+ case DELETE: {
+ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
+ return true;
+ }
+ default:
+ break;
+ }
+ }
+ return false;
+ }
+ // Paper end - duplicate uuid resolving
}

View File

@@ -0,0 +1,158 @@
--- a/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+++ b/net/minecraft/world/level/chunk/storage/ChunkStorage.java
@@ -15,10 +15,16 @@
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.resources.ResourceKey;
+import net.minecraft.server.level.ServerChunkCache;
+import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.datafix.DataFixTypes;
import net.minecraft.world.level.ChunkPos;
-import net.minecraft.world.level.Level;
+import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.chunk.ChunkGenerator;
+// CraftBukkit start
+import java.util.concurrent.ExecutionException;
+import net.minecraft.world.level.chunk.status.ChunkStatus;
+import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.structure.LegacyStructureDataHandler;
import net.minecraft.world.level.storage.DimensionDataStorage;
@@ -39,27 +45,86 @@
return this.worker.isOldChunkAround(chunkPos, checkRadius);
}
- public CompoundTag upgradeChunkTag(ResourceKey<Level> worldKey, Supplier<DimensionDataStorage> persistentStateManagerFactory, CompoundTag nbt, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> generatorCodecKey) {
- int i = ChunkStorage.getVersion(nbt);
+ // CraftBukkit start
+ private boolean check(ServerChunkCache cps, int x, int z) {
+ if (true) return true; // Paper - Perf: this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full"
+ ChunkPos pos = new ChunkPos(x, z);
+ if (cps != null) {
+ com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread");
+ if (cps.hasChunk(x, z)) {
+ return true;
+ }
+ }
+ CompoundTag nbt;
+ try {
+ nbt = this.read(pos).get().orElse(null);
+ } catch (InterruptedException | ExecutionException ex) {
+ throw new RuntimeException(ex);
+ }
+ if (nbt != null) {
+ CompoundTag level = nbt.getCompound("Level");
+ if (level.getBoolean("TerrainPopulated")) {
+ return true;
+ }
+
+ ChunkStatus status = ChunkStatus.byName(level.getString("Status"));
+ if (status != null && status.isOrAfter(ChunkStatus.FEATURES)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public CompoundTag upgradeChunkTag(ResourceKey<LevelStem> resourcekey, Supplier<DimensionDataStorage> supplier, CompoundTag nbttagcompound, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> optional, ChunkPos pos, @Nullable LevelAccessor generatoraccess) {
+ // CraftBukkit end
+ int i = ChunkStorage.getVersion(nbttagcompound);
+
if (i == SharedConstants.getCurrentVersion().getDataVersion().getVersion()) {
- return nbt;
+ return nbttagcompound;
} else {
try {
+ // CraftBukkit start
+ if (i < 1466) {
+ CompoundTag level = nbttagcompound.getCompound("Level");
+ if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) {
+ ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource();
+ if (this.check(cps, pos.x - 1, pos.z) && this.check(cps, pos.x - 1, pos.z - 1) && this.check(cps, pos.x, pos.z - 1)) {
+ level.putBoolean("LightPopulated", true);
+ }
+ }
+ }
+ // CraftBukkit end
+
if (i < 1493) {
- nbt = DataFixTypes.CHUNK.update(this.fixerUpper, nbt, i, 1493);
- if (nbt.getCompound("Level").getBoolean("hasLegacyStructureData")) {
- LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(worldKey, persistentStateManagerFactory);
+ nbttagcompound = DataFixTypes.CHUNK.update(this.fixerUpper, nbttagcompound, i, 1493);
+ if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) {
+ LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier);
- nbt = persistentstructurelegacy.updateFromLegacy(nbt);
+ nbttagcompound = persistentstructurelegacy.updateFromLegacy(nbttagcompound);
}
}
- ChunkStorage.injectDatafixingContext(nbt, worldKey, generatorCodecKey);
- nbt = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, nbt, Math.max(1493, i));
- ChunkStorage.removeDatafixingContext(nbt);
- NbtUtils.addCurrentDataVersion(nbt);
- return nbt;
+ // Spigot start - SPIGOT-6806: Quick and dirty way to prevent below zero generation in old chunks, by setting the status to heightmap instead of empty
+ boolean stopBelowZero = false;
+ boolean belowZeroGenerationInExistingChunks = (generatoraccess != null) ? ((ServerLevel) generatoraccess).spigotConfig.belowZeroGenerationInExistingChunks : org.spigotmc.SpigotConfig.belowZeroGenerationInExistingChunks;
+
+ if (i <= 2730 && !belowZeroGenerationInExistingChunks) {
+ stopBelowZero = "full".equals(nbttagcompound.getCompound("Level").getString("Status"));
+ }
+ // Spigot end
+
+ ChunkStorage.injectDatafixingContext(nbttagcompound, resourcekey, optional);
+ nbttagcompound = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, nbttagcompound, Math.max(1493, i));
+ // Spigot start
+ if (stopBelowZero) {
+ nbttagcompound.putString("Status", net.minecraft.core.registries.BuiltInRegistries.CHUNK_STATUS.getKey(ChunkStatus.SPAWN).toString());
+ }
+ // Spigot end
+ ChunkStorage.removeDatafixingContext(nbttagcompound);
+ NbtUtils.addCurrentDataVersion(nbttagcompound);
+ return nbttagcompound;
} catch (Exception exception) {
CrashReport crashreport = CrashReport.forThrowable(exception, "Updated chunk");
CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Updated chunk details");
@@ -70,7 +135,7 @@
}
}
- private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<Level> worldKey, Supplier<DimensionDataStorage> stateManagerGetter) {
+ private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<LevelStem> worldKey, Supplier<DimensionDataStorage> stateManagerGetter) { // CraftBukkit
LegacyStructureDataHandler persistentstructurelegacy = this.legacyStructureHandler;
if (persistentstructurelegacy == null) {
@@ -85,7 +150,7 @@
return persistentstructurelegacy;
}
- public static void injectDatafixingContext(CompoundTag nbt, ResourceKey<Level> worldKey, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> generatorCodecKey) {
+ public static void injectDatafixingContext(CompoundTag nbt, ResourceKey<LevelStem> worldKey, Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> generatorCodecKey) { // CraftBukkit
CompoundTag nbttagcompound1 = new CompoundTag();
nbttagcompound1.putString("dimension", worldKey.location().toString());
@@ -108,8 +173,19 @@
}
public CompletableFuture<Void> write(ChunkPos chunkPos, Supplier<CompoundTag> nbtSupplier) {
+ // Paper start - guard against possible chunk pos desync
+ final Supplier<CompoundTag> guardedPosCheck = () -> {
+ CompoundTag nbt = nbtSupplier.get();
+ if (nbt != null && !chunkPos.equals(SerializableChunkData.getChunkCoordinate(nbt))) {
+ final String world = (ChunkStorage.this instanceof net.minecraft.server.level.ChunkMap) ? ((net.minecraft.server.level.ChunkMap) ChunkStorage.this).level.getWorld().getName() : null;
+ throw new IllegalArgumentException("Chunk coordinate and serialized data do not have matching coordinates, trying to serialize coordinate " + chunkPos
+ + " but compound says coordinate is " + SerializableChunkData.getChunkCoordinate(nbt) + (world == null ? " for an unknown world" : (" for world: " + world)));
+ }
+ return nbt;
+ };
+ // Paper end - guard against possible chunk pos desync
this.handleLegacyStructureIndex(chunkPos);
- return this.worker.store(chunkPos, nbtSupplier);
+ return this.worker.store(chunkPos, guardedPosCheck); // Paper - guard against possible chunk pos desync
}
protected void handleLegacyStructureIndex(ChunkPos chunkPos) {

View File

@@ -0,0 +1,127 @@
--- a/net/minecraft/world/level/chunk/storage/RegionFile.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFile.java
@@ -1,3 +1,4 @@
+// mc-dev import
package net.minecraft.world.level.chunk.storage;
import com.google.common.annotations.VisibleForTesting;
@@ -49,7 +50,7 @@
protected final RegionBitmap usedSectors;
public RegionFile(RegionStorageInfo storageKey, Path directory, Path path, boolean dsync) throws IOException {
- this(storageKey, directory, path, RegionFileVersion.getSelected(), dsync);
+ this(storageKey, directory, path, RegionFileVersion.getCompressionFormat(), dsync); // Paper - Configurable region compression format
}
public RegionFile(RegionStorageInfo storageKey, Path path, Path directory, RegionFileVersion compressionFormat, boolean dsync) throws IOException {
@@ -63,8 +64,8 @@
} else {
this.externalFileDir = directory;
this.offsets = this.header.asIntBuffer();
- this.offsets.limit(1024);
- this.header.position(4096);
+ ((java.nio.Buffer) this.offsets).limit(1024); // CraftBukkit - decompile error
+ ((java.nio.Buffer) this.header).position(4096); // CraftBukkit - decompile error
this.timestamps = this.header.asIntBuffer();
if (dsync) {
this.file = FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.DSYNC);
@@ -73,7 +74,7 @@
}
this.usedSectors.force(0, 2);
- this.header.position(0);
+ ((java.nio.Buffer) this.header).position(0); // CraftBukkit - decompile error
int i = this.file.read(this.header, 0L);
if (i != -1) {
@@ -89,6 +90,14 @@
if (l != 0) {
int i1 = RegionFile.getSectorNumber(l);
int j1 = RegionFile.getNumSectors(l);
+ // Spigot start
+ if (j1 == 255) {
+ // We're maxed out, so we need to read the proper length from the section
+ ByteBuffer realLen = ByteBuffer.allocate(4);
+ this.file.read(realLen, i1 * 4096);
+ j1 = (realLen.getInt(0) + 4) / 4096 + 1;
+ }
+ // Spigot end
if (i1 < 2) {
RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", new Object[]{path, k, i1});
@@ -128,11 +137,18 @@
} else {
int j = RegionFile.getSectorNumber(i);
int k = RegionFile.getNumSectors(i);
+ // Spigot start
+ if (k == 255) {
+ ByteBuffer realLen = ByteBuffer.allocate(4);
+ this.file.read(realLen, j * 4096);
+ k = (realLen.getInt(0) + 4) / 4096 + 1;
+ }
+ // Spigot end
int l = k * 4096;
ByteBuffer bytebuffer = ByteBuffer.allocate(l);
this.file.read(bytebuffer, (long) (j * 4096));
- bytebuffer.flip();
+ ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
if (bytebuffer.remaining() < 5) {
RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", new Object[]{pos, l, bytebuffer.remaining()});
return null;
@@ -246,7 +262,7 @@
try {
this.file.read(bytebuffer, (long) (j * 4096));
- bytebuffer.flip();
+ ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
if (bytebuffer.remaining() != 5) {
return false;
} else {
@@ -280,6 +296,7 @@
return true;
}
} catch (IOException ioexception) {
+ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(ioexception); // Paper - ServerExceptionEvent
return false;
}
}
@@ -349,7 +366,7 @@
bytebuffer.putInt(1);
bytebuffer.put((byte) (this.version.getId() | 128));
- bytebuffer.flip();
+ ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error
return bytebuffer;
}
@@ -358,9 +375,10 @@
FileChannel filechannel = FileChannel.open(path1, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
try {
- buf.position(5);
+ ((java.nio.Buffer) buf).position(5); // CraftBukkit - decompile error
filechannel.write(buf);
} catch (Throwable throwable) {
+ com.destroystokyo.paper.exception.ServerInternalException.reportInternalException(throwable); // Paper - ServerExceptionEvent
if (filechannel != null) {
try {
filechannel.close();
@@ -382,7 +400,7 @@
}
private void writeHeader() throws IOException {
- this.header.position(0);
+ ((java.nio.Buffer) this.header).position(0); // CraftBukkit - decompile error
this.file.write(this.header, 0L);
}
@@ -418,7 +436,7 @@
if (i != j) {
ByteBuffer bytebuffer = RegionFile.PADDING_BUFFER.duplicate();
- bytebuffer.position(0);
+ ((java.nio.Buffer) bytebuffer).position(0); // CraftBukkit - decompile error
this.file.write(bytebuffer, (long) (j - 1));
}

View File

@@ -0,0 +1,67 @@
--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
@@ -32,21 +32,22 @@
this.info = storageKey;
}
- private RegionFile getRegionFile(ChunkPos pos) throws IOException {
- long i = ChunkPos.asLong(pos.getRegionX(), pos.getRegionZ());
+ private RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit
+ long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ());
RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i);
if (regionfile != null) {
return regionfile;
} else {
- if (this.regionCache.size() >= 256) {
+ if (this.regionCache.size() >= io.papermc.paper.configuration.GlobalConfiguration.get().misc.regionFileCacheSize) { // Paper - Sanitise RegionFileCache and make configurable
((RegionFile) this.regionCache.removeLast()).close();
}
FileUtil.createDirectoriesSafe(this.folder);
Path path = this.folder;
- int j = pos.getRegionX();
- Path path1 = path.resolve("r." + j + "." + pos.getRegionZ() + ".mca");
+ int j = chunkcoordintpair.getRegionX();
+ Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca");
+ if (existingOnly && !java.nio.file.Files.exists(path1)) return null; // CraftBukkit
RegionFile regionfile1 = new RegionFile(this.info, path1, this.folder, this.sync);
this.regionCache.putAndMoveToFirst(i, regionfile1);
@@ -56,7 +57,12 @@
@Nullable
public CompoundTag read(ChunkPos pos) throws IOException {
- RegionFile regionfile = this.getRegionFile(pos);
+ // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
+ RegionFile regionfile = this.getRegionFile(pos, true);
+ if (regionfile == null) {
+ return null;
+ }
+ // CraftBukkit end
DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos);
CompoundTag nbttagcompound;
@@ -96,7 +102,12 @@
}
public void scanChunk(ChunkPos chunkPos, StreamTagVisitor scanner) throws IOException {
- RegionFile regionfile = this.getRegionFile(chunkPos);
+ // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
+ RegionFile regionfile = this.getRegionFile(chunkPos, true);
+ if (regionfile == null) {
+ return;
+ }
+ // CraftBukkit end
DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkPos);
try {
@@ -122,7 +133,7 @@
}
protected void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException {
- RegionFile regionfile = this.getRegionFile(pos);
+ RegionFile regionfile = this.getRegionFile(pos, false); // CraftBukkit
if (nbt == null) {
regionfile.clear(pos);

View File

@@ -0,0 +1,18 @@
--- a/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
+++ b/net/minecraft/world/level/chunk/storage/RegionFileVersion.java
@@ -58,6 +58,15 @@
private final RegionFileVersion.StreamWrapper<InputStream> inputWrapper;
private final RegionFileVersion.StreamWrapper<OutputStream> outputWrapper;
+ // Paper start - Configurable region compression format
+ public static RegionFileVersion getCompressionFormat() {
+ return switch (io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.compressionFormat) {
+ case GZIP -> VERSION_GZIP;
+ case ZLIB -> VERSION_DEFLATE;
+ case NONE -> VERSION_NONE;
+ };
+ }
+ // Paper end - Configurable region compression format
private RegionFileVersion(
int id,
@Nullable String name,

View File

@@ -0,0 +1,216 @@
--- a/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
+++ b/net/minecraft/world/level/chunk/storage/SerializableChunkData.java
@@ -76,7 +76,8 @@
import net.minecraft.world.ticks.SavedTick;
import org.slf4j.Logger;
-public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chunkPos, int minSectionY, long lastUpdateTime, long inhabitedTime, ChunkStatus chunkStatus, @Nullable BlendingData.Packed blendingData, @Nullable BelowZeroRetrogen belowZeroRetrogen, UpgradeData upgradeData, @Nullable long[] carvingMask, Map<Heightmap.Types, long[]> heightmaps, ChunkAccess.PackedTicks packedTicks, ShortList[] postProcessingSections, boolean lightCorrect, List<SerializableChunkData.SectionData> sectionData, List<CompoundTag> entities, List<CompoundTag> blockEntities, CompoundTag structureData) {
+// CraftBukkit - persistentDataContainer
+public record SerializableChunkData(Registry<Biome> biomeRegistry, ChunkPos chunkPos, int minSectionY, long lastUpdateTime, long inhabitedTime, ChunkStatus chunkStatus, @Nullable BlendingData.Packed blendingData, @Nullable BelowZeroRetrogen belowZeroRetrogen, UpgradeData upgradeData, @Nullable long[] carvingMask, Map<Heightmap.Types, long[]> heightmaps, ChunkAccess.PackedTicks packedTicks, ShortList[] postProcessingSections, boolean lightCorrect, List<SerializableChunkData.SectionData> sectionData, List<CompoundTag> entities, List<CompoundTag> blockEntities, CompoundTag structureData, @Nullable Tag persistentDataContainer) {
public static final Codec<PalettedContainer<BlockState>> BLOCK_STATE_CODEC = PalettedContainer.codecRW(Block.BLOCK_STATE_REGISTRY, BlockState.CODEC, PalettedContainer.Strategy.SECTION_STATES, Blocks.AIR.defaultBlockState());
private static final Logger LOGGER = LogUtils.getLogger();
@@ -90,13 +91,39 @@
public static final String SECTIONS_TAG = "sections";
public static final String BLOCK_LIGHT_TAG = "BlockLight";
public static final String SKY_LIGHT_TAG = "SkyLight";
+ // Paper start - guard against serializing mismatching coordinates
+ // TODO Note: This needs to be re-checked each update
+ public static ChunkPos getChunkCoordinate(final CompoundTag chunkData) {
+ final int dataVersion = ChunkStorage.getVersion(chunkData);
+ if (dataVersion < 2842) { // Level tag is removed after this version
+ final CompoundTag levelData = chunkData.getCompound("Level");
+ return new ChunkPos(levelData.getInt("xPos"), levelData.getInt("zPos"));
+ } else {
+ return new ChunkPos(chunkData.getInt("xPos"), chunkData.getInt("zPos"));
+ }
+ }
+ // Paper end - guard against serializing mismatching coordinates
+ // Paper start - Do not let the server load chunks from newer versions
+ private static final int CURRENT_DATA_VERSION = net.minecraft.SharedConstants.getCurrentVersion().getDataVersion().getVersion();
+ private static final boolean JUST_CORRUPT_IT = Boolean.getBoolean("Paper.ignoreWorldDataVersion");
+ // Paper end - Do not let the server load chunks from newer versions
+
@Nullable
public static SerializableChunkData parse(LevelHeightAccessor world, RegistryAccess registryManager, CompoundTag nbt) {
if (!nbt.contains("Status", 8)) {
return null;
} else {
- ChunkPos chunkcoordintpair = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos"));
+ // Paper start - Do not let the server load chunks from newer versions
+ if (nbt.contains("DataVersion", net.minecraft.nbt.Tag.TAG_ANY_NUMERIC)) {
+ final int dataVersion = nbt.getInt("DataVersion");
+ if (!JUST_CORRUPT_IT && dataVersion > CURRENT_DATA_VERSION) {
+ new RuntimeException("Server attempted to load chunk saved with newer version of minecraft! " + dataVersion + " > " + CURRENT_DATA_VERSION).printStackTrace();
+ System.exit(1);
+ }
+ }
+ // Paper end - Do not let the server load chunks from newer versions
+ ChunkPos chunkcoordintpair = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos")); // Paper - guard against serializing mismatching coordinates; diff on change, see ChunkSerializer#getChunkCoordinate
long i = nbt.getLong("LastUpdate");
long j = nbt.getLong("InhabitedTime");
ChunkStatus chunkstatus = ChunkStatus.byName(nbt.getString("Status"));
@@ -110,7 +137,7 @@
dataresult = BlendingData.Packed.CODEC.parse(NbtOps.INSTANCE, nbt.getCompound("blending_data"));
logger = SerializableChunkData.LOGGER;
Objects.requireNonNull(logger);
- blendingdata_d = (BlendingData.Packed) dataresult.resultOrPartial(logger::error).orElse((Object) null);
+ blendingdata_d = (BlendingData.Packed) ((DataResult<BlendingData.Packed>) dataresult).resultOrPartial(logger::error).orElse(null); // CraftBukkit - decompile error
} else {
blendingdata_d = null;
}
@@ -121,7 +148,7 @@
dataresult = BelowZeroRetrogen.CODEC.parse(NbtOps.INSTANCE, nbt.getCompound("below_zero_retrogen"));
logger = SerializableChunkData.LOGGER;
Objects.requireNonNull(logger);
- belowzeroretrogen = (BelowZeroRetrogen) dataresult.resultOrPartial(logger::error).orElse((Object) null);
+ belowzeroretrogen = (BelowZeroRetrogen) ((DataResult<BelowZeroRetrogen>) dataresult).resultOrPartial(logger::error).orElse(null); // CraftBukkit - decompile error
} else {
belowzeroretrogen = null;
}
@@ -178,7 +205,7 @@
ListTag nbttaglist2 = nbt.getList("sections", 10);
List<SerializableChunkData.SectionData> list4 = new ArrayList(nbttaglist2.size());
Registry<Biome> iregistry = registryManager.lookupOrThrow(Registries.BIOME);
- Codec<PalettedContainerRO<Holder<Biome>>> codec = makeBiomeCodec(iregistry);
+ Codec<PalettedContainer<Holder<Biome>>> codec = makeBiomeCodecRW(iregistry); // CraftBukkit - read/write
for (int i1 = 0; i1 < nbttaglist2.size(); ++i1) {
CompoundTag nbttagcompound3 = nbttaglist2.getCompound(i1);
@@ -196,17 +223,17 @@
datapaletteblock = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
}
- Object object;
+ PalettedContainer object; // CraftBukkit - read/write
if (nbttagcompound3.contains("biomes", 10)) {
- object = (PalettedContainerRO) codec.parse(NbtOps.INSTANCE, nbttagcompound3.getCompound("biomes")).promotePartial((s1) -> {
+ object = codec.parse(NbtOps.INSTANCE, nbttagcompound3.getCompound("biomes")).promotePartial((s1) -> { // CraftBukkit - read/write
logErrors(chunkcoordintpair, b0, s1);
}).getOrThrow(SerializableChunkData.ChunkReadException::new);
} else {
object = new PalettedContainer<>(iregistry.asHolderIdMap(), iregistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
}
- chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainerRO) object);
+ chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write
} else {
chunksection = null;
}
@@ -217,7 +244,8 @@
list4.add(new SerializableChunkData.SectionData(b0, chunksection, nibblearray, nibblearray1));
}
- return new SerializableChunkData(iregistry, chunkcoordintpair, world.getMinSectionY(), i, j, chunkstatus, blendingdata_d, belowzeroretrogen, chunkconverter, along, map, ichunkaccess_a, ashortlist, flag, list4, list2, list3, nbttagcompound2);
+ // CraftBukkit - ChunkBukkitValues
+ return new SerializableChunkData(iregistry, chunkcoordintpair, world.getMinSectionY(), i, j, chunkstatus, blendingdata_d, belowzeroretrogen, chunkconverter, along, map, ichunkaccess_a, ashortlist, flag, list4, list2, list3, nbttagcompound2, nbt.get("ChunkBukkitValues"));
}
}
@@ -287,7 +315,13 @@
if (this.chunkStatus.isOrAfter(ChunkStatus.INITIALIZE_LIGHT)) {
protochunk.setLightEngine(levellightengine);
}
+ }
+
+ // CraftBukkit start - load chunk persistent data from nbt - SPIGOT-6814: Already load PDC here to account for 1.17 to 1.18 chunk upgrading.
+ if (this.persistentDataContainer instanceof CompoundTag) {
+ ((ChunkAccess) object).persistentDataContainer.putAll((CompoundTag) this.persistentDataContainer);
}
+ // CraftBukkit end
((ChunkAccess) object).setLightCorrect(this.lightCorrect);
EnumSet<Heightmap.Types> enumset = EnumSet.noneOf(Heightmap.Types.class);
@@ -329,6 +363,13 @@
while (iterator2.hasNext()) {
nbttagcompound = (CompoundTag) iterator2.next();
+ // Paper start - do not read tile entities positioned outside the chunk
+ final BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound);
+ if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) {
+ LOGGER.warn("Tile entity serialized in chunk {} in world '{}' positioned at {} is located outside of the chunk", chunkPos, world.getWorld().getName(), blockposition);
+ continue;
+ }
+ // Paper end - do not read tile entities positioned outside the chunk
protochunk1.setBlockEntityNbt(nbttagcompound);
}
@@ -348,6 +389,12 @@
return PalettedContainer.codecRO(biomeRegistry.asHolderIdMap(), biomeRegistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, biomeRegistry.getOrThrow(Biomes.PLAINS));
}
+ // CraftBukkit start - read/write
+ private static Codec<PalettedContainer<Holder<Biome>>> makeBiomeCodecRW(Registry<Biome> iregistry) {
+ return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getOrThrow(Biomes.PLAINS));
+ }
+ // CraftBukkit end
+
public static SerializableChunkData copyOf(ServerLevel world, ChunkAccess chunk) {
if (!chunk.canBeSerialized()) {
throw new IllegalArgumentException("Chunk can't be serialized: " + String.valueOf(chunk));
@@ -419,7 +466,14 @@
});
CompoundTag nbttagcompound1 = packStructureData(StructurePieceSerializationContext.fromLevel(world), chunkcoordintpair, chunk.getAllStarts(), chunk.getAllReferences());
- return new SerializableChunkData(world.registryAccess().lookupOrThrow(Registries.BIOME), chunkcoordintpair, chunk.getMinSectionY(), world.getGameTime(), chunk.getInhabitedTime(), chunk.getPersistedStatus(), (BlendingData.Packed) Optionull.map(chunk.getBlendingData(), BlendingData::pack), chunk.getBelowZeroRetrogen(), chunk.getUpgradeData().copy(), along, map, ichunkaccess_a, ashortlist, chunk.isLightCorrect(), list, list2, list1, nbttagcompound1);
+ // CraftBukkit start - store chunk persistent data in nbt
+ CompoundTag persistentDataContainer = null;
+ if (!chunk.persistentDataContainer.isEmpty()) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading.
+ persistentDataContainer = chunk.persistentDataContainer.toTagCompound();
+ }
+
+ return new SerializableChunkData(world.registryAccess().lookupOrThrow(Registries.BIOME), chunkcoordintpair, chunk.getMinSectionY(), world.getGameTime(), chunk.getInhabitedTime(), chunk.getPersistedStatus(), (BlendingData.Packed) Optionull.map(chunk.getBlendingData(), BlendingData::pack), chunk.getBelowZeroRetrogen(), chunk.getUpgradeData().copy(), along, map, ichunkaccess_a, ashortlist, chunk.isLightCorrect(), list, list2, list1, nbttagcompound1, persistentDataContainer);
+ // CraftBukkit end
}
}
@@ -432,7 +486,7 @@
nbttagcompound.putLong("LastUpdate", this.lastUpdateTime);
nbttagcompound.putLong("InhabitedTime", this.inhabitedTime);
nbttagcompound.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(this.chunkStatus).toString());
- DataResult dataresult;
+ DataResult<Tag> dataresult; // CraftBukkit - decompile error
Logger logger;
if (this.blendingData != null) {
@@ -513,6 +567,11 @@
});
nbttagcompound.put("Heightmaps", nbttagcompound2);
nbttagcompound.put("structures", this.structureData);
+ // CraftBukkit start - store chunk persistent data in nbt
+ if (this.persistentDataContainer != null) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading.
+ nbttagcompound.put("ChunkBukkitValues", this.persistentDataContainer);
+ }
+ // CraftBukkit end
return nbttagcompound;
}
@@ -564,6 +623,13 @@
chunk.setBlockEntityNbt(nbttagcompound);
} else {
BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound);
+ // Paper start - do not read tile entities positioned outside the chunk
+ ChunkPos chunkPos = chunk.getPos();
+ if ((blockposition.getX() >> 4) != chunkPos.x || (blockposition.getZ() >> 4) != chunkPos.z) {
+ LOGGER.warn("Tile entity serialized in chunk " + chunkPos + " in world '" + world.getWorld().getName() + "' positioned at " + blockposition + " is located outside of the chunk");
+ continue;
+ }
+ // Paper end - do not read tile entities positioned outside the chunk
BlockEntity tileentity = BlockEntity.loadStatic(blockposition, chunk.getBlockState(blockposition), nbttagcompound, world.registryAccess());
if (tileentity != null) {
@@ -623,6 +689,12 @@
StructureStart structurestart = StructureStart.loadStaticStart(context, nbttagcompound1.getCompound(s), worldSeed);
if (structurestart != null) {
+ // CraftBukkit start - load persistent data for structure start
+ net.minecraft.nbt.Tag persistentBase = nbttagcompound1.getCompound(s).get("StructureBukkitValues");
+ if (persistentBase instanceof CompoundTag) {
+ structurestart.persistentDataContainer.putAll((CompoundTag) persistentBase);
+ }
+ // CraftBukkit end
map.put(structure, structurestart);
}
}