package net.minecraft.world.level.chunk; import com.google.common.base.Stopwatch; import com.mojang.datafixers.util.Pair; import com.mojang.logging.LogUtils; import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; 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.SectionPos; import net.minecraft.util.RandomSource; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.levelgen.RandomState; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureSet; 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.StructurePlacement; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class ChunkGeneratorStructureState { private static final Logger LOGGER = LogUtils.getLogger(); private final RandomState randomState; private final BiomeSource biomeSource; private final long levelSeed; private final long concentricRingsSeed; private final Map> placementsForStructure = new Object2ObjectOpenHashMap<>(); private final Map>> ringPositions = new Object2ObjectArrayMap<>(); private boolean hasGeneratedPositions; private final List> possibleStructureSets; public static ChunkGeneratorStructureState createForFlat( RandomState randomState, long levelSeed, BiomeSource biomeSource, Stream> structureSets ) { List> list = structureSets.filter(holder -> hasBiomesForStructureSet((StructureSet)holder.value(), biomeSource)).toList(); return new ChunkGeneratorStructureState(randomState, biomeSource, levelSeed, 0L, list); } public static ChunkGeneratorStructureState createForNormal( RandomState randomState, long seed, BiomeSource biomeSource, HolderLookup structureSetLookup ) { List> list = (List>)structureSetLookup.listElements() .filter(reference -> hasBiomesForStructureSet((StructureSet)reference.value(), biomeSource)) .collect(Collectors.toUnmodifiableList()); return new ChunkGeneratorStructureState(randomState, biomeSource, seed, seed, list); } private static boolean hasBiomesForStructureSet(StructureSet structureSet, BiomeSource biomeSource) { Stream> stream = structureSet.structures().stream().flatMap(structureSelectionEntry -> { Structure structure = structureSelectionEntry.structure().value(); return structure.biomes().stream(); }); return stream.anyMatch(biomeSource.possibleBiomes()::contains); } private ChunkGeneratorStructureState( RandomState randomState, BiomeSource biomeSource, long levelSeed, long cocentricRingsSeed, List> possibleStructureSets ) { this.randomState = randomState; this.levelSeed = levelSeed; this.biomeSource = biomeSource; this.concentricRingsSeed = cocentricRingsSeed; this.possibleStructureSets = possibleStructureSets; } public List> possibleStructureSets() { return this.possibleStructureSets; } private void generatePositions() { Set> set = this.biomeSource.possibleBiomes(); this.possibleStructureSets().forEach(holder -> { StructureSet structureSet = (StructureSet)holder.value(); boolean bl = false; for (StructureSelectionEntry structureSelectionEntry : structureSet.structures()) { Structure structure = structureSelectionEntry.structure().value(); if (structure.biomes().stream().anyMatch(set::contains)) { ((List)this.placementsForStructure.computeIfAbsent(structure, structurex -> new ArrayList())).add(structureSet.placement()); bl = true; } } if (bl && structureSet.placement() instanceof ConcentricRingsStructurePlacement concentricRingsStructurePlacement) { this.ringPositions.put(concentricRingsStructurePlacement, this.generateRingPositions(holder, concentricRingsStructurePlacement)); } }); } private CompletableFuture> generateRingPositions(Holder structureSet, ConcentricRingsStructurePlacement placement) { if (placement.count() == 0) { return CompletableFuture.completedFuture(List.of()); } else { Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER); int i = placement.distance(); int j = placement.count(); List> list = new ArrayList(j); int k = placement.spread(); HolderSet holderSet = placement.preferredBiomes(); RandomSource randomSource = RandomSource.create(); randomSource.setSeed(this.concentricRingsSeed); double d = randomSource.nextDouble() * Math.PI * 2.0; int l = 0; int m = 0; for (int n = 0; n < j; n++) { double e = 4 * i + i * m * 6 + (randomSource.nextDouble() - 0.5) * (i * 2.5); int o = (int)Math.round(Math.cos(d) * e); int p = (int)Math.round(Math.sin(d) * e); RandomSource randomSource2 = randomSource.fork(); list.add( CompletableFuture.supplyAsync( () -> { Pair> pair = this.biomeSource .findBiomeHorizontal( SectionPos.sectionToBlockCoord(o, 8), 0, SectionPos.sectionToBlockCoord(p, 8), 112, holderSet::contains, randomSource2, this.randomState.sampler() ); if (pair != null) { BlockPos blockPos = pair.getFirst(); return new ChunkPos(SectionPos.blockToSectionCoord(blockPos.getX()), SectionPos.blockToSectionCoord(blockPos.getZ())); } else { return new ChunkPos(o, p); } }, Util.backgroundExecutor().forName("structureRings") ) ); d += (Math.PI * 2) / k; if (++l == k) { m++; l = 0; k += 2 * k / (m + 1); k = Math.min(k, j - n); d += randomSource.nextDouble() * Math.PI * 2.0; } } return Util.sequence(list).thenApply(listx -> { double dx = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS) / 1000.0; LOGGER.debug("Calculation for {} took {}s", structureSet, dx); return listx; }); } } public void ensureStructuresGenerated() { if (!this.hasGeneratedPositions) { this.generatePositions(); this.hasGeneratedPositions = true; } } @Nullable public List getRingPositionsFor(ConcentricRingsStructurePlacement placement) { this.ensureStructuresGenerated(); CompletableFuture> completableFuture = (CompletableFuture>)this.ringPositions.get(placement); return completableFuture != null ? (List)completableFuture.join() : null; } public List getPlacementsForStructure(Holder structure) { this.ensureStructuresGenerated(); return (List)this.placementsForStructure.getOrDefault(structure.value(), List.of()); } public RandomState randomState() { return this.randomState; } public boolean hasStructureChunkInRange(Holder structureSet, int x, int z, int range) { StructurePlacement structurePlacement = structureSet.value().placement(); for (int i = x - range; i <= x + range; i++) { for (int j = z - range; j <= z + range; j++) { if (structurePlacement.isStructureChunk(this, i, j)) { return true; } } } return false; } public long getLevelSeed() { return this.levelSeed; } }