package net.minecraft.world.level.chunk; import com.google.common.base.Suppliers; import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import it.unimi.dsi.fastutil.ints.IntArraySet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import it.unimi.dsi.fastutil.objects.ObjectArraySet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.CrashReportDetail; import net.minecraft.ReportedException; import net.minecraft.SharedConstants; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.HolderLookup; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.network.protocol.game.DebugPackets; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.WorldGenRegion; import net.minecraft.util.random.WeightedList; import net.minecraft.world.entity.MobCategory; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelHeightAccessor; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.NoiseColumn; import net.minecraft.world.level.StructureManager; import net.minecraft.world.level.WorldGenLevel; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeGenerationSettings; import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.biome.FeatureSorter; import net.minecraft.world.level.biome.MobSpawnSettings; import net.minecraft.world.level.biome.FeatureSorter.StepFeatureData; import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.levelgen.GenerationStep; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.LegacyRandomSource; import net.minecraft.world.level.levelgen.RandomState; import net.minecraft.world.level.levelgen.RandomSupport; import net.minecraft.world.level.levelgen.WorldgenRandom; import net.minecraft.world.level.levelgen.XoroshiroRandomSource; import net.minecraft.world.level.levelgen.blending.Blender; import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureCheckResult; import net.minecraft.world.level.levelgen.structure.StructureSet; import net.minecraft.world.level.levelgen.structure.StructureSpawnOverride; import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.levelgen.structure.StructureSet.StructureSelectionEntry; import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement; import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; import org.apache.commons.lang3.mutable.MutableBoolean; import org.jetbrains.annotations.Nullable; public abstract class ChunkGenerator { public static final Codec CODEC = BuiltInRegistries.CHUNK_GENERATOR.byNameCodec().dispatchStable(ChunkGenerator::codec, Function.identity()); protected final BiomeSource biomeSource; private final Supplier> featuresPerStep; private final Function, BiomeGenerationSettings> generationSettingsGetter; public ChunkGenerator(BiomeSource biomeSource) { this(biomeSource, holder -> ((Biome)holder.value()).getGenerationSettings()); } public ChunkGenerator(BiomeSource biomeSource, Function, BiomeGenerationSettings> generationSettingsGetter) { this.biomeSource = biomeSource; this.generationSettingsGetter = generationSettingsGetter; this.featuresPerStep = Suppliers.memoize( () -> FeatureSorter.buildFeaturesPerStep( List.copyOf(biomeSource.possibleBiomes()), holder -> ((BiomeGenerationSettings)generationSettingsGetter.apply(holder)).features(), true ) ); } public void validate() { this.featuresPerStep.get(); } protected abstract MapCodec codec(); public ChunkGeneratorStructureState createState(HolderLookup structureSetLookup, RandomState randomState, long seed) { return ChunkGeneratorStructureState.createForNormal(randomState, seed, this.biomeSource, structureSetLookup); } public Optional>> getTypeNameForDataFixer() { return BuiltInRegistries.CHUNK_GENERATOR.getResourceKey(this.codec()); } public CompletableFuture createBiomes(RandomState randomState, Blender blender, StructureManager structureManager, ChunkAccess chunk) { return CompletableFuture.supplyAsync(() -> { chunk.fillBiomesFromNoise(this.biomeSource, randomState.sampler()); return chunk; }, Util.backgroundExecutor().forName("init_biomes")); } public abstract void applyCarvers( WorldGenRegion level, long seed, RandomState random, BiomeManager biomeManager, StructureManager structureManager, ChunkAccess chunk ); @Nullable public Pair> findNearestMapStructure( ServerLevel level, HolderSet structure, BlockPos pos, int searchRadius, boolean skipKnownStructures ) { ChunkGeneratorStructureState chunkGeneratorStructureState = level.getChunkSource().getGeneratorState(); Map>> map = new Object2ObjectArrayMap<>(); for (Holder holder : structure) { for (StructurePlacement structurePlacement : chunkGeneratorStructureState.getPlacementsForStructure(holder)) { ((Set)map.computeIfAbsent(structurePlacement, structurePlacementx -> new ObjectArraySet())).add(holder); } } if (map.isEmpty()) { return null; } else { Pair> pair = null; double d = Double.MAX_VALUE; StructureManager structureManager = level.structureManager(); List>>> list = new ArrayList(map.size()); for (Entry>> entry : map.entrySet()) { StructurePlacement structurePlacement2 = (StructurePlacement)entry.getKey(); if (structurePlacement2 instanceof ConcentricRingsStructurePlacement concentricRingsStructurePlacement) { Pair> pair2 = this.getNearestGeneratedStructure( (Set>)entry.getValue(), level, structureManager, pos, skipKnownStructures, concentricRingsStructurePlacement ); if (pair2 != null) { BlockPos blockPos = pair2.getFirst(); double e = pos.distSqr(blockPos); if (e < d) { d = e; pair = pair2; } } } else if (structurePlacement2 instanceof RandomSpreadStructurePlacement) { list.add(entry); } } if (!list.isEmpty()) { int i = SectionPos.blockToSectionCoord(pos.getX()); int j = SectionPos.blockToSectionCoord(pos.getZ()); for (int k = 0; k <= searchRadius; k++) { boolean bl = false; for (Entry>> entry2 : list) { RandomSpreadStructurePlacement randomSpreadStructurePlacement = (RandomSpreadStructurePlacement)entry2.getKey(); Pair> pair3 = getNearestGeneratedStructure( (Set>)entry2.getValue(), level, structureManager, i, j, k, skipKnownStructures, chunkGeneratorStructureState.getLevelSeed(), randomSpreadStructurePlacement ); if (pair3 != null) { bl = true; double f = pos.distSqr(pair3.getFirst()); if (f < d) { d = f; pair = pair3; } } } if (bl) { return pair; } } } return pair; } } @Nullable private Pair> getNearestGeneratedStructure( Set> structureHoldersSet, ServerLevel level, StructureManager structureManager, BlockPos pos, boolean skipKnownStructures, ConcentricRingsStructurePlacement placement ) { List list = level.getChunkSource().getGeneratorState().getRingPositionsFor(placement); if (list == null) { throw new IllegalStateException("Somehow tried to find structures for a placement that doesn't exist"); } else { Pair> pair = null; double d = Double.MAX_VALUE; BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); for (ChunkPos chunkPos : list) { mutableBlockPos.set(SectionPos.sectionToBlockCoord(chunkPos.x, 8), 32, SectionPos.sectionToBlockCoord(chunkPos.z, 8)); double e = mutableBlockPos.distSqr(pos); boolean bl = pair == null || e < d; if (bl) { Pair> pair2 = getStructureGeneratingAt(structureHoldersSet, level, structureManager, skipKnownStructures, placement, chunkPos); if (pair2 != null) { pair = pair2; d = e; } } } return pair; } } @Nullable private static Pair> getNearestGeneratedStructure( Set> structureHoldersSet, LevelReader level, StructureManager structureManager, int x, int y, int z, boolean skipKnownStructures, long seed, RandomSpreadStructurePlacement spreadPlacement ) { int i = spreadPlacement.spacing(); for (int j = -z; j <= z; j++) { boolean bl = j == -z || j == z; for (int k = -z; k <= z; k++) { boolean bl2 = k == -z || k == z; if (bl || bl2) { int l = x + i * j; int m = y + i * k; ChunkPos chunkPos = spreadPlacement.getPotentialStructureChunk(seed, l, m); Pair> pair = getStructureGeneratingAt( structureHoldersSet, level, structureManager, skipKnownStructures, spreadPlacement, chunkPos ); if (pair != null) { return pair; } } } } return null; } @Nullable private static Pair> getStructureGeneratingAt( Set> structureHoldersSet, LevelReader level, StructureManager structureManager, boolean skipKnownStructures, StructurePlacement placement, ChunkPos chunkPos ) { for (Holder holder : structureHoldersSet) { StructureCheckResult structureCheckResult = structureManager.checkStructurePresence(chunkPos, holder.value(), placement, skipKnownStructures); if (structureCheckResult != StructureCheckResult.START_NOT_PRESENT) { if (!skipKnownStructures && structureCheckResult == StructureCheckResult.START_PRESENT) { return Pair.of(placement.getLocatePos(chunkPos), holder); } ChunkAccess chunkAccess = level.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.STRUCTURE_STARTS); StructureStart structureStart = structureManager.getStartForStructure(SectionPos.bottomOf(chunkAccess), holder.value(), chunkAccess); if (structureStart != null && structureStart.isValid() && (!skipKnownStructures || tryAddReference(structureManager, structureStart))) { return Pair.of(placement.getLocatePos(structureStart.getChunkPos()), holder); } } } return null; } private static boolean tryAddReference(StructureManager structureManager, StructureStart structureStart) { if (structureStart.canBeReferenced()) { structureManager.addReference(structureStart); return true; } else { return false; } } public void applyBiomeDecoration(WorldGenLevel level, ChunkAccess chunk, StructureManager structureManager) { ChunkPos chunkPos = chunk.getPos(); if (!SharedConstants.debugVoidTerrain(chunkPos)) { SectionPos sectionPos = SectionPos.of(chunkPos, level.getMinSectionY()); BlockPos blockPos = sectionPos.origin(); Registry registry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE); Map> map = (Map>)registry.stream().collect(Collectors.groupingBy(structure -> structure.step().ordinal())); List list = (List)this.featuresPerStep.get(); WorldgenRandom worldgenRandom = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed())); long l = worldgenRandom.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ()); Set> set = new ObjectArraySet<>(); ChunkPos.rangeClosed(sectionPos.chunk(), 1).forEach(chunkPosx -> { ChunkAccess chunkAccess = level.getChunk(chunkPosx.x, chunkPosx.z); for (LevelChunkSection levelChunkSection : chunkAccess.getSections()) { levelChunkSection.getBiomes().getAll(set::add); } }); set.retainAll(this.biomeSource.possibleBiomes()); int i = list.size(); try { Registry registry2 = level.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); int j = Math.max(GenerationStep.Decoration.values().length, i); for (int k = 0; k < j; k++) { int m = 0; if (structureManager.shouldGenerateStructures()) { for (Structure structure : (List)map.getOrDefault(k, Collections.emptyList())) { worldgenRandom.setFeatureSeed(l, m, k); Supplier supplier = () -> (String)registry.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString); try { level.setCurrentlyGenerating(supplier); structureManager.startsForStructure(sectionPos, structure) .forEach(structureStart -> structureStart.placeInChunk(level, structureManager, this, worldgenRandom, getWritableArea(chunk), chunkPos)); } catch (Exception var29) { CrashReport crashReport = CrashReport.forThrowable(var29, "Feature placement"); crashReport.addCategory("Feature").setDetail("Description", supplier::get); throw new ReportedException(crashReport); } m++; } } if (k < i) { IntSet intSet = new IntArraySet(); for (Holder holder : set) { List> list3 = ((BiomeGenerationSettings)this.generationSettingsGetter.apply(holder)).features(); if (k < list3.size()) { HolderSet holderSet = (HolderSet)list3.get(k); StepFeatureData stepFeatureData = (StepFeatureData)list.get(k); holderSet.stream().map(Holder::value).forEach(placedFeaturex -> intSet.add(stepFeatureData.indexMapping().applyAsInt(placedFeaturex))); } } int n = intSet.size(); int[] is = intSet.toIntArray(); Arrays.sort(is); StepFeatureData stepFeatureData2 = (StepFeatureData)list.get(k); for (int o = 0; o < n; o++) { int p = is[o]; PlacedFeature placedFeature = (PlacedFeature)stepFeatureData2.features().get(p); Supplier supplier2 = () -> (String)registry2.getResourceKey(placedFeature).map(Object::toString).orElseGet(placedFeature::toString); worldgenRandom.setFeatureSeed(l, p, k); try { level.setCurrentlyGenerating(supplier2); placedFeature.placeWithBiomeCheck(level, this, worldgenRandom, blockPos); } catch (Exception var30) { CrashReport crashReport2 = CrashReport.forThrowable(var30, "Feature placement"); crashReport2.addCategory("Feature").setDetail("Description", supplier2::get); throw new ReportedException(crashReport2); } } } } level.setCurrentlyGenerating(null); } catch (Exception var31) { CrashReport crashReport3 = CrashReport.forThrowable(var31, "Biome decoration"); crashReport3.addCategory("Generation").setDetail("CenterX", chunkPos.x).setDetail("CenterZ", chunkPos.z).setDetail("Decoration Seed", l); throw new ReportedException(crashReport3); } } } private static BoundingBox getWritableArea(ChunkAccess chunk) { ChunkPos chunkPos = chunk.getPos(); int i = chunkPos.getMinBlockX(); int j = chunkPos.getMinBlockZ(); LevelHeightAccessor levelHeightAccessor = chunk.getHeightAccessorForGeneration(); int k = levelHeightAccessor.getMinY() + 1; int l = levelHeightAccessor.getMaxY(); return new BoundingBox(i, k, j, i + 15, l, j + 15); } public abstract void buildSurface(WorldGenRegion level, StructureManager structureManager, RandomState random, ChunkAccess chunk); public abstract void spawnOriginalMobs(WorldGenRegion level); public int getSpawnHeight(LevelHeightAccessor level) { return 64; } public BiomeSource getBiomeSource() { return this.biomeSource; } public abstract int getGenDepth(); public WeightedList getMobsAt(Holder biome, StructureManager structureManager, MobCategory category, BlockPos pos) { Map map = structureManager.getAllStructuresAt(pos); for (Entry entry : map.entrySet()) { Structure structure = (Structure)entry.getKey(); StructureSpawnOverride structureSpawnOverride = (StructureSpawnOverride)structure.spawnOverrides().get(category); if (structureSpawnOverride != null) { MutableBoolean mutableBoolean = new MutableBoolean(false); Predicate predicate = structureSpawnOverride.boundingBox() == StructureSpawnOverride.BoundingBoxType.PIECE ? structureStart -> structureManager.structureHasPieceAt(pos, structureStart) : structureStart -> structureStart.getBoundingBox().isInside(pos); structureManager.fillStartsForStructure(structure, (LongSet)entry.getValue(), structureStart -> { if (mutableBoolean.isFalse() && predicate.test(structureStart)) { mutableBoolean.setTrue(); } }); if (mutableBoolean.isTrue()) { return structureSpawnOverride.spawns(); } } } return biome.value().getMobSettings().getMobs(category); } public void createStructures( RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess chunk, StructureTemplateManager structureTemplateManager, ResourceKey level ) { ChunkPos chunkPos = chunk.getPos(); SectionPos sectionPos = SectionPos.bottomOf(chunk); RandomState randomState = structureState.randomState(); structureState.possibleStructureSets() .forEach( holder -> { StructurePlacement structurePlacement = ((StructureSet)holder.value()).placement(); List list = ((StructureSet)holder.value()).structures(); for (StructureSelectionEntry structureSelectionEntry : list) { StructureStart structureStart = structureManager.getStartForStructure(sectionPos, structureSelectionEntry.structure().value(), chunk); if (structureStart != null && structureStart.isValid()) { return; } } if (structurePlacement.isStructureChunk(structureState, chunkPos.x, chunkPos.z)) { if (list.size() == 1) { this.tryGenerateStructure( (StructureSelectionEntry)list.get(0), structureManager, registryAccess, randomState, structureTemplateManager, structureState.getLevelSeed(), chunk, chunkPos, sectionPos, level ); } else { ArrayList arrayList = new ArrayList(list.size()); arrayList.addAll(list); WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); worldgenRandom.setLargeFeatureSeed(structureState.getLevelSeed(), chunkPos.x, chunkPos.z); int i = 0; for (StructureSelectionEntry structureSelectionEntry2 : arrayList) { i += structureSelectionEntry2.weight(); } while (!arrayList.isEmpty()) { int j = worldgenRandom.nextInt(i); int k = 0; for (StructureSelectionEntry structureSelectionEntry3 : arrayList) { j -= structureSelectionEntry3.weight(); if (j < 0) { break; } k++; } StructureSelectionEntry structureSelectionEntry4 = (StructureSelectionEntry)arrayList.get(k); if (this.tryGenerateStructure( structureSelectionEntry4, structureManager, registryAccess, randomState, structureTemplateManager, structureState.getLevelSeed(), chunk, chunkPos, sectionPos, level )) { return; } arrayList.remove(k); i -= structureSelectionEntry4.weight(); } } } } ); } private boolean tryGenerateStructure( StructureSelectionEntry structureSelectionEntry, StructureManager structureManager, RegistryAccess registryAccess, RandomState random, StructureTemplateManager structureTemplateManager, long seed, ChunkAccess chunk, ChunkPos chunkPos, SectionPos sectionPos, ResourceKey level ) { Structure structure = structureSelectionEntry.structure().value(); int i = fetchReferences(structureManager, chunk, sectionPos, structure); HolderSet holderSet = structure.biomes(); Predicate> predicate = holderSet::contains; StructureStart structureStart = structure.generate( structureSelectionEntry.structure(), level, registryAccess, this, this.biomeSource, random, structureTemplateManager, seed, chunkPos, i, chunk, predicate ); if (structureStart.isValid()) { structureManager.setStartForStructure(sectionPos, structure, structureStart, chunk); return true; } else { return false; } } private static int fetchReferences(StructureManager structureManager, ChunkAccess chunk, SectionPos sectionPos, Structure structure) { StructureStart structureStart = structureManager.getStartForStructure(sectionPos, structure, chunk); return structureStart != null ? structureStart.getReferences() : 0; } public void createReferences(WorldGenLevel level, StructureManager structureManager, ChunkAccess chunk) { int i = 8; ChunkPos chunkPos = chunk.getPos(); int j = chunkPos.x; int k = chunkPos.z; int l = chunkPos.getMinBlockX(); int m = chunkPos.getMinBlockZ(); SectionPos sectionPos = SectionPos.bottomOf(chunk); for (int n = j - 8; n <= j + 8; n++) { for (int o = k - 8; o <= k + 8; o++) { long p = ChunkPos.asLong(n, o); for (StructureStart structureStart : level.getChunk(n, o).getAllStarts().values()) { try { if (structureStart.isValid() && structureStart.getBoundingBox().intersects(l, m, l + 15, m + 15)) { structureManager.addReferenceForStructure(sectionPos, structureStart.getStructure(), p, chunk); DebugPackets.sendStructurePacket(level, structureStart); } } catch (Exception var21) { CrashReport crashReport = CrashReport.forThrowable(var21, "Generating structure reference"); CrashReportCategory crashReportCategory = crashReport.addCategory("Structure"); Optional> optional = level.registryAccess().lookup(Registries.STRUCTURE); crashReportCategory.setDetail( "Id", (CrashReportDetail)(() -> (String)optional.map(registry -> registry.getKey(structureStart.getStructure()).toString()).orElse("UNKNOWN")) ); crashReportCategory.setDetail( "Name", (CrashReportDetail)(() -> BuiltInRegistries.STRUCTURE_TYPE.getKey(structureStart.getStructure().type()).toString()) ); crashReportCategory.setDetail("Class", (CrashReportDetail)(() -> structureStart.getStructure().getClass().getCanonicalName())); throw new ReportedException(crashReport); } } } } } public abstract CompletableFuture fillFromNoise(Blender blender, RandomState randomState, StructureManager structureManager, ChunkAccess chunk); public abstract int getSeaLevel(); public abstract int getMinY(); public abstract int getBaseHeight(int x, int z, Heightmap.Types type, LevelHeightAccessor level, RandomState random); public abstract NoiseColumn getBaseColumn(int x, int z, LevelHeightAccessor height, RandomState random); public int getFirstFreeHeight(int x, int z, Heightmap.Types type, LevelHeightAccessor level, RandomState random) { return this.getBaseHeight(x, z, type, level, random); } public int getFirstOccupiedHeight(int x, int z, Heightmap.Types types, LevelHeightAccessor level, RandomState random) { return this.getBaseHeight(x, z, types, level, random) - 1; } public abstract void addDebugScreenInfo(List info, RandomState random, BlockPos pos); @Deprecated public BiomeGenerationSettings getBiomeGenerationSettings(Holder biome) { return (BiomeGenerationSettings)this.generationSettingsGetter.apply(biome); } }