This commit is contained in:
Brian Neumann-Fopiano
2026-02-20 23:17:17 -05:00
parent 3964185a81
commit 72c891ce5b
11 changed files with 325 additions and 25 deletions

View File

@@ -6,6 +6,7 @@ import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.engine.object.IrisObject;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisExternalDatapackReplaceTargets;
import art.arcane.iris.engine.object.IrisExternalDatapackStructurePatch;
import art.arcane.iris.engine.object.TileData;
import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.common.math.Vector3i;
@@ -53,6 +54,7 @@ import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
@@ -71,6 +73,8 @@ import java.util.zip.ZipFile;
public final class ExternalDataPackPipeline {
private static final Pattern STRUCTURE_JSON_ENTRY = Pattern.compile("(?i)^data/([^/]+)/worldgen/structure/(.+)\\.json$");
private static final Pattern STRUCTURE_SET_JSON_ENTRY = Pattern.compile("(?i)^data/([^/]+)/worldgen/structure_set/(.+)\\.json$");
private static final Pattern CONFIGURED_FEATURE_JSON_ENTRY = Pattern.compile("(?i)^data/([^/]+)/worldgen/configured_feature/(.+)\\.json$");
private static final Pattern PLACED_FEATURE_JSON_ENTRY = Pattern.compile("(?i)^data/([^/]+)/worldgen/placed_feature/(.+)\\.json$");
private static final Pattern TEMPLATE_POOL_JSON_ENTRY = Pattern.compile("(?i)^data/([^/]+)/worldgen/template_pool/(.+)\\.json$");
private static final Pattern PROCESSOR_LIST_JSON_ENTRY = Pattern.compile("(?i)^data/([^/]+)/worldgen/processor_list/(.+)\\.json$");
private static final Pattern BIOME_HAS_STRUCTURE_TAG_ENTRY = Pattern.compile("(?i)^data/([^/]+)/tags/worldgen/biome/has_structure/(.+)\\.json$");
@@ -581,7 +585,12 @@ public final class ExternalDataPackPipeline {
String relative = source.toPath().relativize(child.toPath()).toString().replace('\\', '/');
String normalizedRelative = normalizeRelativePath(relative);
if (normalizedRelative == null || !shouldProjectEntry(normalizedRelative, request)) {
if (normalizedRelative == null) {
continue;
}
ProjectedEntry projectedEntry = parseProjectedEntry(normalizedRelative);
if (!shouldProjectEntry(projectedEntry, request)) {
continue;
}
@@ -590,7 +599,7 @@ public final class ExternalDataPackPipeline {
if (parent != null) {
parent.mkdirs();
}
copyFile(child, output);
copyProjectedFileEntry(child, output, request, projectedEntry);
copied++;
}
}
@@ -607,7 +616,12 @@ public final class ExternalDataPackPipeline {
for (ZipEntry zipEntry : entries) {
String normalizedRelative = normalizeRelativePath(zipEntry.getName());
if (normalizedRelative == null || !shouldProjectEntry(normalizedRelative, request)) {
if (normalizedRelative == null) {
continue;
}
ProjectedEntry projectedEntry = parseProjectedEntry(normalizedRelative);
if (!shouldProjectEntry(projectedEntry, request)) {
continue;
}
@@ -617,7 +631,7 @@ public final class ExternalDataPackPipeline {
parent.mkdirs();
}
try (InputStream inputStream = zipFile.getInputStream(zipEntry)) {
writeInputStreamToFile(inputStream, output);
copyProjectedStreamEntry(inputStream, output, request, projectedEntry);
}
copied++;
}
@@ -625,6 +639,46 @@ public final class ExternalDataPackPipeline {
return copied;
}
private static void copyProjectedFileEntry(File sourceFile, File output, DatapackRequest request, ProjectedEntry projectedEntry) throws IOException {
Integer startHeightAbsolute = getPatchedStartHeightAbsolute(projectedEntry, request);
if (startHeightAbsolute == null) {
copyFile(sourceFile, output);
return;
}
String content = Files.readString(sourceFile.toPath(), StandardCharsets.UTF_8);
String patched = applyStructureStartHeightPatch(content, startHeightAbsolute);
Files.writeString(output.toPath(), patched, StandardCharsets.UTF_8);
}
private static void copyProjectedStreamEntry(InputStream inputStream, File output, DatapackRequest request, ProjectedEntry projectedEntry) throws IOException {
Integer startHeightAbsolute = getPatchedStartHeightAbsolute(projectedEntry, request);
if (startHeightAbsolute == null) {
writeInputStreamToFile(inputStream, output);
return;
}
byte[] bytes = inputStream.readAllBytes();
String content = new String(bytes, StandardCharsets.UTF_8);
String patched = applyStructureStartHeightPatch(content, startHeightAbsolute);
Files.writeString(output.toPath(), patched, StandardCharsets.UTF_8);
}
private static Integer getPatchedStartHeightAbsolute(ProjectedEntry projectedEntry, DatapackRequest request) {
if (projectedEntry == null || request == null || projectedEntry.type() != ProjectedEntryType.STRUCTURE) {
return null;
}
return request.structureStartHeights().get(projectedEntry.key());
}
private static String applyStructureStartHeightPatch(String content, int startHeightAbsolute) {
JSONObject root = new JSONObject(content);
JSONObject startHeight = new JSONObject();
startHeight.put("absolute", startHeightAbsolute);
root.put("start_height", startHeight);
return root.toString(4);
}
private static void writeInputStreamToFile(InputStream inputStream, File output) throws IOException {
File parent = output.getParentFile();
if (parent != null) {
@@ -653,8 +707,7 @@ public final class ExternalDataPackPipeline {
Files.writeString(new File(managedFolder, "pack.mcmeta").toPath(), root.toString(4), StandardCharsets.UTF_8);
}
private static boolean shouldProjectEntry(String relativePath, DatapackRequest request) {
ProjectedEntry entry = parseProjectedEntry(relativePath);
private static boolean shouldProjectEntry(ProjectedEntry entry, DatapackRequest request) {
if (entry == null) {
return false;
}
@@ -674,6 +727,8 @@ public final class ExternalDataPackPipeline {
return switch (entry.type()) {
case STRUCTURE -> request.structures().contains(entry.key());
case STRUCTURE_SET -> request.structureSets().contains(entry.key());
case CONFIGURED_FEATURE -> request.configuredFeatures().contains(entry.key());
case PLACED_FEATURE -> request.placedFeatures().contains(entry.key());
case TEMPLATE_POOL -> request.templatePools().contains(entry.key());
case PROCESSOR_LIST -> request.processorLists().contains(entry.key());
case BIOME_HAS_STRUCTURE_TAG -> request.biomeHasStructureTags().contains(entry.key());
@@ -695,6 +750,18 @@ public final class ExternalDataPackPipeline {
return key == null ? null : new ProjectedEntry(ProjectedEntryType.STRUCTURE_SET, normalizeNamespace(matcher.group(1)), key);
}
matcher = CONFIGURED_FEATURE_JSON_ENTRY.matcher(normalized);
if (matcher.matches()) {
String key = normalizeResourceKey(matcher.group(1), matcher.group(2), "worldgen/configured_feature/");
return key == null ? null : new ProjectedEntry(ProjectedEntryType.CONFIGURED_FEATURE, normalizeNamespace(matcher.group(1)), key);
}
matcher = PLACED_FEATURE_JSON_ENTRY.matcher(normalized);
if (matcher.matches()) {
String key = normalizeResourceKey(matcher.group(1), matcher.group(2), "worldgen/placed_feature/");
return key == null ? null : new ProjectedEntry(ProjectedEntryType.PLACED_FEATURE, normalizeNamespace(matcher.group(1)), key);
}
matcher = TEMPLATE_POOL_JSON_ENTRY.matcher(normalized);
if (matcher.matches()) {
String key = normalizeResourceKey(matcher.group(1), matcher.group(2), "worldgen/template_pool/");
@@ -1831,9 +1898,12 @@ public final class ExternalDataPackPipeline {
boolean replaceVanilla,
Set<String> structures,
Set<String> structureSets,
Set<String> configuredFeatures,
Set<String> placedFeatures,
Set<String> templatePools,
Set<String> processorLists,
Set<String> biomeHasStructureTags
Set<String> biomeHasStructureTags,
Map<String, Integer> structureStartHeights
) {
public DatapackRequest(
String id,
@@ -1842,7 +1912,8 @@ public final class ExternalDataPackPipeline {
String requiredEnvironment,
boolean required,
boolean replaceVanilla,
IrisExternalDatapackReplaceTargets replaceTargets
IrisExternalDatapackReplaceTargets replaceTargets,
KList<IrisExternalDatapackStructurePatch> structurePatches
) {
this(
normalizeRequestId(id, url),
@@ -1853,12 +1924,15 @@ public final class ExternalDataPackPipeline {
replaceVanilla,
normalizeTargets(replaceTargets == null ? null : replaceTargets.getStructures(), "worldgen/structure/"),
normalizeTargets(replaceTargets == null ? null : replaceTargets.getStructureSets(), "worldgen/structure_set/"),
normalizeTargets(replaceTargets == null ? null : replaceTargets.getConfiguredFeatures(), "worldgen/configured_feature/"),
normalizeTargets(replaceTargets == null ? null : replaceTargets.getPlacedFeatures(), "worldgen/placed_feature/"),
normalizeTargets(replaceTargets == null ? null : replaceTargets.getTemplatePools(), "worldgen/template_pool/"),
normalizeTargets(replaceTargets == null ? null : replaceTargets.getProcessorLists(), "worldgen/processor_list/"),
normalizeTargets(replaceTargets == null ? null : replaceTargets.getBiomeHasStructureTags(),
"tags/worldgen/biome/has_structure/",
"worldgen/biome/has_structure/",
"has_structure/")
"has_structure/"),
normalizeStructureStartHeights(structurePatches)
);
}
@@ -1869,9 +1943,12 @@ public final class ExternalDataPackPipeline {
requiredEnvironment = normalizeEnvironment(requiredEnvironment);
structures = immutableSet(structures);
structureSets = immutableSet(structureSets);
configuredFeatures = immutableSet(configuredFeatures);
placedFeatures = immutableSet(placedFeatures);
templatePools = immutableSet(templatePools);
processorLists = immutableSet(processorLists);
biomeHasStructureTags = immutableSet(biomeHasStructureTags);
structureStartHeights = immutableMap(structureStartHeights);
}
public String getDedupeKey() {
@@ -1881,6 +1958,8 @@ public final class ExternalDataPackPipeline {
public boolean hasReplacementTargets() {
return !structures.isEmpty()
|| !structureSets.isEmpty()
|| !configuredFeatures.isEmpty()
|| !placedFeatures.isEmpty()
|| !templatePools.isEmpty()
|| !processorLists.isEmpty()
|| !biomeHasStructureTags.isEmpty();
@@ -1903,9 +1982,12 @@ public final class ExternalDataPackPipeline {
replaceVanilla || other.replaceVanilla,
union(structures, other.structures),
union(structureSets, other.structureSets),
union(configuredFeatures, other.configuredFeatures),
union(placedFeatures, other.placedFeatures),
union(templatePools, other.templatePools),
union(processorLists, other.processorLists),
union(biomeHasStructureTags, other.biomeHasStructureTags)
union(biomeHasStructureTags, other.biomeHasStructureTags),
unionStructureStartHeights(structureStartHeights, other.structureStartHeights)
);
}
@@ -1947,6 +2029,14 @@ public final class ExternalDataPackPipeline {
return Set.copyOf(copy);
}
private static Map<String, Integer> immutableMap(Map<String, Integer> values) {
LinkedHashMap<String, Integer> copy = new LinkedHashMap<>();
if (values != null) {
copy.putAll(values);
}
return Map.copyOf(copy);
}
private static Set<String> union(Set<String> first, Set<String> second) {
LinkedHashSet<String> merged = new LinkedHashSet<>();
if (first != null) {
@@ -1957,6 +2047,44 @@ public final class ExternalDataPackPipeline {
}
return merged;
}
private static Map<String, Integer> normalizeStructureStartHeights(KList<IrisExternalDatapackStructurePatch> patches) {
LinkedHashMap<String, Integer> normalized = new LinkedHashMap<>();
if (patches == null) {
return normalized;
}
for (IrisExternalDatapackStructurePatch patch : patches) {
if (patch == null || !patch.isEnabled()) {
continue;
}
String structure = patch.getStructure();
if (structure == null || structure.isBlank()) {
continue;
}
String normalizedStructure = normalizeResourceKey("minecraft", structure, "worldgen/structure/");
if (normalizedStructure == null || normalizedStructure.isBlank()) {
continue;
}
normalized.put(normalizedStructure, patch.getStartHeightAbsolute());
}
return normalized;
}
private static Map<String, Integer> unionStructureStartHeights(Map<String, Integer> first, Map<String, Integer> second) {
LinkedHashMap<String, Integer> merged = new LinkedHashMap<>();
if (first != null) {
merged.putAll(first);
}
if (second != null) {
merged.putAll(second);
}
return merged;
}
}
public static final class PipelineSummary {
@@ -2087,6 +2215,8 @@ public final class ExternalDataPackPipeline {
private enum ProjectedEntryType {
STRUCTURE,
STRUCTURE_SET,
CONFIGURED_FEATURE,
PLACED_FEATURE,
TEMPLATE_POOL,
PROCESSOR_LIST,
STRUCTURE_NBT,

View File

@@ -214,7 +214,8 @@ public class ServerConfigurator {
environment,
externalDatapack.isRequired(),
externalDatapack.isReplaceVanilla(),
replaceTargets
replaceTargets,
externalDatapack.getStructurePatches()
);
String dedupeKey = request.getDedupeKey();

View File

@@ -238,8 +238,37 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
@BlockCoordinates
default IrisBiome getCaveBiome(int x, int y, int z) {
IrisBiome caveBiome = getCaveBiome(x, z);
IrisBiome surfaceBiome = getSurfaceBiome(x, z);
IrisBiome entryBiome = null;
int worldY = y + getWorld().minHeight();
KList<IrisDimensionCarvingEntry> carvingEntries = getDimension().getCarving();
if (carvingEntries != null && !carvingEntries.isEmpty()) {
for (IrisDimensionCarvingEntry entry : carvingEntries) {
if (entry == null || !entry.isEnabled()) {
continue;
}
String key = entry.getBiome();
if (key == null || key.isBlank()) {
continue;
}
IrisRange worldYRange = entry.getWorldYRange();
if (worldYRange != null && !worldYRange.contains(worldY)) {
continue;
}
IrisBiome loadedBiome = getData().getBiomeLoader().load(key.trim());
if (loadedBiome != null) {
entryBiome = loadedBiome;
}
}
}
if (entryBiome != null) {
return entryBiome;
}
IrisBiome caveBiome = getCaveBiome(x, z);
if (caveBiome == null) {
return surfaceBiome;
}

View File

@@ -85,7 +85,7 @@ public class IrisCaveCarver3D {
public int carve(MantleWriter writer, int chunkX, int chunkZ) {
double[] fullWeights = new double[256];
Arrays.fill(fullWeights, 1D);
return carve(writer, chunkX, chunkZ, fullWeights, 0D, 0D);
return carve(writer, chunkX, chunkZ, fullWeights, 0D, 0D, null);
}
public int carve(
@@ -95,6 +95,18 @@ public class IrisCaveCarver3D {
double[] columnWeights,
double minWeight,
double thresholdPenalty
) {
return carve(writer, chunkX, chunkZ, columnWeights, minWeight, thresholdPenalty, null);
}
public int carve(
MantleWriter writer,
int chunkX,
int chunkZ,
double[] columnWeights,
double minWeight,
double thresholdPenalty,
IrisRange worldYRange
) {
if (columnWeights == null || columnWeights.length < 256) {
double[] fullWeights = new double[256];
@@ -107,6 +119,13 @@ public class IrisCaveCarver3D {
int worldHeight = writer.getMantle().getWorldHeight();
int minY = Math.max(0, (int) Math.floor(profile.getVerticalRange().getMin()));
int maxY = Math.min(worldHeight - 1, (int) Math.ceil(profile.getVerticalRange().getMax()));
if (worldYRange != null) {
int worldMinHeight = engine.getWorld().minHeight();
int rangeMinY = (int) Math.floor(worldYRange.getMin() - worldMinHeight);
int rangeMaxY = (int) Math.ceil(worldYRange.getMax() - worldMinHeight);
minY = Math.max(minY, rangeMinY);
maxY = Math.min(maxY, rangeMaxY);
}
int sampleStep = Math.max(1, profile.getSampleStep());
int surfaceClearance = Math.max(0, profile.getSurfaceClearance());
int surfaceBreakDepth = Math.max(0, profile.getSurfaceBreakDepth());

View File

@@ -24,12 +24,15 @@ import art.arcane.iris.engine.mantle.IrisMantleComponent;
import art.arcane.iris.engine.mantle.MantleWriter;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisCaveProfile;
import art.arcane.iris.engine.object.IrisDimensionCarvingEntry;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.engine.object.IrisRange;
import art.arcane.iris.util.project.context.ChunkContext;
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
@@ -55,14 +58,14 @@ public class MantleCarvingComponent extends IrisMantleComponent {
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
List<WeightedProfile> weightedProfiles = resolveWeightedProfiles(x, z);
for (WeightedProfile weightedProfile : weightedProfiles) {
carveProfile(weightedProfile.profile, weightedProfile.columnWeights, writer, x, z);
carveProfile(weightedProfile, writer, x, z);
}
}
@ChunkCoordinates
private void carveProfile(IrisCaveProfile profile, double[] columnWeights, MantleWriter writer, int cx, int cz) {
IrisCaveCarver3D carver = getCarver(profile);
carver.carve(writer, cx, cz, columnWeights, MIN_WEIGHT, THRESHOLD_PENALTY);
private void carveProfile(WeightedProfile weightedProfile, MantleWriter writer, int cx, int cz) {
IrisCaveCarver3D carver = getCarver(weightedProfile.profile);
carver.carve(writer, cx, cz, weightedProfile.columnWeights, MIN_WEIGHT, THRESHOLD_PENALTY, weightedProfile.worldYRange);
}
private List<WeightedProfile> resolveWeightedProfiles(int chunkX, int chunkZ) {
@@ -99,10 +102,45 @@ public class MantleCarvingComponent extends IrisMantleComponent {
}
double averageWeight = totalWeight / CHUNK_AREA;
weightedProfiles.add(new WeightedProfile(profile, weights, averageWeight));
weightedProfiles.add(new WeightedProfile(profile, weights, averageWeight, null));
}
weightedProfiles.sort(Comparator.comparingDouble(WeightedProfile::averageWeight));
weightedProfiles.addAll(0, resolveDimensionCarvingProfiles());
return weightedProfiles;
}
private List<WeightedProfile> resolveDimensionCarvingProfiles() {
List<WeightedProfile> weightedProfiles = new ArrayList<>();
List<IrisDimensionCarvingEntry> entries = getDimension().getCarving();
if (entries == null || entries.isEmpty()) {
return weightedProfiles;
}
for (IrisDimensionCarvingEntry entry : entries) {
if (entry == null || !entry.isEnabled()) {
continue;
}
String biomeKey = entry.getBiome();
if (biomeKey == null || biomeKey.isBlank()) {
continue;
}
IrisBiome biome = getData().getBiomeLoader().load(biomeKey.trim());
if (biome == null) {
continue;
}
IrisCaveProfile profile = biome.getCaveProfile();
if (!isProfileEnabled(profile)) {
continue;
}
IrisRange worldYRange = entry.getWorldYRange();
weightedProfiles.add(new WeightedProfile(profile, fullWeights(), -1D, worldYRange));
}
return weightedProfiles;
}
@@ -160,6 +198,12 @@ public class MantleCarvingComponent extends IrisMantleComponent {
return (BLEND_RADIUS + 1D) - edgeDistance;
}
private double[] fullWeights() {
double[] weights = new double[CHUNK_AREA];
Arrays.fill(weights, 1D);
return weights;
}
private IrisCaveProfile resolveColumnProfile(int worldX, int worldZ) {
IrisCaveProfile resolved = null;
IrisCaveProfile dimensionProfile = getDimension().getCaveProfile();
@@ -221,11 +265,13 @@ public class MantleCarvingComponent extends IrisMantleComponent {
private final IrisCaveProfile profile;
private final double[] columnWeights;
private final double averageWeight;
private final IrisRange worldYRange;
private WeightedProfile(IrisCaveProfile profile, double[] columnWeights, double averageWeight) {
private WeightedProfile(IrisCaveProfile profile, double[] columnWeights, double averageWeight, IrisRange worldYRange) {
this.profile = profile;
this.columnWeights = columnWeights;
this.averageWeight = averageWeight;
this.worldYRange = worldYRange;
}
private double averageWeight() {

View File

@@ -144,6 +144,9 @@ public class IrisDimension extends IrisRegistrant {
private boolean postProcessingWalls = true;
@Desc("Enable or disable all carving for this dimension")
private boolean carvingEnabled = true;
@ArrayType(type = IrisDimensionCarvingEntry.class, min = 1)
@Desc("Dimension-level cave biome carving overrides with absolute world Y ranges")
private KList<IrisDimensionCarvingEntry> carving = new KList<>();
@Desc("Profile-driven 3D cave configuration")
private IrisCaveProfile caveProfile = new IrisCaveProfile();
@Desc("Configuration of fluid bodies such as rivers & lakes")

View File

@@ -0,0 +1,30 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.engine.object.annotations.Desc;
import art.arcane.iris.engine.object.annotations.RegistryListResource;
import art.arcane.iris.engine.object.annotations.Snippet;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Snippet("dimension-carving-entry")
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@Desc("Dimension-level cave biome override with absolute world Y bounds.")
@Data
public class IrisDimensionCarvingEntry {
@Desc("Stable id for this carving entry")
private String id = "";
@Desc("Enable or disable this carving entry")
private boolean enabled = true;
@RegistryListResource(IrisBiome.class)
@Desc("Cave biome to apply when world Y falls within worldYRange")
private String biome = "";
@Desc("Absolute world Y bounds where this carving entry applies")
private IrisRange worldYRange = new IrisRange(-64, 320);
}

View File

@@ -1,6 +1,8 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.engine.object.annotations.ArrayType;
import art.arcane.iris.engine.object.annotations.Desc;
import art.arcane.volmlib.util.collection.KList;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -29,4 +31,8 @@ public class IrisExternalDatapack {
@Desc("Explicit replacement targets for minecraft namespace assets")
private IrisExternalDatapackReplaceTargets replaceTargets = new IrisExternalDatapackReplaceTargets();
@ArrayType(type = IrisExternalDatapackStructurePatch.class, min = 1)
@Desc("Structure placement patches applied when this external datapack is projected")
private KList<IrisExternalDatapackStructurePatch> structurePatches = new KList<>();
}

View File

@@ -34,11 +34,21 @@ public class IrisExternalDatapackReplaceTargets {
@Desc("Biome has_structure tag ids that may be replaced when replaceVanilla is enabled")
private KList<String> biomeHasStructureTags = new KList<>();
@ArrayType(type = String.class, min = 1)
@Desc("Configured feature ids that may be replaced when replaceVanilla is enabled")
private KList<String> configuredFeatures = new KList<>();
@ArrayType(type = String.class, min = 1)
@Desc("Placed feature ids that may be replaced when replaceVanilla is enabled")
private KList<String> placedFeatures = new KList<>();
public boolean hasAnyTargets() {
return !structures.isEmpty()
|| !structureSets.isEmpty()
|| !templatePools.isEmpty()
|| !processorLists.isEmpty()
|| !biomeHasStructureTags.isEmpty();
|| !biomeHasStructureTags.isEmpty()
|| !configuredFeatures.isEmpty()
|| !placedFeatures.isEmpty();
}
}

View File

@@ -0,0 +1,23 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.engine.object.annotations.Desc;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@Desc("Defines a structure-level patch override for external datapack projection")
public class IrisExternalDatapackStructurePatch {
@Desc("Structure id to patch")
private String structure = "";
@Desc("Enable or disable this patch entry")
private boolean enabled = true;
@Desc("Absolute start height override for this structure")
private int startHeightAbsolute = -27;
}

View File

@@ -204,13 +204,16 @@ public class CustomBiomeSource extends BiomeSource {
int blockX = x << 2;
int blockZ = z << 2;
int blockY = y << 2;
int surfaceY = engine.getComplex().getHeightStream().get(blockX, blockZ).intValue();
int caveSwitchY = Math.min(-8, engine.getMinHeight() + 40);
boolean deepUnderground = blockY <= caveSwitchY;
boolean belowSurface = blockY <= surfaceY - 8;
int worldMinHeight = engine.getWorld().minHeight();
int surfaceInternalY = engine.getComplex().getHeightStream().get(blockX, blockZ).intValue();
int surfaceWorldY = surfaceInternalY + worldMinHeight;
int caveSwitchWorldY = Math.min(-8, worldMinHeight + 40);
boolean deepUnderground = blockY <= caveSwitchWorldY;
boolean belowSurface = blockY <= surfaceWorldY - 8;
boolean underground = deepUnderground && belowSurface;
int internalY = blockY - worldMinHeight;
IrisBiome irisBiome = underground
? engine.getCaveBiome(blockX, blockY, blockZ)
? engine.getCaveBiome(blockX, internalY, blockZ)
: engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
if (irisBiome == null && underground) {
irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);