Allow placing datapack structures in Iris worlds (#1225)

* Allow placing datapack structures in Iris worlds

* command for generating configs for datapack structures

* remove the sub dir from the snippet key
This commit is contained in:
Julian Krings
2025-09-07 18:36:08 +02:00
committed by GitHub
parent aa14242b54
commit a8ee321eb8
35 changed files with 2718 additions and 265 deletions

View File

@@ -8,16 +8,18 @@ import com.volmit.iris.engine.framework.ResultLocator;
import com.volmit.iris.engine.framework.WrongEngineBroException;
import com.volmit.iris.engine.object.IrisJigsawStructure;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.engine.object.IrisStructurePopulator;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.collection.KSet;
import com.volmit.iris.util.mantle.flag.MantleFlag;
import com.volmit.iris.util.math.Position2;
import com.volmit.iris.util.reflect.WrappedField;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.RegistryAccess;
import com.volmit.iris.util.reflect.WrappedReturningMethod;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.core.*;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
@@ -27,45 +29,55 @@ import net.minecraft.tags.StructureTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.MobSpawnSettings;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.chunk.ChunkStatus;
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.templatesystem.StructureTemplateManager;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R1.generator.CustomChunkGenerator;
import org.bukkit.craftbukkit.v1_20_R1.generator.structure.CraftStructure;
import org.bukkit.event.world.AsyncStructureSpawnEvent;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
public class IrisChunkGenerator extends CustomChunkGenerator {
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
private final ChunkGenerator delegate;
private final Engine engine;
private final KMap<ResourceKey<Structure>, KSet<String>> structures = new KMap<>();
private final IrisStructurePopulator populator;
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null);
this.delegate = delegate;
this.engine = engine;
this.populator = new IrisStructurePopulator(engine);
var dimension = engine.getDimension();
KSet<IrisJigsawStructure> placements = new KSet<>();
@@ -182,8 +194,58 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
@Override
public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) {
delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager);
public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager) {
if (!structureManager.shouldGenerateStructures())
return;
var chunkPos = access.getPos();
var sectionPos = SectionPos.bottomOf(access);
var registry = registryAccess.registryOrThrow(Registries.STRUCTURE);
populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> {
var loc = ResourceLocation.tryParse(key);
if (loc == null) return false;
var structure = registry.getOptional(loc).orElse(null);
if (structure == null) return false;
var biomes = structure.biomes();
var start = structure.generate(
registryAccess,
this,
biomeSource,
structureState.randomState(),
templateManager,
structureState.getLevelSeed(),
chunkPos,
fetchReferences(structureManager, access, sectionPos, structure),
access,
(biome) -> ignoreBiomes || biomes.contains(biome)
);
if (!start.isValid())
return false;
BoundingBox box = start.getBoundingBox();
AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent(
structureManager.level.getMinecraftWorld().getWorld(),
CraftStructure.minecraftToBukkit(structure, registryAccess),
new org.bukkit.util.BoundingBox(
box.minX(),
box.minY(),
box.minZ(),
box.maxX(),
box.maxY(),
box.maxZ()
), chunkPos.x, chunkPos.z);
Bukkit.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
structureManager.setStartForStructure(sectionPos, structure, start, access);
}
return true;
});
}
private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) {
StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access);
return structurestart != null ? structurestart.getReferences() : 0;
}
@Override
@@ -201,11 +263,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.fillFromNoise(executor, blender, randomstate, structuremanager, ichunkaccess);
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
}
@Override
public WeightedRandomList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition);
@@ -213,7 +270,75 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager);
applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true);
}
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false);
}
@Override
public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) {
if (!structureManager.shouldGenerateStructures())
return;
SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSection());
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().registryOrThrow(Registries.STRUCTURE);
var list = structures.stream()
.sorted(Comparator.comparingInt(s -> s.step().ordinal()))
.toList();
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);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int wX = x + blockPos.getX();
int wZ = z + blockPos.getZ();
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)));
}
}
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);
try {
level.setCurrentlyGenerating(supplier);
structureManager.startsForStructure(sectionPos, structure).forEach((start) -> 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");
category.setDetail("Description", supplier::get);
throw new ReportedException(crashReport);
}
}
Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.POST_FEATURES);
}
private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) {
ChunkPos chunkPos = ichunkaccess.getPos();
int minX = chunkPos.getMinBlockX();
int minZ = chunkPos.getMinBlockZ();
LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration();
int minY = heightAccessor.getMinBuildHeight() + 1;
int maxY = heightAccessor.getMaxBuildHeight() - 1;
return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15);
}
@Override
@@ -236,9 +361,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.getGenDepth();
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return levelheightaccessor.getMinBuildHeight() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1;
}
@Override
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate);
int block = engine.getHeight(i, j, true);
int water = engine.getHeight(i, j, false);
BlockState[] column = new BlockState[levelheightaccessor.getHeight()];
for (int k = 0; k < column.length; k++) {
if (k <= block) column[k] = Blocks.STONE.defaultBlockState();
else if (k <= water) column[k] = Blocks.WATER.defaultBlockState();
else column[k] = Blocks.AIR.defaultBlockState();
}
return new NoiseColumn(levelheightaccessor.getMinBuildHeight(), column);
}
static {
@@ -251,7 +389,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
if (biomeSource == null)
throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!");
Method setHeight = null;
for (Method method : Heightmap.class.getDeclaredMethods()) {
var types = method.getParameterTypes();
if (types.length != 3 || !Arrays.equals(types, new Class<?>[]{int.class, int.class, int.class})
|| !method.getReturnType().equals(void.class))
continue;
setHeight = method;
break;
}
if (setHeight == null)
throw new RuntimeException("Could not find setHeight method in Heightmap!");
BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName());
SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes());
}
private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) {

View File

@@ -3,11 +3,14 @@ package com.volmit.iris.core.nms.v1_20_R1;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.datafixers.util.Pair;
import com.volmit.iris.Iris;
import com.volmit.iris.core.link.Identifier;
import com.volmit.iris.core.nms.INMSBinding;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.container.StructurePlacement;
import com.volmit.iris.core.nms.container.BlockProperty;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
import com.volmit.iris.util.agent.Agent;
import com.volmit.iris.util.collection.KList;
@@ -59,6 +62,8 @@ import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -79,7 +84,6 @@ import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.Color;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@@ -90,6 +94,7 @@ import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class NMSBinding implements INMSBinding {
@@ -704,6 +709,71 @@ public class NMSBinding implements INMSBinding {
return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName);
}
@Override
public void placeStructures(Chunk chunk) {
var craft = ((CraftChunk) chunk);
var level = craft.getCraftWorld().getHandle();
var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL);
level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager());
}
@Override
public KMap<Identifier, StructurePlacement> collectStructures() {
var structureSets = registry().registryOrThrow(Registries.STRUCTURE_SET);
var structurePlacements = registry().registryOrThrow(Registries.STRUCTURE_PLACEMENT);
return structureSets.registryKeySet()
.stream()
.map(structureSets::getHolder)
.filter(Optional::isPresent)
.map(Optional::get)
.map(holder -> {
var set = holder.value();
var placement = set.placement();
var key = holder.key().location();
StructurePlacement.StructurePlacementBuilder<?, ?> builder;
if (placement instanceof RandomSpreadStructurePlacement random) {
builder = StructurePlacement.RandomSpread.builder()
.separation(random.separation())
.spacing(random.spacing())
.spreadType(switch (random.spreadType()) {
case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR;
case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR;
});
} else if (placement instanceof ConcentricRingsStructurePlacement rings) {
builder = StructurePlacement.ConcentricRings.builder()
.distance(rings.distance())
.spread(rings.spread())
.count(rings.count());
} else {
Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type()));
return null;
}
return new com.volmit.iris.core.nms.container.Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder
.salt(placement.salt)
.frequency(placement.frequency)
.structures(set.structures()
.stream()
.map(entry -> new StructurePlacement.Structure(
entry.weight(),
entry.structure()
.unwrapKey()
.map(ResourceKey::location)
.map(ResourceLocation::toString)
.orElse(null),
entry.structure().tags()
.map(TagKey::location)
.map(ResourceLocation::toString)
.toList()
))
.filter(StructurePlacement.Structure::isValid)
.toList())
.build());
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(com.volmit.iris.core.nms.container.Pair::getA, com.volmit.iris.core.nms.container.Pair::getB, (a, b) -> a, KMap::new));
}
public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) {
if (!(raw instanceof PlatformChunkGenerator gen))
throw new IllegalStateException("Generator is not platform chunk generator!");

View File

@@ -8,16 +8,18 @@ import com.volmit.iris.engine.framework.ResultLocator;
import com.volmit.iris.engine.framework.WrongEngineBroException;
import com.volmit.iris.engine.object.IrisJigsawStructure;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.engine.object.IrisStructurePopulator;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.collection.KSet;
import com.volmit.iris.util.mantle.flag.MantleFlag;
import com.volmit.iris.util.math.Position2;
import com.volmit.iris.util.reflect.WrappedField;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.RegistryAccess;
import com.volmit.iris.util.reflect.WrappedReturningMethod;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.core.*;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
@@ -27,45 +29,55 @@ import net.minecraft.tags.StructureTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.MobSpawnSettings;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.chunk.ChunkStatus;
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.templatesystem.StructureTemplateManager;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R2.generator.CustomChunkGenerator;
import org.bukkit.craftbukkit.v1_20_R2.generator.structure.CraftStructure;
import org.bukkit.event.world.AsyncStructureSpawnEvent;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
public class IrisChunkGenerator extends CustomChunkGenerator {
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
private final ChunkGenerator delegate;
private final Engine engine;
private final KMap<ResourceKey<Structure>, KSet<String>> structures = new KMap<>();
private final IrisStructurePopulator populator;
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null);
this.delegate = delegate;
this.engine = engine;
this.populator = new IrisStructurePopulator(engine);
var dimension = engine.getDimension();
KSet<IrisJigsawStructure> placements = new KSet<>();
@@ -182,8 +194,58 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
@Override
public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) {
delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager);
public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager) {
if (!structureManager.shouldGenerateStructures())
return;
var chunkPos = access.getPos();
var sectionPos = SectionPos.bottomOf(access);
var registry = registryAccess.registryOrThrow(Registries.STRUCTURE);
populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> {
var loc = ResourceLocation.tryParse(key);
if (loc == null) return false;
var structure = registry.getOptional(loc).orElse(null);
if (structure == null) return false;
var biomes = structure.biomes();
var start = structure.generate(
registryAccess,
this,
biomeSource,
structureState.randomState(),
templateManager,
structureState.getLevelSeed(),
chunkPos,
fetchReferences(structureManager, access, sectionPos, structure),
access,
(biome) -> ignoreBiomes || biomes.contains(biome)
);
if (!start.isValid())
return false;
BoundingBox box = start.getBoundingBox();
AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent(
structureManager.level.getMinecraftWorld().getWorld(),
CraftStructure.minecraftToBukkit(structure, registryAccess),
new org.bukkit.util.BoundingBox(
box.minX(),
box.minY(),
box.minZ(),
box.maxX(),
box.maxY(),
box.maxZ()
), chunkPos.x, chunkPos.z);
Bukkit.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
structureManager.setStartForStructure(sectionPos, structure, start, access);
}
return true;
});
}
private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) {
StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access);
return structurestart != null ? structurestart.getReferences() : 0;
}
@Override
@@ -201,11 +263,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.fillFromNoise(executor, blender, randomstate, structuremanager, ichunkaccess);
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
}
@Override
public WeightedRandomList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition);
@@ -213,7 +270,75 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager);
applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true);
}
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false);
}
@Override
public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) {
if (!structureManager.shouldGenerateStructures())
return;
SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSection());
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().registryOrThrow(Registries.STRUCTURE);
var list = structures.stream()
.sorted(Comparator.comparingInt(s -> s.step().ordinal()))
.toList();
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);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int wX = x + blockPos.getX();
int wZ = z + blockPos.getZ();
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)));
}
}
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);
try {
level.setCurrentlyGenerating(supplier);
structureManager.startsForStructure(sectionPos, structure).forEach((start) -> 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");
category.setDetail("Description", supplier::get);
throw new ReportedException(crashReport);
}
}
Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.POST_FEATURES);
}
private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) {
ChunkPos chunkPos = ichunkaccess.getPos();
int minX = chunkPos.getMinBlockX();
int minZ = chunkPos.getMinBlockZ();
LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration();
int minY = heightAccessor.getMinBuildHeight() + 1;
int maxY = heightAccessor.getMaxBuildHeight() - 1;
return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15);
}
@Override
@@ -236,9 +361,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.getGenDepth();
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return levelheightaccessor.getMinBuildHeight() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1;
}
@Override
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate);
int block = engine.getHeight(i, j, true);
int water = engine.getHeight(i, j, false);
BlockState[] column = new BlockState[levelheightaccessor.getHeight()];
for (int k = 0; k < column.length; k++) {
if (k <= block) column[k] = Blocks.STONE.defaultBlockState();
else if (k <= water) column[k] = Blocks.WATER.defaultBlockState();
else column[k] = Blocks.AIR.defaultBlockState();
}
return new NoiseColumn(levelheightaccessor.getMinBuildHeight(), column);
}
static {
@@ -251,7 +389,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
if (biomeSource == null)
throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!");
Method setHeight = null;
for (Method method : Heightmap.class.getDeclaredMethods()) {
var types = method.getParameterTypes();
if (types.length != 3 || !Arrays.equals(types, new Class<?>[]{int.class, int.class, int.class})
|| !method.getReturnType().equals(void.class))
continue;
setHeight = method;
break;
}
if (setHeight == null)
throw new RuntimeException("Could not find setHeight method in Heightmap!");
BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName());
SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes());
}
private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) {

View File

@@ -1,9 +1,26 @@
package com.volmit.iris.core.nms.v1_20_R2;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.mojang.datafixers.util.Pair;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMSBinding;
import com.mojang.serialization.Lifecycle;
import com.volmit.iris.core.link.Identifier;
import com.volmit.iris.core.nms.container.AutoClosing;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.container.BlockProperty;
import com.volmit.iris.engine.data.cache.AtomicCache;
@@ -21,6 +38,8 @@ import com.volmit.iris.util.matter.MatterBiomeInject;
import com.volmit.iris.util.nbt.mca.NBTWorld;
import com.volmit.iris.util.nbt.mca.palette.*;
import com.volmit.iris.util.nbt.tag.CompoundTag;
import com.volmit.iris.core.nms.container.StructurePlacement;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.util.scheduling.J;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import net.bytebuddy.ByteBuddy;
@@ -58,6 +77,8 @@ import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.bukkit.*;
@@ -707,6 +728,71 @@ public class NMSBinding implements INMSBinding {
return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName);
}
@Override
public void placeStructures(Chunk chunk) {
var craft = ((CraftChunk) chunk);
var level = craft.getCraftWorld().getHandle();
var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL);
level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager());
}
@Override
public KMap<Identifier, StructurePlacement> collectStructures() {
var structureSets = registry().registryOrThrow(Registries.STRUCTURE_SET);
var structurePlacements = registry().registryOrThrow(Registries.STRUCTURE_PLACEMENT);
return structureSets.registryKeySet()
.stream()
.map(structureSets::getHolder)
.filter(Optional::isPresent)
.map(Optional::get)
.map(holder -> {
var set = holder.value();
var placement = set.placement();
var key = holder.key().location();
StructurePlacement.StructurePlacementBuilder<?, ?> builder;
if (placement instanceof RandomSpreadStructurePlacement random) {
builder = StructurePlacement.RandomSpread.builder()
.separation(random.separation())
.spacing(random.spacing())
.spreadType(switch (random.spreadType()) {
case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR;
case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR;
});
} else if (placement instanceof ConcentricRingsStructurePlacement rings) {
builder = StructurePlacement.ConcentricRings.builder()
.distance(rings.distance())
.spread(rings.spread())
.count(rings.count());
} else {
Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type()));
return null;
}
return new com.volmit.iris.core.nms.container.Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder
.salt(placement.salt)
.frequency(placement.frequency)
.structures(set.structures()
.stream()
.map(entry -> new StructurePlacement.Structure(
entry.weight(),
entry.structure()
.unwrapKey()
.map(ResourceKey::location)
.map(ResourceLocation::toString)
.orElse(null),
entry.structure().tags()
.map(TagKey::location)
.map(ResourceLocation::toString)
.toList()
))
.filter(StructurePlacement.Structure::isValid)
.toList())
.build());
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(com.volmit.iris.core.nms.container.Pair::getA, com.volmit.iris.core.nms.container.Pair::getB, (a, b) -> a, KMap::new));
}
public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) {
if (!(raw instanceof PlatformChunkGenerator gen))
throw new IllegalStateException("Generator is not platform chunk generator!");

View File

@@ -8,16 +8,18 @@ import com.volmit.iris.engine.framework.ResultLocator;
import com.volmit.iris.engine.framework.WrongEngineBroException;
import com.volmit.iris.engine.object.IrisJigsawStructure;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.engine.object.IrisStructurePopulator;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.collection.KSet;
import com.volmit.iris.util.mantle.flag.MantleFlag;
import com.volmit.iris.util.math.Position2;
import com.volmit.iris.util.reflect.WrappedField;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.RegistryAccess;
import com.volmit.iris.util.reflect.WrappedReturningMethod;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.core.*;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
@@ -27,45 +29,55 @@ import net.minecraft.tags.StructureTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.MobSpawnSettings;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.chunk.ChunkStatus;
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.templatesystem.StructureTemplateManager;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_20_R3.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R3.generator.CustomChunkGenerator;
import org.bukkit.craftbukkit.v1_20_R3.generator.structure.CraftStructure;
import org.bukkit.event.world.AsyncStructureSpawnEvent;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
public class IrisChunkGenerator extends CustomChunkGenerator {
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
private final ChunkGenerator delegate;
private final Engine engine;
private final KMap<ResourceKey<Structure>, KSet<String>> structures = new KMap<>();
private final IrisStructurePopulator populator;
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null);
this.delegate = delegate;
this.engine = engine;
this.populator = new IrisStructurePopulator(engine);
var dimension = engine.getDimension();
KSet<IrisJigsawStructure> placements = new KSet<>();
@@ -182,8 +194,58 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
@Override
public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) {
delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager);
public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager) {
if (!structureManager.shouldGenerateStructures())
return;
var chunkPos = access.getPos();
var sectionPos = SectionPos.bottomOf(access);
var registry = registryAccess.registryOrThrow(Registries.STRUCTURE);
populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> {
var loc = ResourceLocation.tryParse(key);
if (loc == null) return false;
var structure = registry.getOptional(loc).orElse(null);
if (structure == null) return false;
var biomes = structure.biomes();
var start = structure.generate(
registryAccess,
this,
biomeSource,
structureState.randomState(),
templateManager,
structureState.getLevelSeed(),
chunkPos,
fetchReferences(structureManager, access, sectionPos, structure),
access,
(biome) -> ignoreBiomes || biomes.contains(biome)
);
if (!start.isValid())
return false;
BoundingBox box = start.getBoundingBox();
AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent(
structureManager.level.getMinecraftWorld().getWorld(),
CraftStructure.minecraftToBukkit(structure),
new org.bukkit.util.BoundingBox(
box.minX(),
box.minY(),
box.minZ(),
box.maxX(),
box.maxY(),
box.maxZ()
), chunkPos.x, chunkPos.z);
Bukkit.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
structureManager.setStartForStructure(sectionPos, structure, start, access);
}
return true;
});
}
private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) {
StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access);
return structurestart != null ? structurestart.getReferences() : 0;
}
@Override
@@ -201,11 +263,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.fillFromNoise(executor, blender, randomstate, structuremanager, ichunkaccess);
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
}
@Override
public WeightedRandomList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition);
@@ -213,7 +270,75 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager);
applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true);
}
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false);
}
@Override
public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) {
if (!structureManager.shouldGenerateStructures())
return;
SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSection());
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().registryOrThrow(Registries.STRUCTURE);
var list = structures.stream()
.sorted(Comparator.comparingInt(s -> s.step().ordinal()))
.toList();
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);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int wX = x + blockPos.getX();
int wZ = z + blockPos.getZ();
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)));
}
}
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);
try {
level.setCurrentlyGenerating(supplier);
structureManager.startsForStructure(sectionPos, structure).forEach((start) -> 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");
category.setDetail("Description", supplier::get);
throw new ReportedException(crashReport);
}
}
Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.POST_FEATURES);
}
private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) {
ChunkPos chunkPos = ichunkaccess.getPos();
int minX = chunkPos.getMinBlockX();
int minZ = chunkPos.getMinBlockZ();
LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration();
int minY = heightAccessor.getMinBuildHeight() + 1;
int maxY = heightAccessor.getMaxBuildHeight() - 1;
return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15);
}
@Override
@@ -236,9 +361,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.getGenDepth();
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return levelheightaccessor.getMinBuildHeight() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1;
}
@Override
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate);
int block = engine.getHeight(i, j, true);
int water = engine.getHeight(i, j, false);
BlockState[] column = new BlockState[levelheightaccessor.getHeight()];
for (int k = 0; k < column.length; k++) {
if (k <= block) column[k] = Blocks.STONE.defaultBlockState();
else if (k <= water) column[k] = Blocks.WATER.defaultBlockState();
else column[k] = Blocks.AIR.defaultBlockState();
}
return new NoiseColumn(levelheightaccessor.getMinBuildHeight(), column);
}
static {
@@ -251,7 +389,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
if (biomeSource == null)
throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!");
Method setHeight = null;
for (Method method : Heightmap.class.getDeclaredMethods()) {
var types = method.getParameterTypes();
if (types.length != 3 || !Arrays.equals(types, new Class<?>[]{int.class, int.class, int.class})
|| !method.getReturnType().equals(void.class))
continue;
setHeight = method;
break;
}
if (setHeight == null)
throw new RuntimeException("Could not find setHeight method in Heightmap!");
BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName());
SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes());
}
private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) {

View File

@@ -1,9 +1,26 @@
package com.volmit.iris.core.nms.v1_20_R3;
import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.datafixers.util.Pair;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMSBinding;
import com.mojang.serialization.Lifecycle;
import com.volmit.iris.core.link.Identifier;
import com.volmit.iris.core.nms.container.AutoClosing;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.container.BlockProperty;
import com.volmit.iris.engine.data.cache.AtomicCache;
@@ -21,6 +38,8 @@ import com.volmit.iris.util.matter.MatterBiomeInject;
import com.volmit.iris.util.nbt.mca.NBTWorld;
import com.volmit.iris.util.nbt.mca.palette.*;
import com.volmit.iris.util.nbt.tag.CompoundTag;
import com.volmit.iris.core.nms.container.StructurePlacement;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.util.scheduling.J;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import net.bytebuddy.ByteBuddy;
@@ -58,6 +77,8 @@ import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.bukkit.*;
@@ -708,6 +729,71 @@ public class NMSBinding implements INMSBinding {
return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName);
}
@Override
public void placeStructures(Chunk chunk) {
var craft = ((CraftChunk) chunk);
var level = craft.getCraftWorld().getHandle();
var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL);
level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager());
}
@Override
public KMap<Identifier, StructurePlacement> collectStructures() {
var structureSets = registry().registryOrThrow(Registries.STRUCTURE_SET);
var structurePlacements = registry().registryOrThrow(Registries.STRUCTURE_PLACEMENT);
return structureSets.registryKeySet()
.stream()
.map(structureSets::getHolder)
.filter(Optional::isPresent)
.map(Optional::get)
.map(holder -> {
var set = holder.value();
var placement = set.placement();
var key = holder.key().location();
StructurePlacement.StructurePlacementBuilder<?, ?> builder;
if (placement instanceof RandomSpreadStructurePlacement random) {
builder = StructurePlacement.RandomSpread.builder()
.separation(random.separation())
.spacing(random.spacing())
.spreadType(switch (random.spreadType()) {
case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR;
case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR;
});
} else if (placement instanceof ConcentricRingsStructurePlacement rings) {
builder = StructurePlacement.ConcentricRings.builder()
.distance(rings.distance())
.spread(rings.spread())
.count(rings.count());
} else {
Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type()));
return null;
}
return new com.volmit.iris.core.nms.container.Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder
.salt(placement.salt)
.frequency(placement.frequency)
.structures(set.structures()
.stream()
.map(entry -> new StructurePlacement.Structure(
entry.weight(),
entry.structure()
.unwrapKey()
.map(ResourceKey::location)
.map(ResourceLocation::toString)
.orElse(null),
entry.structure().tags()
.map(TagKey::location)
.map(ResourceLocation::toString)
.toList()
))
.filter(StructurePlacement.Structure::isValid)
.toList())
.build());
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(com.volmit.iris.core.nms.container.Pair::getA, com.volmit.iris.core.nms.container.Pair::getB, (a, b) -> a, KMap::new));
}
public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) {
if (!(raw instanceof PlatformChunkGenerator gen))
throw new IllegalStateException("Generator is not platform chunk generator!");

View File

@@ -8,16 +8,18 @@ import com.volmit.iris.engine.framework.ResultLocator;
import com.volmit.iris.engine.framework.WrongEngineBroException;
import com.volmit.iris.engine.object.IrisJigsawStructure;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.engine.object.IrisStructurePopulator;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.collection.KSet;
import com.volmit.iris.util.mantle.flag.MantleFlag;
import com.volmit.iris.util.math.Position2;
import com.volmit.iris.util.reflect.WrappedField;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.RegistryAccess;
import com.volmit.iris.util.reflect.WrappedReturningMethod;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.core.*;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
@@ -27,43 +29,53 @@ import net.minecraft.tags.StructureTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.MobSpawnSettings;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.chunk.status.ChunkStatus;
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.templatesystem.StructureTemplateManager;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_20_R4.CraftWorld;
import org.bukkit.craftbukkit.v1_20_R4.generator.CustomChunkGenerator;
import org.bukkit.craftbukkit.v1_20_R4.generator.structure.CraftStructure;
import org.bukkit.event.world.AsyncStructureSpawnEvent;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.function.Supplier;
public class IrisChunkGenerator extends CustomChunkGenerator {
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
private final ChunkGenerator delegate;
private final Engine engine;
private final KMap<ResourceKey<Structure>, KSet<String>> structures = new KMap<>();
private final IrisStructurePopulator populator;
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null);
this.delegate = delegate;
this.engine = engine;
this.populator = new IrisStructurePopulator(engine);
var dimension = engine.getDimension();
KSet<IrisJigsawStructure> placements = new KSet<>();
@@ -180,8 +192,59 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
@Override
public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) {
delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager);
public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager) {
if (!structureManager.shouldGenerateStructures())
return;
var chunkPos = access.getPos();
var sectionPos = SectionPos.bottomOf(access);
var registry = registryAccess.registryOrThrow(Registries.STRUCTURE);
populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> {
var loc = ResourceLocation.tryParse(key);
if (loc == null) return false;
var holder = registry.getHolder(loc).orElse(null);
if (holder == null) return false;
var structure = holder.value();
var biomes = structure.biomes();
var start = structure.generate(
registryAccess,
this,
biomeSource,
structureState.randomState(),
templateManager,
structureState.getLevelSeed(),
chunkPos,
fetchReferences(structureManager, access, sectionPos, structure),
access,
(biome) -> ignoreBiomes || biomes.contains(biome)
);
if (!start.isValid())
return false;
BoundingBox box = start.getBoundingBox();
AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent(
structureManager.level.getMinecraftWorld().getWorld(),
CraftStructure.minecraftToBukkit(structure),
new org.bukkit.util.BoundingBox(
box.minX(),
box.minY(),
box.minZ(),
box.maxX(),
box.maxY(),
box.maxZ()
), chunkPos.x, chunkPos.z);
Bukkit.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
structureManager.setStartForStructure(sectionPos, structure, start, access);
}
return true;
});
}
private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) {
StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access);
return structurestart != null ? structurestart.getReferences() : 0;
}
@Override
@@ -199,11 +262,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.fillFromNoise(executor, blender, randomstate, structuremanager, ichunkaccess);
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
}
@Override
public WeightedRandomList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition);
@@ -211,7 +269,75 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager);
applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true);
}
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false);
}
@Override
public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) {
if (!structureManager.shouldGenerateStructures())
return;
SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSection());
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().registryOrThrow(Registries.STRUCTURE);
var list = structures.stream()
.sorted(Comparator.comparingInt(s -> s.step().ordinal()))
.toList();
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);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int wX = x + blockPos.getX();
int wZ = z + blockPos.getZ();
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)));
}
}
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);
try {
level.setCurrentlyGenerating(supplier);
structureManager.startsForStructure(sectionPos, structure).forEach((start) -> 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");
category.setDetail("Description", supplier::get);
throw new ReportedException(crashReport);
}
}
Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.POST_FEATURES);
}
private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) {
ChunkPos chunkPos = ichunkaccess.getPos();
int minX = chunkPos.getMinBlockX();
int minZ = chunkPos.getMinBlockZ();
LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration();
int minY = heightAccessor.getMinBuildHeight() + 1;
int maxY = heightAccessor.getMaxBuildHeight() - 1;
return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15);
}
@Override
@@ -234,9 +360,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.getGenDepth();
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return levelheightaccessor.getMinBuildHeight() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1;
}
@Override
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate);
int block = engine.getHeight(i, j, true);
int water = engine.getHeight(i, j, false);
BlockState[] column = new BlockState[levelheightaccessor.getHeight()];
for (int k = 0; k < column.length; k++) {
if (k <= block) column[k] = Blocks.STONE.defaultBlockState();
else if (k <= water) column[k] = Blocks.WATER.defaultBlockState();
else column[k] = Blocks.AIR.defaultBlockState();
}
return new NoiseColumn(levelheightaccessor.getMinBuildHeight(), column);
}
static {
@@ -249,7 +388,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
if (biomeSource == null)
throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!");
Method setHeight = null;
for (Method method : Heightmap.class.getDeclaredMethods()) {
var types = method.getParameterTypes();
if (types.length != 3 || !Arrays.equals(types, new Class<?>[]{int.class, int.class, int.class})
|| !method.getReturnType().equals(void.class))
continue;
setHeight = method;
break;
}
if (setHeight == null)
throw new RuntimeException("Could not find setHeight method in Heightmap!");
BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName());
SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes());
}
private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) {

View File

@@ -1,11 +1,25 @@
package com.volmit.iris.core.nms.v1_20_R4;
import java.awt.Color;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.datafixers.util.Pair;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMSBinding;
import com.mojang.serialization.Lifecycle;
import com.volmit.iris.core.link.Identifier;
import com.volmit.iris.core.nms.container.AutoClosing;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.container.BlockProperty;
import com.volmit.iris.core.nms.container.StructurePlacement;
import com.volmit.iris.core.nms.datapack.DataVersion;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine;
@@ -21,6 +35,7 @@ import com.volmit.iris.util.math.Vector3d;
import com.volmit.iris.util.matter.MatterBiomeInject;
import com.volmit.iris.util.nbt.mca.NBTWorld;
import com.volmit.iris.util.nbt.mca.palette.*;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.util.nbt.tag.CompoundTag;
import com.volmit.iris.util.scheduling.J;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
@@ -59,6 +74,8 @@ import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.bukkit.*;
@@ -726,6 +743,71 @@ public class NMSBinding implements INMSBinding {
return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName);
}
@Override
public void placeStructures(Chunk chunk) {
var craft = ((CraftChunk) chunk);
var level = craft.getCraftWorld().getHandle();
var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL);
level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager());
}
@Override
public KMap<Identifier, StructurePlacement> collectStructures() {
var structureSets = registry().registryOrThrow(Registries.STRUCTURE_SET);
var structurePlacements = registry().registryOrThrow(Registries.STRUCTURE_PLACEMENT);
return structureSets.keySet()
.stream()
.map(structureSets::getHolder)
.filter(Optional::isPresent)
.map(Optional::get)
.map(holder -> {
var set = holder.value();
var placement = set.placement();
var key = holder.key().location();
StructurePlacement.StructurePlacementBuilder<?, ?> builder;
if (placement instanceof RandomSpreadStructurePlacement random) {
builder = StructurePlacement.RandomSpread.builder()
.separation(random.separation())
.spacing(random.spacing())
.spreadType(switch (random.spreadType()) {
case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR;
case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR;
});
} else if (placement instanceof ConcentricRingsStructurePlacement rings) {
builder = StructurePlacement.ConcentricRings.builder()
.distance(rings.distance())
.spread(rings.spread())
.count(rings.count());
} else {
Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type()));
return null;
}
return new com.volmit.iris.core.nms.container.Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder
.salt(placement.salt)
.frequency(placement.frequency)
.structures(set.structures()
.stream()
.map(entry -> new StructurePlacement.Structure(
entry.weight(),
entry.structure()
.unwrapKey()
.map(ResourceKey::location)
.map(ResourceLocation::toString)
.orElse(null),
entry.structure().tags()
.map(TagKey::location)
.map(ResourceLocation::toString)
.toList()
))
.filter(StructurePlacement.Structure::isValid)
.toList())
.build());
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(com.volmit.iris.core.nms.container.Pair::getA, com.volmit.iris.core.nms.container.Pair::getB, (a, b) -> a, KMap::new));
}
public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) {
if (!(raw instanceof PlatformChunkGenerator gen))
throw new IllegalStateException("Generator is not platform chunk generator!");

View File

@@ -8,16 +8,18 @@ import com.volmit.iris.engine.framework.ResultLocator;
import com.volmit.iris.engine.framework.WrongEngineBroException;
import com.volmit.iris.engine.object.IrisJigsawStructure;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.engine.object.IrisStructurePopulator;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.collection.KSet;
import com.volmit.iris.util.mantle.flag.MantleFlag;
import com.volmit.iris.util.math.Position2;
import com.volmit.iris.util.reflect.WrappedField;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.RegistryAccess;
import com.volmit.iris.util.reflect.WrappedReturningMethod;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.core.*;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
@@ -27,44 +29,54 @@ import net.minecraft.tags.StructureTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.MobSpawnSettings;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.chunk.status.ChunkStatus;
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.templatesystem.StructureTemplateManager;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_21_R1.generator.CustomChunkGenerator;
import org.bukkit.craftbukkit.v1_21_R1.generator.structure.CraftStructure;
import org.bukkit.event.world.AsyncStructureSpawnEvent;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
public class IrisChunkGenerator extends CustomChunkGenerator {
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
private final ChunkGenerator delegate;
private final Engine engine;
private final KMap<ResourceKey<Structure>, KSet<String>> structures = new KMap<>();
private final IrisStructurePopulator populator;
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null);
this.delegate = delegate;
this.engine = engine;
this.populator = new IrisStructurePopulator(engine);
var dimension = engine.getDimension();
KSet<IrisJigsawStructure> placements = new KSet<>();
@@ -181,8 +193,59 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
@Override
public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) {
delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager);
public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager) {
if (!structureManager.shouldGenerateStructures())
return;
var chunkPos = access.getPos();
var sectionPos = SectionPos.bottomOf(access);
var registry = registryAccess.registryOrThrow(Registries.STRUCTURE);
populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> {
var loc = ResourceLocation.tryParse(key);
if (loc == null) return false;
var holder = registry.getHolder(loc).orElse(null);
if (holder == null) return false;
var structure = holder.value();
var biomes = structure.biomes();
var start = structure.generate(
registryAccess,
this,
biomeSource,
structureState.randomState(),
templateManager,
structureState.getLevelSeed(),
chunkPos,
fetchReferences(structureManager, access, sectionPos, structure),
access,
(biome) -> ignoreBiomes || biomes.contains(biome)
);
if (!start.isValid())
return false;
BoundingBox box = start.getBoundingBox();
AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent(
structureManager.level.getMinecraftWorld().getWorld(),
CraftStructure.minecraftToBukkit(structure),
new org.bukkit.util.BoundingBox(
box.minX(),
box.minY(),
box.minZ(),
box.maxX(),
box.maxY(),
box.maxZ()
), chunkPos.x, chunkPos.z);
Bukkit.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
structureManager.setStartForStructure(sectionPos, structure, start, access);
}
return true;
});
}
private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) {
StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access);
return structurestart != null ? structurestart.getReferences() : 0;
}
@Override
@@ -200,11 +263,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess);
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
}
@Override
public WeightedRandomList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition);
@@ -212,7 +270,75 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager);
applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true);
}
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false);
}
@Override
public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) {
if (!structureManager.shouldGenerateStructures())
return;
SectionPos sectionPos = SectionPos.of(chunkAccess.getPos(), level.getMinSection());
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().registryOrThrow(Registries.STRUCTURE);
var list = structures.stream()
.sorted(Comparator.comparingInt(s -> s.step().ordinal()))
.toList();
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);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int wX = x + blockPos.getX();
int wZ = z + blockPos.getZ();
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)));
}
}
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);
try {
level.setCurrentlyGenerating(supplier);
structureManager.startsForStructure(sectionPos, structure).forEach((start) -> 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");
category.setDetail("Description", supplier::get);
throw new ReportedException(crashReport);
}
}
Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.FINAL_HEIGHTMAPS);
}
private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) {
ChunkPos chunkPos = ichunkaccess.getPos();
int minX = chunkPos.getMinBlockX();
int minZ = chunkPos.getMinBlockZ();
LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration();
int minY = heightAccessor.getMinBuildHeight() + 1;
int maxY = heightAccessor.getMaxBuildHeight() - 1;
return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15);
}
@Override
@@ -235,9 +361,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.getGenDepth();
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return levelheightaccessor.getMinBuildHeight() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1;
}
@Override
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate);
int block = engine.getHeight(i, j, true);
int water = engine.getHeight(i, j, false);
BlockState[] column = new BlockState[levelheightaccessor.getHeight()];
for (int k = 0; k < column.length; k++) {
if (k <= block) column[k] = Blocks.STONE.defaultBlockState();
else if (k <= water) column[k] = Blocks.WATER.defaultBlockState();
else column[k] = Blocks.AIR.defaultBlockState();
}
return new NoiseColumn(levelheightaccessor.getMinBuildHeight(), column);
}
static {
@@ -250,7 +389,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
if (biomeSource == null)
throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!");
Method setHeight = null;
for (Method method : Heightmap.class.getDeclaredMethods()) {
var types = method.getParameterTypes();
if (types.length != 3 || !Arrays.equals(types, new Class<?>[]{int.class, int.class, int.class})
|| !method.getReturnType().equals(void.class))
continue;
setHeight = method;
break;
}
if (setHeight == null)
throw new RuntimeException("Could not find setHeight method in Heightmap!");
BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName());
SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes());
}
private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) {

View File

@@ -1,12 +1,31 @@
package com.volmit.iris.core.nms.v1_21_R1;
import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Lifecycle;
import com.volmit.iris.core.link.Identifier;
import com.volmit.iris.core.nms.container.AutoClosing;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMSBinding;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.container.BlockProperty;
import com.volmit.iris.core.nms.container.StructurePlacement;
import com.volmit.iris.core.nms.datapack.DataVersion;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
@@ -63,6 +82,8 @@ import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.bukkit.*;
@@ -737,6 +758,71 @@ public class NMSBinding implements INMSBinding {
return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName);
}
@Override
public void placeStructures(Chunk chunk) {
var craft = ((CraftChunk) chunk);
var level = craft.getCraftWorld().getHandle();
var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL);
level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager());
}
@Override
public KMap<Identifier, StructurePlacement> collectStructures() {
var structureSets = registry().registryOrThrow(Registries.STRUCTURE_SET);
var structurePlacements = registry().registryOrThrow(Registries.STRUCTURE_PLACEMENT);
return structureSets.keySet()
.stream()
.map(structureSets::getHolder)
.filter(Optional::isPresent)
.map(Optional::get)
.map(holder -> {
var set = holder.value();
var placement = set.placement();
var key = holder.key().location();
StructurePlacement.StructurePlacementBuilder<?, ?> builder;
if (placement instanceof RandomSpreadStructurePlacement random) {
builder = StructurePlacement.RandomSpread.builder()
.separation(random.separation())
.spacing(random.spacing())
.spreadType(switch (random.spreadType()) {
case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR;
case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR;
});
} else if (placement instanceof ConcentricRingsStructurePlacement rings) {
builder = StructurePlacement.ConcentricRings.builder()
.distance(rings.distance())
.spread(rings.spread())
.count(rings.count());
} else {
Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type()));
return null;
}
return new com.volmit.iris.core.nms.container.Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder
.salt(placement.salt)
.frequency(placement.frequency)
.structures(set.structures()
.stream()
.map(entry -> new StructurePlacement.Structure(
entry.weight(),
entry.structure()
.unwrapKey()
.map(ResourceKey::location)
.map(ResourceLocation::toString)
.orElse(null),
entry.structure().tags()
.map(TagKey::location)
.map(ResourceLocation::toString)
.toList()
))
.filter(StructurePlacement.Structure::isValid)
.toList())
.build());
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(com.volmit.iris.core.nms.container.Pair::getA, com.volmit.iris.core.nms.container.Pair::getB, (a, b) -> a, KMap::new));
}
public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) {
if (!(raw instanceof PlatformChunkGenerator gen))
throw new IllegalStateException("Generator is not platform chunk generator!");

View File

@@ -8,16 +8,18 @@ import com.volmit.iris.engine.framework.ResultLocator;
import com.volmit.iris.engine.framework.WrongEngineBroException;
import com.volmit.iris.engine.object.IrisJigsawStructure;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.engine.object.IrisStructurePopulator;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.collection.KSet;
import com.volmit.iris.util.mantle.flag.MantleFlag;
import com.volmit.iris.util.math.Position2;
import com.volmit.iris.util.reflect.WrappedField;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.RegistryAccess;
import com.volmit.iris.util.reflect.WrappedReturningMethod;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.core.*;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
@@ -27,43 +29,54 @@ import net.minecraft.tags.StructureTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.NoiseColumn;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.MobSpawnSettings;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.chunk.status.ChunkStatus;
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.templatesystem.StructureTemplateManager;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_21_R2.CraftWorld;
import org.bukkit.craftbukkit.v1_21_R2.generator.CustomChunkGenerator;
import org.bukkit.craftbukkit.v1_21_R2.generator.structure.CraftStructure;
import org.bukkit.event.world.AsyncStructureSpawnEvent;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
public class IrisChunkGenerator extends CustomChunkGenerator {
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
private final ChunkGenerator delegate;
private final Engine engine;
private final KMap<ResourceKey<Structure>, KSet<String>> structures = new KMap<>();
private final IrisStructurePopulator populator;
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null);
this.delegate = delegate;
this.engine = engine;
this.populator = new IrisStructurePopulator(engine);
var dimension = engine.getDimension();
KSet<IrisJigsawStructure> placements = new KSet<>();
@@ -180,8 +193,59 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
@Override
public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager) {
delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager);
public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager) {
if (!structureManager.shouldGenerateStructures())
return;
var chunkPos = access.getPos();
var sectionPos = SectionPos.bottomOf(access);
var registry = registryAccess.lookupOrThrow(Registries.STRUCTURE);
populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> {
var loc = ResourceLocation.tryParse(key);
if (loc == null) return false;
var holder = registry.get(loc).orElse(null);
if (holder == null) return false;
var structure = holder.value();
var biomes = structure.biomes();
var start = structure.generate(
registryAccess,
this,
biomeSource,
structureState.randomState(),
templateManager,
structureState.getLevelSeed(),
chunkPos,
fetchReferences(structureManager, access, sectionPos, structure),
access,
(biome) -> ignoreBiomes || biomes.contains(biome)
);
if (!start.isValid())
return false;
BoundingBox box = start.getBoundingBox();
AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent(
structureManager.level.getMinecraftWorld().getWorld(),
CraftStructure.minecraftToBukkit(structure),
new org.bukkit.util.BoundingBox(
box.minX(),
box.minY(),
box.minZ(),
box.maxX(),
box.maxY(),
box.maxZ()
), chunkPos.x, chunkPos.z);
Bukkit.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
structureManager.setStartForStructure(sectionPos, structure, start, access);
}
return true;
});
}
private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) {
StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access);
return structurestart != null ? structurestart.getReferences() : 0;
}
@Override
@@ -199,11 +263,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess);
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
}
@Override
public WeightedRandomList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition);
@@ -211,7 +270,75 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager);
applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true);
}
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false);
}
@Override
public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) {
if (!structureManager.shouldGenerateStructures())
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();
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);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int wX = x + blockPos.getX();
int wZ = z + blockPos.getZ();
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)));
}
}
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);
try {
level.setCurrentlyGenerating(supplier);
structureManager.startsForStructure(sectionPos, structure).forEach((start) -> 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");
category.setDetail("Description", supplier::get);
throw new ReportedException(crashReport);
}
}
Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.FINAL_HEIGHTMAPS);
}
private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) {
ChunkPos chunkPos = ichunkaccess.getPos();
int minX = chunkPos.getMinBlockX();
int minZ = chunkPos.getMinBlockZ();
LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration();
int minY = heightAccessor.getMinY() + 1;
int maxY = heightAccessor.getMaxY();
return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15);
}
@Override
@@ -234,9 +361,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.getGenDepth();
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return levelheightaccessor.getMinY() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1;
}
@Override
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate);
int block = engine.getHeight(i, j, true);
int water = engine.getHeight(i, j, false);
BlockState[] column = new BlockState[levelheightaccessor.getHeight()];
for (int k = 0; k < column.length; k++) {
if (k <= block) column[k] = Blocks.STONE.defaultBlockState();
else if (k <= water) column[k] = Blocks.WATER.defaultBlockState();
else column[k] = Blocks.AIR.defaultBlockState();
}
return new NoiseColumn(levelheightaccessor.getMinY(), column);
}
static {
@@ -249,7 +389,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
if (biomeSource == null)
throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!");
Method setHeight = null;
for (Method method : Heightmap.class.getDeclaredMethods()) {
var types = method.getParameterTypes();
if (types.length != 3 || !Arrays.equals(types, new Class<?>[]{int.class, int.class, int.class})
|| !method.getReturnType().equals(void.class))
continue;
setHeight = method;
break;
}
if (setHeight == null)
throw new RuntimeException("Could not find setHeight method in Heightmap!");
BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName());
SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes());
}
private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) {

View File

@@ -1,9 +1,24 @@
package com.volmit.iris.core.nms.v1_21_R2;
import java.awt.Color;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.mojang.serialization.Lifecycle;
import com.volmit.iris.core.link.Identifier;
import com.volmit.iris.core.nms.container.AutoClosing;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMSBinding;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.container.Pair;
import com.volmit.iris.core.nms.container.StructurePlacement;
import com.volmit.iris.core.nms.container.BlockProperty;
import com.volmit.iris.core.nms.datapack.DataVersion;
import com.volmit.iris.engine.data.cache.AtomicCache;
@@ -21,6 +36,7 @@ import com.volmit.iris.util.matter.MatterBiomeInject;
import com.volmit.iris.util.nbt.mca.NBTWorld;
import com.volmit.iris.util.nbt.mca.palette.*;
import com.volmit.iris.util.nbt.tag.CompoundTag;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.util.scheduling.J;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.shorts.ShortList;
@@ -60,6 +76,8 @@ import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.bukkit.*;
@@ -734,6 +752,71 @@ public class NMSBinding implements INMSBinding {
return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName);
}
@Override
public void placeStructures(Chunk chunk) {
var craft = ((CraftChunk) chunk);
var level = craft.getCraftWorld().getHandle();
var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL);
level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager());
}
@Override
public KMap<Identifier, StructurePlacement> collectStructures() {
var structureSets = registry().lookupOrThrow(Registries.STRUCTURE_SET);
var structurePlacements = registry().lookupOrThrow(Registries.STRUCTURE_PLACEMENT);
return structureSets.keySet()
.stream()
.map(structureSets::get)
.filter(Optional::isPresent)
.map(Optional::get)
.map(holder -> {
var set = holder.value();
var placement = set.placement();
var key = holder.key().location();
StructurePlacement.StructurePlacementBuilder<?, ?> builder;
if (placement instanceof RandomSpreadStructurePlacement random) {
builder = StructurePlacement.RandomSpread.builder()
.separation(random.separation())
.spacing(random.spacing())
.spreadType(switch (random.spreadType()) {
case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR;
case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR;
});
} else if (placement instanceof ConcentricRingsStructurePlacement rings) {
builder = StructurePlacement.ConcentricRings.builder()
.distance(rings.distance())
.spread(rings.spread())
.count(rings.count());
} else {
Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type()));
return null;
}
return new Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder
.salt(placement.salt)
.frequency(placement.frequency)
.structures(set.structures()
.stream()
.map(entry -> new StructurePlacement.Structure(
entry.weight(),
entry.structure()
.unwrapKey()
.map(ResourceKey::location)
.map(ResourceLocation::toString)
.orElse(null),
entry.structure().tags()
.map(TagKey::location)
.map(ResourceLocation::toString)
.toList()
))
.filter(StructurePlacement.Structure::isValid)
.toList())
.build());
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(Pair::getA, Pair::getB, (a, b) -> a, KMap::new));
}
public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) {
if (!(raw instanceof PlatformChunkGenerator gen))
throw new IllegalStateException("Generator is not platform chunk generator!");

View File

@@ -8,12 +8,17 @@ import com.volmit.iris.engine.framework.ResultLocator;
import com.volmit.iris.engine.framework.WrongEngineBroException;
import com.volmit.iris.engine.object.IrisJigsawStructure;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.engine.object.IrisStructurePopulator;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.collection.KSet;
import com.volmit.iris.util.mantle.flag.MantleFlag;
import com.volmit.iris.util.math.Position2;
import com.volmit.iris.util.reflect.WrappedField;
import com.volmit.iris.util.reflect.WrappedReturningMethod;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.core.*;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
@@ -26,38 +31,49 @@ import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.*;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.levelgen.*;
import net.minecraft.world.level.levelgen.blending.Blender;
import net.minecraft.world.level.levelgen.structure.BuiltinStructures;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_21_R3.CraftWorld;
import org.bukkit.craftbukkit.v1_21_R3.generator.CustomChunkGenerator;
import org.bukkit.craftbukkit.v1_21_R3.generator.structure.CraftStructure;
import org.bukkit.event.world.AsyncStructureSpawnEvent;
import org.spigotmc.SpigotWorldConfig;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
public class IrisChunkGenerator extends CustomChunkGenerator {
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
private final ChunkGenerator delegate;
private final Engine engine;
private final KMap<ResourceKey<Structure>, KSet<String>> structures = new KMap<>();
private final IrisStructurePopulator populator;
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null);
this.delegate = delegate;
this.engine = engine;
this.populator = new IrisStructurePopulator(engine);
var dimension = engine.getDimension();
KSet<IrisJigsawStructure> placements = new KSet<>();
@@ -174,8 +190,61 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
@Override
public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager, ResourceKey<Level> resourcekey) {
delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager, resourcekey);
public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager, ResourceKey<Level> levelKey) {
if (!structureManager.shouldGenerateStructures())
return;
var chunkPos = access.getPos();
var sectionPos = SectionPos.bottomOf(access);
var registry = registryAccess.lookupOrThrow(Registries.STRUCTURE);
populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> {
var loc = ResourceLocation.tryParse(key);
if (loc == null) return false;
var holder = registry.get(loc).orElse(null);
if (holder == null) return false;
var structure = holder.value();
var biomes = structure.biomes();
var start = structure.generate(
holder,
levelKey,
registryAccess,
this,
biomeSource,
structureState.randomState(),
templateManager,
structureState.getLevelSeed(),
chunkPos,
fetchReferences(structureManager, access, sectionPos, structure),
access,
biome -> ignoreBiomes || biomes.contains(biome)
);
if (!start.isValid())
return false;
BoundingBox box = start.getBoundingBox();
AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent(
structureManager.level.getMinecraftWorld().getWorld(),
CraftStructure.minecraftToBukkit(structure),
new org.bukkit.util.BoundingBox(
box.minX(),
box.minY(),
box.minZ(),
box.maxX(),
box.maxY(),
box.maxZ()
), chunkPos.x, chunkPos.z);
Bukkit.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
structureManager.setStartForStructure(sectionPos, structure, start, access);
}
return true;
});
}
private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) {
StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access);
return structurestart != null ? structurestart.getReferences() : 0;
}
@Override
@@ -208,11 +277,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess);
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
}
@Override
public WeightedRandomList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition);
@@ -220,7 +284,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager);
applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true);
}
@Override
@@ -230,22 +294,70 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, vanilla);
addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false);
}
@Override
public int getFirstFreeHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getFirstFreeHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) {
if (!structureManager.shouldGenerateStructures())
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();
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);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int wX = x + blockPos.getX();
int wZ = z + blockPos.getZ();
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)));
}
}
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);
try {
level.setCurrentlyGenerating(supplier);
structureManager.startsForStructure(sectionPos, structure).forEach((start) -> 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");
category.setDetail("Description", supplier::get);
throw new ReportedException(crashReport);
}
}
Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.FINAL_HEIGHTMAPS);
}
@Override
public int getFirstOccupiedHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getFirstOccupiedHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
}
@Override
public void addVanillaDecorations(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
delegate.addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) {
ChunkPos chunkPos = ichunkaccess.getPos();
int minX = chunkPos.getMinBlockX();
int minZ = chunkPos.getMinBlockZ();
LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration();
int minY = heightAccessor.getMinY() + 1;
int maxY = heightAccessor.getMaxY();
return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15);
}
@Override
@@ -263,9 +375,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.getGenDepth();
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return levelheightaccessor.getMinY() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1;
}
@Override
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate);
int block = engine.getHeight(i, j, true);
int water = engine.getHeight(i, j, false);
BlockState[] column = new BlockState[levelheightaccessor.getHeight()];
for (int k = 0; k < column.length; k++) {
if (k <= block) column[k] = Blocks.STONE.defaultBlockState();
else if (k <= water) column[k] = Blocks.WATER.defaultBlockState();
else column[k] = Blocks.AIR.defaultBlockState();
}
return new NoiseColumn(levelheightaccessor.getMinY(), column);
}
@Override
@@ -294,7 +419,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
if (biomeSource == null)
throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!");
Method setHeight = null;
for (Method method : Heightmap.class.getDeclaredMethods()) {
var types = method.getParameterTypes();
if (types.length != 3 || !Arrays.equals(types, new Class<?>[]{int.class, int.class, int.class})
|| !method.getReturnType().equals(void.class))
continue;
setHeight = method;
break;
}
if (setHeight == null)
throw new RuntimeException("Could not find setHeight method in Heightmap!");
BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName());
SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes());
}
private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) {

View File

@@ -2,12 +2,17 @@ package com.volmit.iris.core.nms.v1_21_R3;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.volmit.iris.Iris;
import com.volmit.iris.core.link.Identifier;
import com.volmit.iris.core.nms.INMSBinding;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.container.Pair;
import com.volmit.iris.core.nms.container.StructurePlacement;
import com.volmit.iris.core.nms.container.BlockProperty;
import com.volmit.iris.core.nms.datapack.DataVersion;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisJigsawStructure;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
import com.volmit.iris.util.agent.Agent;
import com.volmit.iris.util.collection.KList;
@@ -61,6 +66,9 @@ import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.bukkit.*;
@@ -89,10 +97,16 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class NMSBinding implements INMSBinding {
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
@@ -179,14 +193,14 @@ public class NMSBinding implements INMSBinding {
}
@Contract(value = "null, _, _ -> null", pure = true)
private Object convertFromTag(net.minecraft.nbt.Tag tag, int depth, int maxDepth) {
private Object convertFromTag(Tag tag, int depth, int maxDepth) {
if (tag == null || depth > maxDepth) return null;
return switch (tag) {
case CollectionTag<?> collection -> {
KList<Object> list = new KList<>();
for (Object i : collection) {
if (i instanceof net.minecraft.nbt.Tag t)
if (i instanceof Tag t)
list.add(convertFromTag(t, depth + 1, maxDepth));
else list.add(i);
}
@@ -243,7 +257,7 @@ public class NMSBinding implements INMSBinding {
yield tag;
}
case List<?> list -> {
var tag = new net.minecraft.nbt.ListTag();
var tag = new ListTag();
for (var i : list) {
tag.add(convertToTag(i, depth + 1, maxDepth));
}
@@ -497,13 +511,13 @@ public class NMSBinding implements INMSBinding {
@Override
public MCAPaletteAccess createPalette() {
MCAIdMapper<BlockState> registry = registryCache.aquireNasty(() -> {
Field cf = net.minecraft.core.IdMapper.class.getDeclaredField("tToId");
Field df = net.minecraft.core.IdMapper.class.getDeclaredField("idToT");
Field bf = net.minecraft.core.IdMapper.class.getDeclaredField("nextId");
Field cf = IdMapper.class.getDeclaredField("tToId");
Field df = IdMapper.class.getDeclaredField("idToT");
Field bf = IdMapper.class.getDeclaredField("nextId");
cf.setAccessible(true);
df.setAccessible(true);
bf.setAccessible(true);
net.minecraft.core.IdMapper<BlockState> blockData = Block.BLOCK_STATE_REGISTRY;
IdMapper<BlockState> blockData = Block.BLOCK_STATE_REGISTRY;
int b = bf.getInt(blockData);
Object2IntMap<BlockState> c = (Object2IntMap<BlockState>) cf.get(blockData);
List<BlockState> d = (List<BlockState>) df.get(blockData);
@@ -601,7 +615,7 @@ public class NMSBinding implements INMSBinding {
}
@Override
public java.awt.Color getBiomeColor(Location location, BiomeColor type) {
public Color getBiomeColor(Location location, BiomeColor type) {
LevelReader reader = ((CraftWorld) location.getWorld()).getHandle();
var holder = reader.getBiome(new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()));
var biome = holder.value();
@@ -734,6 +748,71 @@ public class NMSBinding implements INMSBinding {
return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName);
}
@Override
public void placeStructures(Chunk chunk) {
var craft = ((CraftChunk) chunk);
var level = craft.getCraftWorld().getHandle();
var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL);
level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager());
}
@Override
public KMap<Identifier, StructurePlacement> collectStructures() {
var structureSets = registry().lookupOrThrow(Registries.STRUCTURE_SET);
var structurePlacements = registry().lookupOrThrow(Registries.STRUCTURE_PLACEMENT);
return structureSets.keySet()
.stream()
.map(structureSets::get)
.filter(Optional::isPresent)
.map(Optional::get)
.map(holder -> {
var set = holder.value();
var placement = set.placement();
var key = holder.key().location();
StructurePlacement.StructurePlacementBuilder<?, ?> builder;
if (placement instanceof RandomSpreadStructurePlacement random) {
builder = StructurePlacement.RandomSpread.builder()
.separation(random.separation())
.spacing(random.spacing())
.spreadType(switch (random.spreadType()) {
case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR;
case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR;
});
} else if (placement instanceof ConcentricRingsStructurePlacement rings) {
builder = StructurePlacement.ConcentricRings.builder()
.distance(rings.distance())
.spread(rings.spread())
.count(rings.count());
} else {
Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type()));
return null;
}
return new Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder
.salt(placement.salt)
.frequency(placement.frequency)
.structures(set.structures()
.stream()
.map(entry -> new StructurePlacement.Structure(
entry.weight(),
entry.structure()
.unwrapKey()
.map(ResourceKey::location)
.map(ResourceLocation::toString)
.orElse(null),
entry.structure().tags()
.map(TagKey::location)
.map(ResourceLocation::toString)
.toList()
))
.filter(StructurePlacement.Structure::isValid)
.toList())
.build());
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(Pair::getA, Pair::getB, (a,b) -> a, KMap::new));
}
public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) {
if (!(raw instanceof PlatformChunkGenerator gen))
throw new IllegalStateException("Generator is not platform chunk generator!");

View File

@@ -8,12 +8,17 @@ import com.volmit.iris.engine.framework.ResultLocator;
import com.volmit.iris.engine.framework.WrongEngineBroException;
import com.volmit.iris.engine.object.IrisJigsawStructure;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.engine.object.IrisStructurePopulator;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.collection.KSet;
import com.volmit.iris.util.mantle.flag.MantleFlag;
import com.volmit.iris.util.math.Position2;
import com.volmit.iris.util.reflect.WrappedField;
import com.volmit.iris.util.reflect.WrappedReturningMethod;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.core.*;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
@@ -26,38 +31,48 @@ import net.minecraft.util.random.WeightedList;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.*;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.chunk.status.ChunkStatus;
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.StructureSet;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_21_R4.CraftWorld;
import org.bukkit.craftbukkit.v1_21_R4.generator.CustomChunkGenerator;
import org.bukkit.craftbukkit.v1_21_R4.generator.structure.CraftStructure;
import org.bukkit.event.world.AsyncStructureSpawnEvent;
import org.spigotmc.SpigotWorldConfig;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
public class IrisChunkGenerator extends CustomChunkGenerator {
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
private final ChunkGenerator delegate;
private final Engine engine;
private final KMap<ResourceKey<Structure>, KSet<String>> structures = new KMap<>();
private final IrisStructurePopulator populator;
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null);
this.delegate = delegate;
this.engine = engine;
this.populator = new IrisStructurePopulator(engine);
var dimension = engine.getDimension();
KSet<IrisJigsawStructure> placements = new KSet<>();
@@ -174,8 +189,61 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
@Override
public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager, ResourceKey<Level> resourcekey) {
delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager, resourcekey);
public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager, ResourceKey<Level> levelKey) {
if (!structureManager.shouldGenerateStructures())
return;
var chunkPos = access.getPos();
var sectionPos = SectionPos.bottomOf(access);
var registry = registryAccess.lookupOrThrow(Registries.STRUCTURE);
populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> {
var loc = ResourceLocation.tryParse(key);
if (loc == null) return false;
var holder = registry.get(loc).orElse(null);
if (holder == null) return false;
var structure = holder.value();
var biomes = structure.biomes();
var start = structure.generate(
holder,
levelKey,
registryAccess,
this,
biomeSource,
structureState.randomState(),
templateManager,
structureState.getLevelSeed(),
chunkPos,
fetchReferences(structureManager, access, sectionPos, structure),
access,
biome -> ignoreBiomes || biomes.contains(biome)
);
if (!start.isValid())
return false;
BoundingBox box = start.getBoundingBox();
AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent(
structureManager.level.getMinecraftWorld().getWorld(),
CraftStructure.minecraftToBukkit(structure),
new org.bukkit.util.BoundingBox(
box.minX(),
box.minY(),
box.minZ(),
box.maxX(),
box.maxY(),
box.maxZ()
), chunkPos.x, chunkPos.z);
Bukkit.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
structureManager.setStartForStructure(sectionPos, structure, start, access);
}
return true;
});
}
private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) {
StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access);
return structurestart != null ? structurestart.getReferences() : 0;
}
@Override
@@ -208,11 +276,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess);
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
}
@Override
public WeightedList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition);
@@ -220,7 +283,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager);
applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true);
}
@Override
@@ -230,22 +293,70 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, vanilla);
addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false);
}
@Override
public int getFirstFreeHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getFirstFreeHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) {
if (!structureManager.shouldGenerateStructures())
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();
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);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int wX = x + blockPos.getX();
int wZ = z + blockPos.getZ();
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)));
}
}
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);
try {
level.setCurrentlyGenerating(supplier);
structureManager.startsForStructure(sectionPos, structure).forEach((start) -> 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");
category.setDetail("Description", supplier::get);
throw new ReportedException(crashReport);
}
}
Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.FINAL_HEIGHTMAPS);
}
@Override
public int getFirstOccupiedHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getFirstOccupiedHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
}
@Override
public void addVanillaDecorations(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
delegate.addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) {
ChunkPos chunkPos = ichunkaccess.getPos();
int minX = chunkPos.getMinBlockX();
int minZ = chunkPos.getMinBlockZ();
LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration();
int minY = heightAccessor.getMinY() + 1;
int maxY = heightAccessor.getMaxY();
return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15);
}
@Override
@@ -263,9 +374,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.getGenDepth();
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return levelheightaccessor.getMinY() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1;
}
@Override
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate);
int block = engine.getHeight(i, j, true);
int water = engine.getHeight(i, j, false);
BlockState[] column = new BlockState[levelheightaccessor.getHeight()];
for (int k = 0; k < column.length; k++) {
if (k <= block) column[k] = Blocks.STONE.defaultBlockState();
else if (k <= water) column[k] = Blocks.WATER.defaultBlockState();
else column[k] = Blocks.AIR.defaultBlockState();
}
return new NoiseColumn(levelheightaccessor.getMinY(), column);
}
@Override
@@ -294,7 +418,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
if (biomeSource == null)
throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!");
Method setHeight = null;
for (Method method : Heightmap.class.getDeclaredMethods()) {
var types = method.getParameterTypes();
if (types.length != 3 || !Arrays.equals(types, new Class<?>[]{int.class, int.class, int.class})
|| !method.getReturnType().equals(void.class))
continue;
setHeight = method;
break;
}
if (setHeight == null)
throw new RuntimeException("Could not find setHeight method in Heightmap!");
BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName());
SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes());
}
private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) {

View File

@@ -2,12 +2,16 @@ package com.volmit.iris.core.nms.v1_21_R4;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.volmit.iris.Iris;
import com.volmit.iris.core.link.Identifier;
import com.volmit.iris.core.nms.INMSBinding;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.container.Pair;
import com.volmit.iris.core.nms.container.StructurePlacement;
import com.volmit.iris.core.nms.container.BlockProperty;
import com.volmit.iris.core.nms.datapack.DataVersion;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
import com.volmit.iris.util.agent.Agent;
import com.volmit.iris.util.collection.KList;
@@ -61,6 +65,8 @@ import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.bukkit.*;
@@ -93,6 +99,7 @@ import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class NMSBinding implements INMSBinding {
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
@@ -734,6 +741,71 @@ public class NMSBinding implements INMSBinding {
return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName);
}
@Override
public void placeStructures(Chunk chunk) {
var craft = ((CraftChunk) chunk);
var level = craft.getCraftWorld().getHandle();
var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL);
level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager());
}
@Override
public KMap<Identifier, StructurePlacement> collectStructures() {
var structureSets = registry().lookupOrThrow(Registries.STRUCTURE_SET);
var structurePlacements = registry().lookupOrThrow(Registries.STRUCTURE_PLACEMENT);
return structureSets.keySet()
.stream()
.map(structureSets::get)
.filter(Optional::isPresent)
.map(Optional::get)
.map(holder -> {
var set = holder.value();
var placement = set.placement();
var key = holder.key().location();
StructurePlacement.StructurePlacementBuilder<?, ?> builder;
if (placement instanceof RandomSpreadStructurePlacement random) {
builder = StructurePlacement.RandomSpread.builder()
.separation(random.separation())
.spacing(random.spacing())
.spreadType(switch (random.spreadType()) {
case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR;
case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR;
});
} else if (placement instanceof ConcentricRingsStructurePlacement rings) {
builder = StructurePlacement.ConcentricRings.builder()
.distance(rings.distance())
.spread(rings.spread())
.count(rings.count());
} else {
Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type()));
return null;
}
return new Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder
.salt(placement.salt)
.frequency(placement.frequency)
.structures(set.structures()
.stream()
.map(entry -> new StructurePlacement.Structure(
entry.weight(),
entry.structure()
.unwrapKey()
.map(ResourceKey::location)
.map(ResourceLocation::toString)
.orElse(null),
entry.structure().tags()
.map(TagKey::location)
.map(ResourceLocation::toString)
.toList()
))
.filter(StructurePlacement.Structure::isValid)
.toList())
.build());
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(Pair::getA, Pair::getB, (a, b) -> a, KMap::new));
}
public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) {
if (!(raw instanceof PlatformChunkGenerator gen))
throw new IllegalStateException("Generator is not platform chunk generator!");

View File

@@ -8,12 +8,17 @@ import com.volmit.iris.engine.framework.ResultLocator;
import com.volmit.iris.engine.framework.WrongEngineBroException;
import com.volmit.iris.engine.object.IrisJigsawStructure;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.engine.object.IrisStructurePopulator;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.collection.KSet;
import com.volmit.iris.util.mantle.flag.MantleFlag;
import com.volmit.iris.util.math.Position2;
import com.volmit.iris.util.reflect.WrappedField;
import com.volmit.iris.util.reflect.WrappedReturningMethod;
import net.minecraft.CrashReport;
import net.minecraft.CrashReportCategory;
import net.minecraft.ReportedException;
import net.minecraft.core.*;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
@@ -26,38 +31,48 @@ import net.minecraft.util.random.WeightedList;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.*;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.chunk.status.ChunkStatus;
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.StructureSet;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_21_R5.CraftWorld;
import org.bukkit.craftbukkit.v1_21_R5.generator.CustomChunkGenerator;
import org.bukkit.craftbukkit.v1_21_R5.generator.structure.CraftStructure;
import org.bukkit.event.world.AsyncStructureSpawnEvent;
import org.spigotmc.SpigotWorldConfig;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
public class IrisChunkGenerator extends CustomChunkGenerator {
private static final WrappedField<ChunkGenerator, BiomeSource> BIOME_SOURCE;
private static final WrappedReturningMethod<Heightmap, Object> SET_HEIGHT;
private final ChunkGenerator delegate;
private final Engine engine;
private final KMap<ResourceKey<Structure>, KSet<String>> structures = new KMap<>();
private final IrisStructurePopulator populator;
public IrisChunkGenerator(ChunkGenerator delegate, long seed, Engine engine, World world) {
super(((CraftWorld) world).getHandle(), edit(delegate, new CustomBiomeSource(seed, engine, world)), null);
this.delegate = delegate;
this.engine = engine;
this.populator = new IrisStructurePopulator(engine);
var dimension = engine.getDimension();
KSet<IrisJigsawStructure> placements = new KSet<>();
@@ -174,8 +189,61 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
@Override
public void createStructures(RegistryAccess iregistrycustom, ChunkGeneratorStructureState chunkgeneratorstructurestate, StructureManager structuremanager, ChunkAccess ichunkaccess, StructureTemplateManager structuretemplatemanager, ResourceKey<Level> resourcekey) {
delegate.createStructures(iregistrycustom, chunkgeneratorstructurestate, structuremanager, ichunkaccess, structuretemplatemanager, resourcekey);
public void createStructures(RegistryAccess registryAccess, ChunkGeneratorStructureState structureState, StructureManager structureManager, ChunkAccess access, StructureTemplateManager templateManager, ResourceKey<Level> levelKey) {
if (!structureManager.shouldGenerateStructures())
return;
var chunkPos = access.getPos();
var sectionPos = SectionPos.bottomOf(access);
var registry = registryAccess.lookupOrThrow(Registries.STRUCTURE);
populator.populateStructures(chunkPos.x, chunkPos.z, (key, ignoreBiomes) -> {
var loc = ResourceLocation.tryParse(key);
if (loc == null) return false;
var holder = registry.get(loc).orElse(null);
if (holder == null) return false;
var structure = holder.value();
var biomes = structure.biomes();
var start = structure.generate(
holder,
levelKey,
registryAccess,
this,
biomeSource,
structureState.randomState(),
templateManager,
structureState.getLevelSeed(),
chunkPos,
fetchReferences(structureManager, access, sectionPos, structure),
access,
biome -> ignoreBiomes || biomes.contains(biome)
);
if (!start.isValid())
return false;
BoundingBox box = start.getBoundingBox();
AsyncStructureSpawnEvent event = new AsyncStructureSpawnEvent(
structureManager.level.getMinecraftWorld().getWorld(),
CraftStructure.minecraftToBukkit(structure),
new org.bukkit.util.BoundingBox(
box.minX(),
box.minY(),
box.minZ(),
box.maxX(),
box.maxY(),
box.maxZ()
), chunkPos.x, chunkPos.z);
Bukkit.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
structureManager.setStartForStructure(sectionPos, structure, start, access);
}
return true;
});
}
private static int fetchReferences(StructureManager structureManager, ChunkAccess access, SectionPos sectionPos, Structure structure) {
StructureStart structurestart = structureManager.getStartForStructure(sectionPos, structure, access);
return structurestart != null ? structurestart.getReferences() : 0;
}
@Override
@@ -208,11 +276,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.fillFromNoise(blender, randomstate, structuremanager, ichunkaccess);
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
}
@Override
public WeightedList<MobSpawnSettings.SpawnerData> getMobsAt(Holder<Biome> holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) {
return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition);
@@ -220,7 +283,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager);
applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, true);
}
@Override
@@ -230,22 +293,70 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, vanilla);
addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
delegate.applyBiomeDecoration(generatoraccessseed, ichunkaccess, structuremanager, false);
}
@Override
public int getFirstFreeHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getFirstFreeHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunkAccess, StructureManager structureManager) {
if (!structureManager.shouldGenerateStructures())
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();
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);
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int wX = x + blockPos.getX();
int wZ = z + blockPos.getZ();
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)));
}
}
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);
try {
level.setCurrentlyGenerating(supplier);
structureManager.startsForStructure(sectionPos, structure).forEach((start) -> 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");
category.setDetail("Description", supplier::get);
throw new ReportedException(crashReport);
}
}
Heightmap.primeHeightmaps(chunkAccess, ChunkStatus.FINAL_HEIGHTMAPS);
}
@Override
public int getFirstOccupiedHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getFirstOccupiedHeight(i, j, heightmap_type, levelheightaccessor, randomstate);
}
@Override
public void addVanillaDecorations(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) {
delegate.addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
private static BoundingBox getWritableArea(ChunkAccess ichunkaccess) {
ChunkPos chunkPos = ichunkaccess.getPos();
int minX = chunkPos.getMinBlockX();
int minZ = chunkPos.getMinBlockZ();
LevelHeightAccessor heightAccessor = ichunkaccess.getHeightAccessorForGeneration();
int minY = heightAccessor.getMinY() + 1;
int maxY = heightAccessor.getMaxY();
return new BoundingBox(minX, minY, minZ, minX + 15, maxY, minZ + 15);
}
@Override
@@ -263,9 +374,22 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
return delegate.getGenDepth();
}
@Override
public int getBaseHeight(int i, int j, Heightmap.Types heightmap_type, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return levelheightaccessor.getMinY() + engine.getHeight(i, j, !heightmap_type.isOpaque().test(Blocks.WATER.defaultBlockState())) + 1;
}
@Override
public NoiseColumn getBaseColumn(int i, int j, LevelHeightAccessor levelheightaccessor, RandomState randomstate) {
return delegate.getBaseColumn(i, j, levelheightaccessor, randomstate);
int block = engine.getHeight(i, j, true);
int water = engine.getHeight(i, j, false);
BlockState[] column = new BlockState[levelheightaccessor.getHeight()];
for (int k = 0; k < column.length; k++) {
if (k <= block) column[k] = Blocks.STONE.defaultBlockState();
else if (k <= water) column[k] = Blocks.WATER.defaultBlockState();
else column[k] = Blocks.AIR.defaultBlockState();
}
return new NoiseColumn(levelheightaccessor.getMinY(), column);
}
@Override
@@ -294,7 +418,21 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
}
if (biomeSource == null)
throw new RuntimeException("Could not find biomeSource field in ChunkGenerator!");
Method setHeight = null;
for (Method method : Heightmap.class.getDeclaredMethods()) {
var types = method.getParameterTypes();
if (types.length != 3 || !Arrays.equals(types, new Class<?>[]{int.class, int.class, int.class})
|| !method.getReturnType().equals(void.class))
continue;
setHeight = method;
break;
}
if (setHeight == null)
throw new RuntimeException("Could not find setHeight method in Heightmap!");
BIOME_SOURCE = new WrappedField<>(ChunkGenerator.class, biomeSource.getName());
SET_HEIGHT = new WrappedReturningMethod<>(Heightmap.class, setHeight.getName(), setHeight.getParameterTypes());
}
private static ChunkGenerator edit(ChunkGenerator generator, BiomeSource source) {

View File

@@ -2,12 +2,16 @@ package com.volmit.iris.core.nms.v1_21_R5;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.volmit.iris.Iris;
import com.volmit.iris.core.link.Identifier;
import com.volmit.iris.core.nms.INMSBinding;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.container.Pair;
import com.volmit.iris.core.nms.container.StructurePlacement;
import com.volmit.iris.core.nms.container.BlockProperty;
import com.volmit.iris.core.nms.datapack.DataVersion;
import com.volmit.iris.engine.data.cache.AtomicCache;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisJigsawStructurePlacement;
import com.volmit.iris.engine.platform.PlatformChunkGenerator;
import com.volmit.iris.util.agent.Agent;
import com.volmit.iris.util.collection.KList;
@@ -60,6 +64,8 @@ import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.PrimaryLevelData;
import org.bukkit.*;
@@ -92,6 +98,7 @@ import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
public class NMSBinding implements INMSBinding {
private final KMap<Biome, Object> baseBiomeCache = new KMap<>();
@@ -733,6 +740,71 @@ public class NMSBinding implements INMSBinding {
return new BlockProperty(property.getName(), property.getValueClass(), state.getValue(property), property.getPossibleValues(), property::getName);
}
@Override
public void placeStructures(Chunk chunk) {
var craft = ((CraftChunk) chunk);
var level = craft.getCraftWorld().getHandle();
var access = ((CraftChunk) chunk).getHandle(ChunkStatus.FULL);
level.getChunkSource().getGenerator().applyBiomeDecoration(level, access, level.structureManager());
}
@Override
public KMap<Identifier, StructurePlacement> collectStructures() {
var structureSets = registry().lookupOrThrow(Registries.STRUCTURE_SET);
var structurePlacements = registry().lookupOrThrow(Registries.STRUCTURE_PLACEMENT);
return structureSets.keySet()
.stream()
.map(structureSets::get)
.filter(Optional::isPresent)
.map(Optional::get)
.map(holder -> {
var set = holder.value();
var placement = set.placement();
var key = holder.key().location();
StructurePlacement.StructurePlacementBuilder<?, ?> builder;
if (placement instanceof RandomSpreadStructurePlacement random) {
builder = StructurePlacement.RandomSpread.builder()
.separation(random.separation())
.spacing(random.spacing())
.spreadType(switch (random.spreadType()) {
case LINEAR -> IrisJigsawStructurePlacement.SpreadType.LINEAR;
case TRIANGULAR -> IrisJigsawStructurePlacement.SpreadType.TRIANGULAR;
});
} else if (placement instanceof ConcentricRingsStructurePlacement rings) {
builder = StructurePlacement.ConcentricRings.builder()
.distance(rings.distance())
.spread(rings.spread())
.count(rings.count());
} else {
Iris.warn("Unsupported structure placement for set " + key + " with type " + structurePlacements.getKey(placement.type()));
return null;
}
return new Pair<>(new Identifier(key.getNamespace(), key.getPath()), builder
.salt(placement.salt)
.frequency(placement.frequency)
.structures(set.structures()
.stream()
.map(entry -> new StructurePlacement.Structure(
entry.weight(),
entry.structure()
.unwrapKey()
.map(ResourceKey::location)
.map(ResourceLocation::toString)
.orElse(null),
entry.structure().tags()
.map(TagKey::location)
.map(ResourceLocation::toString)
.toList()
))
.filter(StructurePlacement.Structure::isValid)
.toList())
.build());
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(Pair::getA, Pair::getB, (a, b) -> a, KMap::new));
}
public LevelStem levelStem(RegistryAccess access, ChunkGenerator raw) {
if (!(raw instanceof PlatformChunkGenerator gen))
throw new IllegalStateException("Generator is not platform chunk generator!");