This commit is contained in:
Brian Neumann-Fopiano
2026-02-19 04:09:06 -05:00
parent b491d9efd0
commit 415c2f5837
11 changed files with 392 additions and 843 deletions
@@ -25,28 +25,29 @@ import org.bukkit.craftbukkit.v1_21_R7.CraftWorld;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
public class CustomBiomeSource extends BiomeSource {
private static final int NOISE_BIOME_CACHE_MAX = 262144;
private final long seed;
private final Engine engine;
private final Registry<Biome> biomeCustomRegistry;
private final Registry<Biome> biomeRegistry;
private final AtomicCache<RegistryAccess> registryAccess = new AtomicCache<>();
private final RNG rng;
private final KMap<String, Holder<Biome>> customBiomes;
private final Holder<Biome> fallbackBiome;
private final ConcurrentHashMap<Long, Holder<Biome>> noiseBiomeCache = new ConcurrentHashMap<>();
public CustomBiomeSource(long seed, Engine engine, World world) {
this.engine = engine;
this.seed = seed;
this.biomeCustomRegistry = registry().lookup(Registries.BIOME).orElse(null);
this.biomeRegistry = ((RegistryAccess) getFor(RegistryAccess.Frozen.class, ((CraftServer) Bukkit.getServer()).getHandle().getServer())).lookup(Registries.BIOME).orElse(null);
this.rng = new RNG(engine.getSeedManager().getBiome());
this.fallbackBiome = resolveFallbackBiome(this.biomeRegistry, this.biomeCustomRegistry);
this.customBiomes = fillCustomBiomes(this.biomeCustomRegistry, engine, this.fallbackBiome);
}
@@ -172,31 +173,85 @@ public class CustomBiomeSource extends BiomeSource {
@Override
public Holder<Biome> getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
int m = (y - engine.getMinHeight()) << 2;
IrisBiome ib = engine.getComplex().getTrueBiomeStream().get(x << 2, z << 2);
if (ib == null) {
return resolveFallbackBiome(biomeRegistry, biomeCustomRegistry);
long cacheKey = packNoiseKey(x, y, z);
Holder<Biome> cachedHolder = noiseBiomeCache.get(cacheKey);
if (cachedHolder != null) {
return cachedHolder;
}
if (ib.isCustom()) {
IrisBiomeCustom custom = ib.getCustomBiome(rng, x << 2, m, z << 2);
if (custom != null) {
Holder<Biome> holder = customBiomes.get(custom.getId());
Holder<Biome> resolvedHolder = resolveNoiseBiomeHolder(x, y, z);
Holder<Biome> existingHolder = noiseBiomeCache.putIfAbsent(cacheKey, resolvedHolder);
if (existingHolder != null) {
return existingHolder;
}
if (noiseBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
noiseBiomeCache.clear();
}
return resolvedHolder;
}
private Holder<Biome> resolveNoiseBiomeHolder(int x, int y, int z) {
if (engine == null || engine.isClosed()) {
return getFallbackBiome();
}
if (engine.getComplex() == null) {
return getFallbackBiome();
}
int blockX = x << 2;
int blockZ = z << 2;
int blockY = (y - engine.getMinHeight()) << 2;
IrisBiome irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
if (irisBiome == null) {
return getFallbackBiome();
}
RNG noiseRng = new RNG(seed
^ (((long) blockX) * 341873128712L)
^ (((long) blockY) * 132897987541L)
^ (((long) blockZ) * 42317861L));
if (irisBiome.isCustom()) {
IrisBiomeCustom customBiome = irisBiome.getCustomBiome(noiseRng, blockX, blockY, blockZ);
if (customBiome != null) {
Holder<Biome> holder = customBiomes.get(customBiome.getId());
if (holder != null) {
return holder;
}
}
return resolveFallbackBiome(biomeRegistry, biomeCustomRegistry);
return getFallbackBiome();
}
org.bukkit.block.Biome v = ib.getSkyBiome(rng, x << 2, m, z << 2);
Holder<Biome> holder = NMSBinding.biomeToBiomeBase(biomeRegistry, v);
org.bukkit.block.Biome vanillaBiome = irisBiome.getSkyBiome(noiseRng, blockX, blockY, blockZ);
Holder<Biome> holder = NMSBinding.biomeToBiomeBase(biomeRegistry, vanillaBiome);
if (holder != null) {
return holder;
}
return resolveFallbackBiome(biomeRegistry, biomeCustomRegistry);
return getFallbackBiome();
}
private Holder<Biome> getFallbackBiome() {
if (fallbackBiome != null) {
return fallbackBiome;
}
Holder<Biome> holder = resolveFallbackBiome(biomeRegistry, biomeCustomRegistry);
if (holder != null) {
return holder;
}
throw new IllegalStateException("Unable to resolve any biome holder fallback for Iris biome source");
}
private static long packNoiseKey(int x, int y, int z) {
return (((long) x & 67108863L) << 38)
| (((long) z & 67108863L) << 12)
| ((long) y & 4095L);
}
private static Holder<Biome> resolveCustomBiomeHolder(Registry<Biome> customRegistry, Engine engine, String customBiomeId) {
@@ -30,6 +30,7 @@ import net.minecraft.world.level.levelgen.*;
import net.minecraft.world.level.levelgen.blending.Blender;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.bukkit.Bukkit;
@@ -50,6 +51,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
private final ChunkGenerator delegate;
private final Engine engine;
private volatile Registry<Structure> cachedStructureRegistry;
private volatile Map<Structure, Integer> cachedStructureOrder;
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null);
@@ -152,20 +155,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) {
if (!structureManager.shouldGenerateStructures())
return;
if (!IrisSettings.get().getGeneral().isAutoGenerateIntrinsicStructures()) {
return;
}
SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSectionY());
BlockPos blockPos = sectionPos.origin();
WorldgenRandom random = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
long i = random.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ());
var structures = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
var list = structures.stream()
.sorted(Comparator.comparingInt(s -> s.step().ordinal()))
.toList();
Registry<Structure> structureRegistry = level.registryAccess().lookupOrThrow(Registries.STRUCTURE);
Map<Structure, Integer> structureOrder = getStructureOrder(structureRegistry);
var surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG);
var ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG);
var motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING);
var motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES);
Heightmap surface = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG);
Heightmap ocean = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG);
Heightmap motion = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING);
Heightmap motionNoLeaves = chunkAccess.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
@@ -174,21 +178,42 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
int noAir = engine.getHeight(wX, wZ, false) + engine.getMinHeight() + 1;
int noFluid = engine.getHeight(wX, wZ, true) + engine.getMinHeight() + 1;
SET_HEIGHT.invoke(ocean, x, z, Math.min(noFluid, ocean.getFirstAvailable(x, z)));
SET_HEIGHT.invoke(surface, x, z, Math.min(noAir, surface.getFirstAvailable(x, z)));
SET_HEIGHT.invoke(motion, x, z, Math.min(noAir, motion.getFirstAvailable(x, z)));
SET_HEIGHT.invoke(motionNoLeaves, x, z, Math.min(noAir, motionNoLeaves.getFirstAvailable(x, z)));
int oceanHeight = ocean.getFirstAvailable(x, z);
int surfaceHeight = surface.getFirstAvailable(x, z);
int motionHeight = motion.getFirstAvailable(x, z);
int motionNoLeavesHeight = motionNoLeaves.getFirstAvailable(x, z);
if (noFluid > oceanHeight) {
SET_HEIGHT.invoke(ocean, x, z, noFluid);
}
if (noAir > surfaceHeight) {
SET_HEIGHT.invoke(surface, x, z, noAir);
}
if (noAir > motionHeight) {
SET_HEIGHT.invoke(motion, x, z, noAir);
}
if (noAir > motionNoLeavesHeight) {
SET_HEIGHT.invoke(motionNoLeaves, x, z, noAir);
}
}
}
for (int j = 0; j < list.size(); j++) {
Structure structure = list.get(j);
random.setFeatureSeed(i, j, structure.step().ordinal());
Supplier<String> supplier = () -> structures.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString);
List<StructureStart> starts = new ArrayList<>(structureManager.startsForStructure(chunkAccess.getPos(), structure -> true));
starts.sort(Comparator.comparingInt(start -> structureOrder.getOrDefault(start.getStructure(), Integer.MAX_VALUE)));
int seededStructureIndex = Integer.MIN_VALUE;
for (int j = 0; j < starts.size(); j++) {
StructureStart start = starts.get(j);
Structure structure = start.getStructure();
int structureIndex = structureOrder.getOrDefault(structure, j);
if (structureIndex != seededStructureIndex) {
random.setFeatureSeed(i, structureIndex, structure.step().ordinal());
seededStructureIndex = structureIndex;
}
Supplier<String> supplier = () -> structureRegistry.getResourceKey(structure).map(Object::toString).orElseGet(structure::toString);
try {
level.setCurrentlyGenerating(supplier);
structureManager.startsForStructure(sectionPos, structure).forEach((start) -> start.placeInChunk(level, structureManager, this, random, getWritableArea(chunkAccess), chunkAccess.getPos()));
start.placeInChunk(level, structureManager, this, random, getWritableArea(chunkAccess), chunkAccess.getPos());
} catch (Exception exception) {
CrashReport crashReport = CrashReport.forThrowable(exception, "Feature placement");
CrashReportCategory category = crashReport.addCategory("Feature");
@@ -210,6 +235,35 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15);
}
private Map<Structure, Integer> getStructureOrder(Registry<Structure> structureRegistry) {
Map<Structure, Integer> localOrder = cachedStructureOrder;
Registry<Structure> localRegistry = cachedStructureRegistry;
if (localRegistry == structureRegistry && localOrder != null) {
return localOrder;
}
synchronized (this) {
Map<Structure, Integer> synchronizedOrder = cachedStructureOrder;
Registry<Structure> synchronizedRegistry = cachedStructureRegistry;
if (synchronizedRegistry == structureRegistry && synchronizedOrder != null) {
return synchronizedOrder;
}
List<Structure> sortedStructures = structureRegistry.stream()
.sorted(Comparator.comparingInt(structure -> structure.step().ordinal()))
.toList();
Map<Structure, Integer> builtOrder = new IdentityHashMap<>(sortedStructures.size());
for (int index = 0; index < sortedStructures.size(); index++) {
Structure structure = sortedStructures.get(index);
builtOrder.put(structure, index);
}
cachedStructureRegistry = structureRegistry;
cachedStructureOrder = builtOrder;
return builtOrder;
}
}
@Override
public void spawnOriginalMobs(WorldGenRegion regionlimitedworldaccess) {
delegate.spawnOriginalMobs(regionlimitedworldaccess);
@@ -727,6 +727,48 @@ public class NMSBinding implements INMSBinding {
return keys;
}
@Override
public KMap<String, KList<String>> getVanillaStructureBiomeTags() {
KMap<String, KList<String>> tags = new KMap<>();
Registry<net.minecraft.world.level.biome.Biome> registry = registry().lookup(Registries.BIOME).orElse(null);
if (registry == null) {
return tags;
}
registry.getTags().forEach(named -> {
TagKey<net.minecraft.world.level.biome.Biome> tagKey = named.key();
Identifier location = tagKey.location();
if (!"minecraft".equals(location.getNamespace())) {
return;
}
String path = location.getPath();
if (!path.startsWith("has_structure/")) {
return;
}
KList<String> values = new KList<>();
named.stream().forEach(holder -> {
net.minecraft.world.level.biome.Biome biome = holder.value();
Identifier biomeLocation = registry.getKey(biome);
if (biomeLocation == null) {
return;
}
if ("minecraft".equals(biomeLocation.getNamespace())) {
values.add(biomeLocation.toString());
}
});
KList<String> uniqueValues = values.removeDuplicates();
if (!uniqueValues.isEmpty()) {
tags.put(path, uniqueValues);
}
});
return tags;
}
@Override
public boolean missingDimensionTypes(String... keys) {
var type = registry().lookupOrThrow(Registries.DIMENSION_TYPE);