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
@@ -6,6 +6,7 @@ import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.engine.object.IrisObject; import art.arcane.iris.engine.object.IrisObject;
import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisExternalDatapackReplaceTargets; 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.engine.object.TileData;
import art.arcane.iris.util.common.data.B; import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.common.math.Vector3i; import art.arcane.iris.util.common.math.Vector3i;
@@ -53,6 +54,7 @@ import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -71,6 +73,8 @@ import java.util.zip.ZipFile;
public final class ExternalDataPackPipeline { public final class ExternalDataPackPipeline {
private static final Pattern STRUCTURE_JSON_ENTRY = Pattern.compile("(?i)^data/([^/]+)/worldgen/structure/(.+)\\.json$"); 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 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 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 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$"); 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 relative = source.toPath().relativize(child.toPath()).toString().replace('\\', '/');
String normalizedRelative = normalizeRelativePath(relative); String normalizedRelative = normalizeRelativePath(relative);
if (normalizedRelative == null || !shouldProjectEntry(normalizedRelative, request)) { if (normalizedRelative == null) {
continue;
}
ProjectedEntry projectedEntry = parseProjectedEntry(normalizedRelative);
if (!shouldProjectEntry(projectedEntry, request)) {
continue; continue;
} }
@@ -590,7 +599,7 @@ public final class ExternalDataPackPipeline {
if (parent != null) { if (parent != null) {
parent.mkdirs(); parent.mkdirs();
} }
copyFile(child, output); copyProjectedFileEntry(child, output, request, projectedEntry);
copied++; copied++;
} }
} }
@@ -607,7 +616,12 @@ public final class ExternalDataPackPipeline {
for (ZipEntry zipEntry : entries) { for (ZipEntry zipEntry : entries) {
String normalizedRelative = normalizeRelativePath(zipEntry.getName()); String normalizedRelative = normalizeRelativePath(zipEntry.getName());
if (normalizedRelative == null || !shouldProjectEntry(normalizedRelative, request)) { if (normalizedRelative == null) {
continue;
}
ProjectedEntry projectedEntry = parseProjectedEntry(normalizedRelative);
if (!shouldProjectEntry(projectedEntry, request)) {
continue; continue;
} }
@@ -617,7 +631,7 @@ public final class ExternalDataPackPipeline {
parent.mkdirs(); parent.mkdirs();
} }
try (InputStream inputStream = zipFile.getInputStream(zipEntry)) { try (InputStream inputStream = zipFile.getInputStream(zipEntry)) {
writeInputStreamToFile(inputStream, output); copyProjectedStreamEntry(inputStream, output, request, projectedEntry);
} }
copied++; copied++;
} }
@@ -625,6 +639,46 @@ public final class ExternalDataPackPipeline {
return copied; 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 { private static void writeInputStreamToFile(InputStream inputStream, File output) throws IOException {
File parent = output.getParentFile(); File parent = output.getParentFile();
if (parent != null) { 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); Files.writeString(new File(managedFolder, "pack.mcmeta").toPath(), root.toString(4), StandardCharsets.UTF_8);
} }
private static boolean shouldProjectEntry(String relativePath, DatapackRequest request) { private static boolean shouldProjectEntry(ProjectedEntry entry, DatapackRequest request) {
ProjectedEntry entry = parseProjectedEntry(relativePath);
if (entry == null) { if (entry == null) {
return false; return false;
} }
@@ -674,6 +727,8 @@ public final class ExternalDataPackPipeline {
return switch (entry.type()) { return switch (entry.type()) {
case STRUCTURE -> request.structures().contains(entry.key()); case STRUCTURE -> request.structures().contains(entry.key());
case STRUCTURE_SET -> request.structureSets().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 TEMPLATE_POOL -> request.templatePools().contains(entry.key());
case PROCESSOR_LIST -> request.processorLists().contains(entry.key()); case PROCESSOR_LIST -> request.processorLists().contains(entry.key());
case BIOME_HAS_STRUCTURE_TAG -> request.biomeHasStructureTags().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); 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); matcher = TEMPLATE_POOL_JSON_ENTRY.matcher(normalized);
if (matcher.matches()) { if (matcher.matches()) {
String key = normalizeResourceKey(matcher.group(1), matcher.group(2), "worldgen/template_pool/"); String key = normalizeResourceKey(matcher.group(1), matcher.group(2), "worldgen/template_pool/");
@@ -1831,9 +1898,12 @@ public final class ExternalDataPackPipeline {
boolean replaceVanilla, boolean replaceVanilla,
Set<String> structures, Set<String> structures,
Set<String> structureSets, Set<String> structureSets,
Set<String> configuredFeatures,
Set<String> placedFeatures,
Set<String> templatePools, Set<String> templatePools,
Set<String> processorLists, Set<String> processorLists,
Set<String> biomeHasStructureTags Set<String> biomeHasStructureTags,
Map<String, Integer> structureStartHeights
) { ) {
public DatapackRequest( public DatapackRequest(
String id, String id,
@@ -1842,7 +1912,8 @@ public final class ExternalDataPackPipeline {
String requiredEnvironment, String requiredEnvironment,
boolean required, boolean required,
boolean replaceVanilla, boolean replaceVanilla,
IrisExternalDatapackReplaceTargets replaceTargets IrisExternalDatapackReplaceTargets replaceTargets,
KList<IrisExternalDatapackStructurePatch> structurePatches
) { ) {
this( this(
normalizeRequestId(id, url), normalizeRequestId(id, url),
@@ -1853,12 +1924,15 @@ public final class ExternalDataPackPipeline {
replaceVanilla, replaceVanilla,
normalizeTargets(replaceTargets == null ? null : replaceTargets.getStructures(), "worldgen/structure/"), normalizeTargets(replaceTargets == null ? null : replaceTargets.getStructures(), "worldgen/structure/"),
normalizeTargets(replaceTargets == null ? null : replaceTargets.getStructureSets(), "worldgen/structure_set/"), 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.getTemplatePools(), "worldgen/template_pool/"),
normalizeTargets(replaceTargets == null ? null : replaceTargets.getProcessorLists(), "worldgen/processor_list/"), normalizeTargets(replaceTargets == null ? null : replaceTargets.getProcessorLists(), "worldgen/processor_list/"),
normalizeTargets(replaceTargets == null ? null : replaceTargets.getBiomeHasStructureTags(), normalizeTargets(replaceTargets == null ? null : replaceTargets.getBiomeHasStructureTags(),
"tags/worldgen/biome/has_structure/", "tags/worldgen/biome/has_structure/",
"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); requiredEnvironment = normalizeEnvironment(requiredEnvironment);
structures = immutableSet(structures); structures = immutableSet(structures);
structureSets = immutableSet(structureSets); structureSets = immutableSet(structureSets);
configuredFeatures = immutableSet(configuredFeatures);
placedFeatures = immutableSet(placedFeatures);
templatePools = immutableSet(templatePools); templatePools = immutableSet(templatePools);
processorLists = immutableSet(processorLists); processorLists = immutableSet(processorLists);
biomeHasStructureTags = immutableSet(biomeHasStructureTags); biomeHasStructureTags = immutableSet(biomeHasStructureTags);
structureStartHeights = immutableMap(structureStartHeights);
} }
public String getDedupeKey() { public String getDedupeKey() {
@@ -1881,6 +1958,8 @@ public final class ExternalDataPackPipeline {
public boolean hasReplacementTargets() { public boolean hasReplacementTargets() {
return !structures.isEmpty() return !structures.isEmpty()
|| !structureSets.isEmpty() || !structureSets.isEmpty()
|| !configuredFeatures.isEmpty()
|| !placedFeatures.isEmpty()
|| !templatePools.isEmpty() || !templatePools.isEmpty()
|| !processorLists.isEmpty() || !processorLists.isEmpty()
|| !biomeHasStructureTags.isEmpty(); || !biomeHasStructureTags.isEmpty();
@@ -1903,9 +1982,12 @@ public final class ExternalDataPackPipeline {
replaceVanilla || other.replaceVanilla, replaceVanilla || other.replaceVanilla,
union(structures, other.structures), union(structures, other.structures),
union(structureSets, other.structureSets), union(structureSets, other.structureSets),
union(configuredFeatures, other.configuredFeatures),
union(placedFeatures, other.placedFeatures),
union(templatePools, other.templatePools), union(templatePools, other.templatePools),
union(processorLists, other.processorLists), 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); 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) { private static Set<String> union(Set<String> first, Set<String> second) {
LinkedHashSet<String> merged = new LinkedHashSet<>(); LinkedHashSet<String> merged = new LinkedHashSet<>();
if (first != null) { if (first != null) {
@@ -1957,6 +2047,44 @@ public final class ExternalDataPackPipeline {
} }
return merged; 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 { public static final class PipelineSummary {
@@ -2087,6 +2215,8 @@ public final class ExternalDataPackPipeline {
private enum ProjectedEntryType { private enum ProjectedEntryType {
STRUCTURE, STRUCTURE,
STRUCTURE_SET, STRUCTURE_SET,
CONFIGURED_FEATURE,
PLACED_FEATURE,
TEMPLATE_POOL, TEMPLATE_POOL,
PROCESSOR_LIST, PROCESSOR_LIST,
STRUCTURE_NBT, STRUCTURE_NBT,
@@ -214,7 +214,8 @@ public class ServerConfigurator {
environment, environment,
externalDatapack.isRequired(), externalDatapack.isRequired(),
externalDatapack.isReplaceVanilla(), externalDatapack.isReplaceVanilla(),
replaceTargets replaceTargets,
externalDatapack.getStructurePatches()
); );
String dedupeKey = request.getDedupeKey(); String dedupeKey = request.getDedupeKey();
@@ -238,8 +238,37 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
@BlockCoordinates @BlockCoordinates
default IrisBiome getCaveBiome(int x, int y, int z) { default IrisBiome getCaveBiome(int x, int y, int z) {
IrisBiome caveBiome = getCaveBiome(x, z);
IrisBiome surfaceBiome = getSurfaceBiome(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) { if (caveBiome == null) {
return surfaceBiome; return surfaceBiome;
} }
@@ -85,7 +85,7 @@ public class IrisCaveCarver3D {
public int carve(MantleWriter writer, int chunkX, int chunkZ) { public int carve(MantleWriter writer, int chunkX, int chunkZ) {
double[] fullWeights = new double[256]; double[] fullWeights = new double[256];
Arrays.fill(fullWeights, 1D); Arrays.fill(fullWeights, 1D);
return carve(writer, chunkX, chunkZ, fullWeights, 0D, 0D); return carve(writer, chunkX, chunkZ, fullWeights, 0D, 0D, null);
} }
public int carve( public int carve(
@@ -95,6 +95,18 @@ public class IrisCaveCarver3D {
double[] columnWeights, double[] columnWeights,
double minWeight, double minWeight,
double thresholdPenalty 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) { if (columnWeights == null || columnWeights.length < 256) {
double[] fullWeights = new double[256]; double[] fullWeights = new double[256];
@@ -107,6 +119,13 @@ public class IrisCaveCarver3D {
int worldHeight = writer.getMantle().getWorldHeight(); int worldHeight = writer.getMantle().getWorldHeight();
int minY = Math.max(0, (int) Math.floor(profile.getVerticalRange().getMin())); int minY = Math.max(0, (int) Math.floor(profile.getVerticalRange().getMin()));
int maxY = Math.min(worldHeight - 1, (int) Math.ceil(profile.getVerticalRange().getMax())); 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 sampleStep = Math.max(1, profile.getSampleStep());
int surfaceClearance = Math.max(0, profile.getSurfaceClearance()); int surfaceClearance = Math.max(0, profile.getSurfaceClearance());
int surfaceBreakDepth = Math.max(0, profile.getSurfaceBreakDepth()); int surfaceBreakDepth = Math.max(0, profile.getSurfaceBreakDepth());
@@ -24,12 +24,15 @@ import art.arcane.iris.engine.mantle.IrisMantleComponent;
import art.arcane.iris.engine.mantle.MantleWriter; import art.arcane.iris.engine.mantle.MantleWriter;
import art.arcane.iris.engine.object.IrisBiome; import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisCaveProfile; 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.IrisRegion;
import art.arcane.iris.engine.object.IrisRange;
import art.arcane.iris.util.project.context.ChunkContext; import art.arcane.iris.util.project.context.ChunkContext;
import art.arcane.volmlib.util.documentation.ChunkCoordinates; import art.arcane.volmlib.util.documentation.ChunkCoordinates;
import art.arcane.volmlib.util.mantle.flag.ReservedFlag; import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
@@ -55,14 +58,14 @@ public class MantleCarvingComponent extends IrisMantleComponent {
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) { public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
List<WeightedProfile> weightedProfiles = resolveWeightedProfiles(x, z); List<WeightedProfile> weightedProfiles = resolveWeightedProfiles(x, z);
for (WeightedProfile weightedProfile : weightedProfiles) { for (WeightedProfile weightedProfile : weightedProfiles) {
carveProfile(weightedProfile.profile, weightedProfile.columnWeights, writer, x, z); carveProfile(weightedProfile, writer, x, z);
} }
} }
@ChunkCoordinates @ChunkCoordinates
private void carveProfile(IrisCaveProfile profile, double[] columnWeights, MantleWriter writer, int cx, int cz) { private void carveProfile(WeightedProfile weightedProfile, MantleWriter writer, int cx, int cz) {
IrisCaveCarver3D carver = getCarver(profile); IrisCaveCarver3D carver = getCarver(weightedProfile.profile);
carver.carve(writer, cx, cz, columnWeights, MIN_WEIGHT, THRESHOLD_PENALTY); carver.carve(writer, cx, cz, weightedProfile.columnWeights, MIN_WEIGHT, THRESHOLD_PENALTY, weightedProfile.worldYRange);
} }
private List<WeightedProfile> resolveWeightedProfiles(int chunkX, int chunkZ) { private List<WeightedProfile> resolveWeightedProfiles(int chunkX, int chunkZ) {
@@ -99,10 +102,45 @@ public class MantleCarvingComponent extends IrisMantleComponent {
} }
double averageWeight = totalWeight / CHUNK_AREA; 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.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; return weightedProfiles;
} }
@@ -160,6 +198,12 @@ public class MantleCarvingComponent extends IrisMantleComponent {
return (BLEND_RADIUS + 1D) - edgeDistance; 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) { private IrisCaveProfile resolveColumnProfile(int worldX, int worldZ) {
IrisCaveProfile resolved = null; IrisCaveProfile resolved = null;
IrisCaveProfile dimensionProfile = getDimension().getCaveProfile(); IrisCaveProfile dimensionProfile = getDimension().getCaveProfile();
@@ -221,11 +265,13 @@ public class MantleCarvingComponent extends IrisMantleComponent {
private final IrisCaveProfile profile; private final IrisCaveProfile profile;
private final double[] columnWeights; private final double[] columnWeights;
private final double averageWeight; 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.profile = profile;
this.columnWeights = columnWeights; this.columnWeights = columnWeights;
this.averageWeight = averageWeight; this.averageWeight = averageWeight;
this.worldYRange = worldYRange;
} }
private double averageWeight() { private double averageWeight() {
@@ -144,6 +144,9 @@ public class IrisDimension extends IrisRegistrant {
private boolean postProcessingWalls = true; private boolean postProcessingWalls = true;
@Desc("Enable or disable all carving for this dimension") @Desc("Enable or disable all carving for this dimension")
private boolean carvingEnabled = true; 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") @Desc("Profile-driven 3D cave configuration")
private IrisCaveProfile caveProfile = new IrisCaveProfile(); private IrisCaveProfile caveProfile = new IrisCaveProfile();
@Desc("Configuration of fluid bodies such as rivers & lakes") @Desc("Configuration of fluid bodies such as rivers & lakes")
@@ -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);
}
@@ -1,6 +1,8 @@
package art.arcane.iris.engine.object; 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.iris.engine.object.annotations.Desc;
import art.arcane.volmlib.util.collection.KList;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@@ -29,4 +31,8 @@ public class IrisExternalDatapack {
@Desc("Explicit replacement targets for minecraft namespace assets") @Desc("Explicit replacement targets for minecraft namespace assets")
private IrisExternalDatapackReplaceTargets replaceTargets = new IrisExternalDatapackReplaceTargets(); 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<>();
} }
@@ -34,11 +34,21 @@ public class IrisExternalDatapackReplaceTargets {
@Desc("Biome has_structure tag ids that may be replaced when replaceVanilla is enabled") @Desc("Biome has_structure tag ids that may be replaced when replaceVanilla is enabled")
private KList<String> biomeHasStructureTags = new KList<>(); 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() { public boolean hasAnyTargets() {
return !structures.isEmpty() return !structures.isEmpty()
|| !structureSets.isEmpty() || !structureSets.isEmpty()
|| !templatePools.isEmpty() || !templatePools.isEmpty()
|| !processorLists.isEmpty() || !processorLists.isEmpty()
|| !biomeHasStructureTags.isEmpty(); || !biomeHasStructureTags.isEmpty()
|| !configuredFeatures.isEmpty()
|| !placedFeatures.isEmpty();
} }
} }
@@ -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;
}
@@ -204,13 +204,16 @@ public class CustomBiomeSource extends BiomeSource {
int blockX = x << 2; int blockX = x << 2;
int blockZ = z << 2; int blockZ = z << 2;
int blockY = y << 2; int blockY = y << 2;
int surfaceY = engine.getComplex().getHeightStream().get(blockX, blockZ).intValue(); int worldMinHeight = engine.getWorld().minHeight();
int caveSwitchY = Math.min(-8, engine.getMinHeight() + 40); int surfaceInternalY = engine.getComplex().getHeightStream().get(blockX, blockZ).intValue();
boolean deepUnderground = blockY <= caveSwitchY; int surfaceWorldY = surfaceInternalY + worldMinHeight;
boolean belowSurface = blockY <= surfaceY - 8; int caveSwitchWorldY = Math.min(-8, worldMinHeight + 40);
boolean deepUnderground = blockY <= caveSwitchWorldY;
boolean belowSurface = blockY <= surfaceWorldY - 8;
boolean underground = deepUnderground && belowSurface; boolean underground = deepUnderground && belowSurface;
int internalY = blockY - worldMinHeight;
IrisBiome irisBiome = underground IrisBiome irisBiome = underground
? engine.getCaveBiome(blockX, blockY, blockZ) ? engine.getCaveBiome(blockX, internalY, blockZ)
: engine.getComplex().getTrueBiomeStream().get(blockX, blockZ); : engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);
if (irisBiome == null && underground) { if (irisBiome == null && underground) {
irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ); irisBiome = engine.getComplex().getTrueBiomeStream().get(blockX, blockZ);