Patching Cleanup

This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-11 00:38:08 -04:00
1 parent bf12f30d26
commit 29a31a69e0
44 files changed
+1340 -970

No files matched your search

@@ -53,22 +53,16 @@ public record CompatFinding(CompatRegistry registry,
/** {@code <key> (<registry>): <action> <subjectType> <subjectKey> at <detail>} */
public String line() {
StringBuilder sb = new StringBuilder(96);
sb.append(key).append(" (").append(registry.label()).append("): ").append(action.label());
if (!subjectType.isEmpty()) {
sb.append(' ').append(subjectType);
}
if (!subjectKey.isEmpty()) {
sb.append(' ').append(subjectKey);
}
if (!detail.isEmpty()) {
sb.append(" at ").append(detail);
}
return sb.toString();
sb.append(key).append(" (").append(registry.label()).append("): ");
return appendSubject(sb);
}
/** The part after the key, for lines already grouped under the key. */
public String subjectLine() {
StringBuilder sb = new StringBuilder(64);
return appendSubject(new StringBuilder(64));
}
private String appendSubject(StringBuilder sb) {
sb.append(action.label());
if (!subjectType.isEmpty()) {
sb.append(' ').append(subjectType);
@@ -22,7 +22,6 @@ import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.engine.image.IrisImageMapCompiler;
import art.arcane.iris.engine.object.IrisImage;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KSet;
import art.arcane.volmlib.util.data.KCache;
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
@@ -143,7 +142,7 @@ public class ImageResourceLoader extends ResourceLoader<IrisImage> {
for (File file : listedFiles) {
if (file.isFile() && file.getName().endsWith(".png")) {
m.add(prefix + file.getName().replaceAll("\\Q.png\\E", ""));
m.add(prefix + file.getName().replace(".png", ""));
} else if (file.isDirectory()) {
getPNGFiles(file, prefix + file.getName() + "/", m, visitedDirectories);
}
@@ -164,8 +163,7 @@ public class ImageResourceLoader extends ResourceLoader<IrisImage> {
getPNGFiles(i, "", m, visitedDirectories);
}
KList<String> v = new KList<>(m);
possibleKeys = v.toArray(new String[0]);
possibleKeys = m.toArray(new String[0]);
return possibleKeys;
}
@@ -21,7 +21,6 @@ package art.arcane.iris.core.loader;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.engine.object.matter.IrisMatterObject;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KSet;
import art.arcane.volmlib.util.data.KCache;
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
@@ -84,7 +83,7 @@ public class MatterObjectResourceLoader extends ResourceLoader<IrisMatterObject>
for (File file : listedFiles) {
if (file.isFile() && file.getName().endsWith(".mat")) {
m.add(prefix + file.getName().replaceAll("\\Q.mat\\E", ""));
m.add(prefix + file.getName().replace(".mat", ""));
} else if (file.isDirectory()) {
findMatFiles(file, prefix + file.getName() + "/", m, visitedDirectories);
}
@@ -104,8 +103,7 @@ public class MatterObjectResourceLoader extends ResourceLoader<IrisMatterObject>
findMatFiles(folder, "", m, visitedDirectories);
}
KList<String> v = new KList<>(m);
possibleKeys = v.toArray(new String[0]);
possibleKeys = m.toArray(new String[0]);
return possibleKeys;
}
@@ -397,12 +397,11 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
HashSet<String> m = new HashSet<>();
for (File i : files) {
for (File j : matchAllFiles(i, (f) -> f.getName().endsWith(".json"))) {
m.add(i.toURI().relativize(j.toURI()).getPath().replaceAll("\\Q.json\\E", ""));
m.add(i.toURI().relativize(j.toURI()).getPath().replace(".json", ""));
}
}
KList<String> v = new KList<>(m);
possibleKeys = v.toArray(new String[0]);
possibleKeys = m.toArray(new String[0]);
return possibleKeys;
}
@@ -345,20 +345,7 @@ public final class StructureTransactionWriter {
if (owner != null) {
verifyRecoveryClaim(transactionRoot, token.transactionId(), owner);
}
Path journalPath = recoveryJournalPath(transactionRoot);
if (journalPath == null || !files.isRegularFile(journalPath)) {
throw new IOException("Missing prepared removal journal at " + transactionRoot);
}
StructureTransactionJournal journal;
try {
journal = StructureTransactionJournal.fromJson(readBoundedBytes(
journalPath,
MAX_STRUCTURE_STATE_BYTES,
"Prepared removal journal"
));
} catch (RuntimeException e) {
throw new IOException("Invalid prepared removal journal at " + journalPath, e);
}
StructureTransactionJournal journal = readPreparedRemovalJournal(transactionRoot);
if (!journal.transactionId().equals(token.transactionId())) {
throw new IOException("Prepared removal journal id does not match " + token.transactionId());
}
@@ -973,20 +960,7 @@ public final class StructureTransactionWriter {
}
private void verifyPreparedRemovalAuthority(Path transactionRoot, UUID transactionId) throws IOException {
Path journalPath = recoveryJournalPath(transactionRoot);
if (journalPath == null || !files.isRegularFile(journalPath)) {
throw new IOException("Missing prepared removal journal at " + transactionRoot);
}
StructureTransactionJournal journal;
try {
journal = StructureTransactionJournal.fromJson(readBoundedBytes(
journalPath,
MAX_STRUCTURE_STATE_BYTES,
"Prepared removal journal"
));
} catch (RuntimeException e) {
throw new IOException("Invalid prepared removal journal at " + journalPath, e);
}
StructureTransactionJournal journal = readPreparedRemovalJournal(transactionRoot);
if (!journal.transactionId().equals(transactionId)
|| journal.phase() != StructureTransactionJournal.Phase.PREPARED) {
throw new IOException("Prepared removal journal does not match its datapack coordinator");
@@ -1015,6 +989,22 @@ public final class StructureTransactionWriter {
}
}
private StructureTransactionJournal readPreparedRemovalJournal(Path transactionRoot) throws IOException {
Path journalPath = recoveryJournalPath(transactionRoot);
if (journalPath == null || !files.isRegularFile(journalPath)) {
throw new IOException("Missing prepared removal journal at " + transactionRoot);
}
try {
return StructureTransactionJournal.fromJson(readBoundedBytes(
journalPath,
MAX_STRUCTURE_STATE_BYTES,
"Prepared removal journal"
));
} catch (RuntimeException e) {
throw new IOException("Invalid prepared removal journal at " + journalPath, e);
}
}
private RecoveryClaim readRecoveryClaim(Path claimPath) throws IOException {
if (!files.isRegularFile(claimPath)) {
throw new IOException("Invalid external recovery claim " + claimPath);
@@ -1,6 +1,5 @@
package art.arcane.iris.engine.data.cache;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.function.Function;
@@ -35,9 +34,7 @@ public final class LazyBoundedCache<K, V> {
}
entries.put(requiredKey, computed);
if (entries.size() > maximumSize) {
Iterator<K> iterator = entries.keySet().iterator();
iterator.next();
iterator.remove();
entries.sequencedKeySet().removeFirst();
}
return computed;
}
@@ -2692,15 +2692,7 @@ public final class GenerationSemanticIndex {
}
}
}
byte[] body = bodyBytes.toByteArray();
CRC32 checksum = new CRC32();
checksum.update(body);
ByteArrayOutputStream encodedBytes = new ByteArrayOutputStream(body.length + CHECKSUM_BYTES);
encodedBytes.write(body);
try (DataOutputStream output = new DataOutputStream(encodedBytes)) {
output.writeInt((int) checksum.getValue());
}
return encodedBytes.toByteArray();
return withChecksum(bodyBytes.toByteArray());
}
private record Summary(
@@ -283,26 +283,12 @@ final class HydrologyCaveCourseFilter {
|| !compatibleSharedCandidates(firstCandidate, secondCandidate, coursesById)) {
continue;
}
LinkedHashMap<CavePosition, HydrologyCaveAction> firstActions = null;
LinkedHashMap<CavePosition, HydrologyCaveAction> secondActions = null;
for (Map.Entry<CavePosition, HydrologyCaveAction> entry : first.actions().entrySet()) {
HydrologyCaveAction secondAction = second.actions().get(entry.getKey());
if (secondAction == null || secondAction == entry.getValue()) {
continue;
}
HydrologyCaveAction sharedAction = sharedTerminalAction(entry.getValue(), secondAction);
if (firstActions == null) {
firstActions = new LinkedHashMap<>(first.actions());
secondActions = new LinkedHashMap<>(second.actions());
}
firstActions.put(entry.getKey(), sharedAction);
secondActions.put(entry.getKey(), sharedAction);
}
if (firstActions == null) {
TerminalActions actions = alignTerminalActions(first.actions(), second.actions());
if (actions == null) {
continue;
}
aligned.set(firstIndex, withActions(first, firstActions));
aligned.set(secondIndex, withActions(second, secondActions));
aligned.set(firstIndex, withActions(first, actions.first()));
aligned.set(secondIndex, withActions(second, actions.second()));
}
}
return List.copyOf(aligned);
@@ -336,26 +322,12 @@ final class HydrologyCaveCourseFilter {
&& !sharesTerminalGrotto(firstCourse, secondCourse, first)) {
continue;
}
LinkedHashMap<CavePosition, HydrologyCaveAction> firstActions = null;
LinkedHashMap<CavePosition, HydrologyCaveAction> secondActions = null;
for (Map.Entry<CavePosition, HydrologyCaveAction> entry : first.actions().entrySet()) {
HydrologyCaveAction secondAction = second.actions().get(entry.getKey());
if (secondAction == null || secondAction == entry.getValue()) {
continue;
}
HydrologyCaveAction sharedAction = sharedTerminalAction(entry.getValue(), secondAction);
if (firstActions == null) {
firstActions = new LinkedHashMap<>(first.actions());
secondActions = new LinkedHashMap<>(second.actions());
}
firstActions.put(entry.getKey(), sharedAction);
secondActions.put(entry.getKey(), sharedAction);
}
if (firstActions == null) {
TerminalActions actions = alignTerminalActions(first.actions(), second.actions());
if (actions == null) {
continue;
}
HydrologyCaveCandidate alignedFirst = withActions(first, firstActions);
HydrologyCaveCandidate alignedSecond = withActions(second, secondActions);
HydrologyCaveCandidate alignedFirst = withActions(first, actions.first());
HydrologyCaveCandidate alignedSecond = withActions(second, actions.second());
replaceExposureCandidate(exposureValidatedCandidates, first, alignedFirst);
replaceExposureCandidate(exposureValidatedCandidates, second, alignedSecond);
candidates.set(firstIndex, alignedFirst);
@@ -364,6 +336,28 @@ final class HydrologyCaveCourseFilter {
}
}
private TerminalActions alignTerminalActions(
Map<CavePosition, HydrologyCaveAction> first,
Map<CavePosition, HydrologyCaveAction> second
) {
LinkedHashMap<CavePosition, HydrologyCaveAction> firstActions = null;
LinkedHashMap<CavePosition, HydrologyCaveAction> secondActions = null;
for (Map.Entry<CavePosition, HydrologyCaveAction> entry : first.entrySet()) {
HydrologyCaveAction secondAction = second.get(entry.getKey());
if (secondAction == null || secondAction == entry.getValue()) {
continue;
}
HydrologyCaveAction sharedAction = sharedTerminalAction(entry.getValue(), secondAction);
if (firstActions == null) {
firstActions = new LinkedHashMap<>(first);
secondActions = new LinkedHashMap<>(second);
}
firstActions.put(entry.getKey(), sharedAction);
secondActions.put(entry.getKey(), sharedAction);
}
return firstActions == null ? null : new TerminalActions(firstActions, secondActions);
}
HydrologyCaveAction sharedTerminalAction(
HydrologyCaveAction first,
HydrologyCaveAction second
@@ -1251,6 +1245,12 @@ final class HydrologyCaveCourseFilter {
return List.copyOf(openings);
}
private record TerminalActions(
LinkedHashMap<CavePosition, HydrologyCaveAction> first,
LinkedHashMap<CavePosition, HydrologyCaveAction> second
) {
}
private record GeneratedChannelView(CaveVoxelView terrain) implements CaveVoxelView {
@Override
public boolean isInWorld(CavePosition position) {
@@ -1,5 +1,7 @@
package art.arcane.iris.engine.hydrology;
import art.arcane.iris.engine.hydrology.surface.SurfaceNoise;
final class HydrologyChannelGeometry {
private final HydrologyFootprintCompiler compiler;
@@ -149,7 +151,7 @@ final class HydrologyChannelGeometry {
if (maximum == 0) {
return 0;
}
double sampled = organicNoise(seed, x, z, 12);
double sampled = SurfaceNoise.value(seed, x, z, 12);
return (int) StrictMath.round(sampled * maximum);
}
@@ -222,13 +224,13 @@ final class HydrologyChannelGeometry {
)) * StrictMath.PI * 2D;
double firstLobe = 0.5D + 0.5D * StrictMath.sin(angle * 3D + firstPhase);
double secondLobe = 0.5D + 0.5D * StrictMath.sin(angle * 5D + secondPhase);
double coherent = signedOrganicNoise(
double coherent = SurfaceNoise.signed(
segment.courseId(),
worldX,
worldZ,
channelShape.roughnessWavelength()
);
double detail = signedOrganicNoise(
double detail = SurfaceNoise.signed(
HydrologyHash.mix(segment.courseId(), ORGANIC_SHAPE_SECOND_PHASE_SALT),
worldX,
worldZ,
@@ -262,14 +264,14 @@ final class HydrologyChannelGeometry {
}
double signedCross = (deltaX * -flowZ + deltaZ * flowX) / flowLength;
HydrologyPlannerSettings.ChannelShape channelShape = compiler.settings.geometry().surface();
double thalweg = signedOrganicNoise(
double thalweg = SurfaceNoise.signed(
HydrologyHash.mix(segment.courseId(), ORGANIC_BED_VARIATION_SALT),
worldX,
worldZ,
channelShape.roughnessWavelength()
) * shape.channelRadius() * channelShape.wallRoughness();
long bankSeed = HydrologyHash.mix(segment.courseId(), ORGANIC_SHAPE_SECOND_PHASE_SALT);
double bankNoise = signedOrganicNoise(
double bankNoise = SurfaceNoise.signed(
bankSeed,
worldX,
worldZ,
@@ -297,10 +299,6 @@ final class HydrologyChannelGeometry {
return compiler.settings.geometry().surface();
}
double signedOrganicNoise(long seed, int x, int z, int scale) {
return organicNoise(seed, x, z, scale) * 2D - 1D;
}
double deepPoolDistance(
FootprintLayerShape shape,
HydraulicSegment segment,
@@ -331,32 +329,4 @@ final class HydrologyChannelGeometry {
);
return normalizedDistance * radius / radialScale;
}
double organicNoise(long seed, int x, int z, int scale) {
int cellX = Math.floorDiv(x, scale);
int cellZ = Math.floorDiv(z, scale);
double localX = Math.floorMod(x, scale) / (double) scale;
double localZ = Math.floorMod(z, scale) / (double) scale;
double smoothX = localX * localX * (3D - 2D * localX);
double smoothZ = localZ * localZ * (3D - 2D * localZ);
double top = interpolate(
organicCorner(seed, cellX, cellZ),
organicCorner(seed, cellX + 1, cellZ),
smoothX
);
double bottom = interpolate(
organicCorner(seed, cellX, cellZ + 1),
organicCorner(seed, cellX + 1, cellZ + 1),
smoothX
);
return interpolate(top, bottom, smoothZ);
}
double organicCorner(long seed, int cellX, int cellZ) {
return HydrologyHash.unit(HydrologyHash.mix(seed, cellX, cellZ));
}
double interpolate(double first, double second, double progress) {
return first + (second - first) * progress;
}
}
@@ -4,6 +4,7 @@ import art.arcane.iris.engine.hydrology.surface.SurfaceFootprint;
import art.arcane.iris.engine.hydrology.surface.SurfaceBounds;
import art.arcane.iris.engine.hydrology.surface.SurfaceFootprintCompiler;
import art.arcane.iris.engine.hydrology.surface.SurfaceLayerColumn;
import art.arcane.iris.engine.hydrology.surface.SurfaceNoise;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
@@ -763,7 +764,7 @@ final class HydrologyFootprintCompiler {
(normalized - thalwegFraction) / (1D - thalwegFraction)
);
int maximumDepth = Math.max(1, shape.fluidHead() - shape.bed());
double roughness = channelGeometry.signedOrganicNoise(
double roughness = SurfaceNoise.signed(
HydrologyHash.mix(segment.id(), HydrologyChannelGeometry.ORGANIC_BED_VARIATION_SALT),
worldX,
worldZ,
@@ -802,7 +803,7 @@ final class HydrologyFootprintCompiler {
return shape.bed();
}
int lowerExtent = shape.fluidHead() - shape.bed();
double roughness = channelGeometry.signedOrganicNoise(
double roughness = SurfaceNoise.signed(
HydrologyHash.mix(segment.id(), HydrologyChannelGeometry.ORGANIC_BED_VARIATION_SALT),
worldX,
worldZ,
@@ -833,7 +834,7 @@ final class HydrologyFootprintCompiler {
int upperExtent = shape.ceiling() - shape.fluidHead();
double extent = upperExtent * scale;
if (channelShape.ceilingRoughness() > 0D) {
double roughness = channelGeometry.signedOrganicNoise(
double roughness = SurfaceNoise.signed(
HydrologyHash.mix(segment.id(), HydrologyChannelGeometry.ORGANIC_CEILING_VARIATION_SALT),
worldX,
worldZ,
@@ -1,8 +1,5 @@
package art.arcane.iris.engine.hydrology.runtime;
import art.arcane.iris.engine.hydrology.policy.SurfaceRiverPolicy;
import art.arcane.iris.engine.hydrology.HydrologyHash;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.loader.ResourceLoader;
import art.arcane.iris.engine.hydrology.HydrologyColumnLayer;
@@ -21,64 +18,39 @@ import art.arcane.iris.engine.hydrology.HydrologyTerrainSample;
import art.arcane.iris.engine.hydrology.HydrologyTile;
import art.arcane.iris.engine.hydrology.HydrologyTileCache;
import art.arcane.iris.engine.hydrology.HydrologyTileKey;
import art.arcane.iris.engine.hydrology.cave.CavePosition;
import art.arcane.iris.engine.hydrology.cave.HydrologyCavePlan;
import art.arcane.iris.engine.hydrology.policy.EffectiveRiverPolicy;
import art.arcane.iris.engine.hydrology.policy.RiverConfinement;
import art.arcane.iris.engine.hydrology.policy.RiverPolicyResolver;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisCoastalRiverGrottoConfig;
import art.arcane.iris.engine.object.IrisDeepFluidConfig;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisHydrology;
import art.arcane.iris.engine.object.IrisInlandRiverGrottoConfig;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.engine.object.IrisRiverChannelShapeConfig;
import art.arcane.iris.engine.object.IrisRiverDropShapeConfig;
import art.arcane.iris.engine.object.IrisRiverGeometryConfig;
import art.arcane.iris.engine.object.IrisRiverGrottoConfig;
import art.arcane.iris.engine.object.IrisRiverHydrology;
import art.arcane.iris.engine.object.IrisRiverInlandOutlet;
import art.arcane.iris.engine.object.IrisRiverMeanderConfig;
import art.arcane.iris.engine.object.IrisRiverMouthConfig;
import art.arcane.iris.engine.object.IrisRiverPolicy;
import art.arcane.iris.engine.object.IrisRiverProfile;
import art.arcane.iris.engine.object.IrisRiverRoutingConfig;
import art.arcane.iris.engine.object.IrisRiverRoutingMode;
import art.arcane.iris.engine.object.IrisSeaCaveConfig;
import art.arcane.iris.engine.object.IrisStyledRange;
import art.arcane.iris.engine.object.IrisSurfacePoolConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverBankConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverChannelConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverErosionConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverFlowConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverPondConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverShapeConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverSourceConfig;
import art.arcane.iris.engine.object.IrisUndergroundRiverConfig;
import art.arcane.iris.engine.object.IrisUndergroundRiverSourceConfig;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.util.common.data.DataProvider;
import art.arcane.iris.util.common.parallel.MultiBurst;
import art.arcane.iris.util.project.stream.ProceduralStream;
import art.arcane.volmlib.util.math.RNG;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.nio.file.Path;
import java.util.function.IntConsumer;
import java.util.function.Predicate;
@@ -90,7 +62,6 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
private static final int BIOME_PATCH_SCALE = 32;
private static final long SURFACE_WIDTH_SALT = 0x5355524657494454L;
private static final long SURFACE_DEPTH_SALT = 0x5355524644455054L;
private static final long SURFACE_BLEND_SALT = 0x53555246424c454eL;
private static final long UNDERGROUND_LEVEL_SALT = 0x554e4445524c564cL;
private static final long UNDERGROUND_WIDTH_SALT = 0x554e444552574944L;
private static final long UNDERGROUND_DEPTH_SALT = 0x554e444552444550L;
@@ -121,7 +92,7 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
this.defaultProfileKey = profileKeys.isEmpty() ? "default" : profileKeys.iterator().next();
this.terrainSampleLock = new Object();
this.terrainSamples = new LinkedHashMap<>(MAXIMUM_TERRAIN_SAMPLES, 0.75F, true);
this.settings = createSettings(context.dimension(), hydrology, context::data);
this.settings = IrisHydrologySettingsCompiler.compile(context.dimension(), hydrology, context::data);
HydrologyGeometrySampler geometrySampler = geometrySampler(context, hydrology);
IrisHydrologyRoutingTerrainSampler.Sources terrainSources = new IrisHydrologyRoutingTerrainSampler.Sources(
this::createTerrainBasis,
@@ -851,463 +822,10 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
return range.stream(new RNG(context.seed() ^ salt), context.data());
}
static HydrologyPlannerSettings createSettings(
IrisDimension dimension,
IrisHydrology hydrology,
DataProvider data
) {
IrisRiverHydrology rivers = hydrology.getRivers();
IrisRiverRoutingConfig routing = rivers.getRouting();
IrisSurfaceRiverConfig surface = rivers.getSurface();
IrisSurfaceRiverSourceConfig surfaceSources = surface.getSources();
IrisSurfaceRiverChannelConfig channel = surface.getChannel();
IrisSurfaceRiverBankConfig banks = surface.getBanks();
IrisSurfaceRiverErosionConfig erosion = surface.getErosion();
IrisSurfaceRiverFlowConfig flow = surface.getFlow();
IrisRiverMouthConfig mouths = surface.getMouths();
IrisUndergroundRiverConfig underground = rivers.getUnderground();
IrisUndergroundRiverSourceConfig undergroundSources = underground.getSources();
int routeNodes = maximumRouteNodes(routing);
int minimumWorldY = dimension.getMinHeight();
boolean riversEnabled = rivers.isEnabled();
boolean surfaceEnabled = riversEnabled && surface.isEnabled();
boolean undergroundEnabled = riversEnabled && underground.isEnabled();
HydrologyPlannerSettings.Routing plannerRouting = new HydrologyPlannerSettings.Routing(
routing.getTileSize(),
routing.getSampleSpacing(),
routeNodes,
routing.getMaximumRouteLength(),
routing.getMinimumSurfaceCourseLength(),
routing.getMinimumUndergroundCourseLength(),
routing.getValleyPreference(),
routing.getUphillPenalty(),
routing.getSlopePenalty(),
routing.getConfluenceAttraction(),
routing.getLengthPreference(),
routing.getTributaries(),
new HydrologyPlannerSettings.Regional(
routing.getRegional().isEnabled(),
routing.getRegional().getSampleSpacing(),
routing.getRegional().getMinimumLength(),
routing.getRegional().getMaximumTrunks(),
routing.getRegional().getMaximumCachedBasins(),
routing.getRegional().getMaximumCachedStations(),
routing.getRegional().isCoastalChannels(),
routing.getRegional().getCoastalChannelChance(),
routing.getRegional().getMaximumCoastalIncision()
)
);
HydrologyPlannerSettings.Source plannerSurfaceSources = new HydrologyPlannerSettings.Source(
surfaceEnabled,
surfaceSources.getDensity(),
surfaceSources.getMinimumElevation() - minimumWorldY,
surfaceSources.getMinimumPerTile(),
maximumSources(surfaceSources.getDensity(), surfaceSources.getMinimumPerTile()),
surfaceSources.getMinimumSpacing()
);
HydrologyPlannerSettings.Surface plannerSurface = new HydrologyPlannerSettings.Surface(
surfaceEnabled,
plannerSurfaceSources,
minimumInt(channel.getWidth()),
maximumInt(channel.getWidth()),
minimumInt(channel.getDepth()),
maximumInt(channel.getDepth()),
channel.getMaximumIncision(),
banks.getShoreWidth(),
new HydrologyPlannerSettings.Banks(
channel.getSink(),
banks.getBlendSlope(),
banks.getMinimumBlendWidth(),
banks.getMaximumBlendWidth(),
channel.getRoughness(),
channel.getRoughnessWavelength(),
flow.getCascadeRun(),
flow.getWaterfallMinimumDrop(),
mouths.getFlareRatio(),
new HydrologyPlannerSettings.Inlet(
mouths.getInletLength(),
mouths.getInletDepth(),
mouths.getMaximumIncision(),
mouths.getInletCourseFraction(),
mouths.getInletRampSlope()
),
channel.getSpringWidthRatio(),
channel.getSpringLength(),
banks.isExposeCutStrata(),
new HydrologyPlannerSettings.Erosion(
erosion.isEnabled(),
erosion.getSmoothingRadius(),
erosion.getThalwegFraction(),
erosion.getBlendCurve(),
erosion.getBedNoise(),
erosion.getStyle(),
erosion.getTerraceSteps(),
erosion.getCliffFraction(),
erosion.getBedProfile(),
banks.getShoreRise(),
banks.getBlendBaseWidth(),
new HydrologyPlannerSettings.Excavation(
banks.getExcavation().getMaximumDepth(),
banks.getExcavation().getMaximumWidth(),
banks.getExcavation().getMaximumVolumePerBlock()
)
),
new HydrologyPlannerSettings.Ponds(
pond(surface.getPonds().getSource()),
pond(surface.getPonds().getTerminal())
),
new HydrologyPlannerSettings.Channel(
channel.getSmoothingRadius(),
channel.getOutlineMinimumRatio(),
channel.getOutlineMaximumRatio(),
channel.getSpringExtraDepth()
),
new HydrologyPlannerSettings.Flow(
flow.getWaterfallThalwegFraction(),
flow.getPlungeBasinMinimumDrop(),
flow.getPlungeBasinLengthRatio(),
flow.getPlungeBasinDepth()
)
)
);
HydrologyPlannerSettings.Hydraulics plannerHydraulics =
new HydrologyPlannerSettings.Hydraulics(flow.getWaterfallMinimumDrop());
HydrologyPlannerSettings.Source plannerUndergroundSources = new HydrologyPlannerSettings.Source(
undergroundEnabled,
undergroundSources.getDensity(),
Integer.MIN_VALUE,
undergroundSources.getMinimumPerTile(),
maximumSources(undergroundSources.getDensity(), undergroundSources.getMinimumPerTile()),
undergroundSources.getMinimumSpacing()
);
HydrologyPlannerSettings.Underground plannerUnderground = new HydrologyPlannerSettings.Underground(
undergroundEnabled,
plannerUndergroundSources,
minimumInt(underground.getFluidLevel()) - minimumWorldY,
maximumInt(underground.getFluidLevel()) - minimumWorldY,
minimumInt(underground.getChannelWidth()),
maximumInt(underground.getChannelWidth()),
minimumInt(underground.getDepth()),
maximumInt(underground.getDepth()),
minimumInt(underground.getHeadroom()),
maximumInt(underground.getHeadroom()),
underground.isConnectToExistingCaves(),
underground.getTributaries(),
underground.getMinimumRockCover(),
underground.getMinimumFloorCover(),
underground.getWideningSources()
);
IrisRiverGrottoConfig grottos = rivers.getGrottos();
IrisCoastalRiverGrottoConfig coastal = grottos.getCoastal();
IrisInlandRiverGrottoConfig inland = grottos.getInland();
boolean inlandEnabled = inland.isEnabled()
&& routing.getInlandOutlets().contains(IrisRiverInlandOutlet.SINKHOLE_GROTTO);
HydrologyPlannerSettings.Outlets plannerOutlets = new HydrologyPlannerSettings.Outlets(
routing.isOceanOutlets(),
grotto(coastal.isEnabled(), coastal.getHorizontalRadius(), coastal.getVerticalRadius(),
coastal.getHeadroom(), coastal.getMaximumVolume()),
grotto(inlandEnabled, inland.getHorizontalRadius(), inland.getVerticalRadius(),
inland.getHeadroom(), inland.getMaximumVolume()),
inlandEnabled && inland.isConnectSurfaceRivers(),
coastal.getCliffMinimumHeight() == null
? Math.max(4, coastal.getVerticalRadius())
: coastal.getCliffMinimumHeight(),
underground.getMouthLevelingDistance(),
mouths.getMaximumOceanApron(),
routing.getMaximumOutletsPerTile(),
routing.getMaximumCoastalOutletsPerTile(),
coastal.getCliffSlopeFactor()
);
HydrologyPlannerSettings.Geometry plannerGeometry = geometry(
rivers.getGeometry(),
surfaceShape(rivers.getGeometry().getSurface(), channel)
);
return new HydrologyPlannerSettings(
dimension.getFluidHeight(),
plannerRouting,
plannerSurface,
plannerHydraulics,
plannerUnderground,
plannerOutlets,
plannerGeometry,
deepFluids(hydrology.getDeepFluids(), minimumWorldY, routing, plannerRouting.refinementSpacing()),
surfacePools(hydrology.getSurfacePools()),
widestShoreBiomeWidth(dimension, data, banks.getShoreWidth()),
seaCaves(coastal),
surfacePolicyBounds(dimension, data)
);
}
static HydrologyPlannerSettings.SurfacePolicyBounds surfacePolicyBounds(IrisDimension dimension, DataProvider data) {
TreeMap<String, SurfaceRiverPolicy> policies = new TreeMap<>();
addSurfacePolicy(policies, "dimension", dimension.getRiverPolicy());
for (IrisRegion region : dimension.getAllRegions(data)) {
addSurfacePolicy(policies, "region:" + region.getLoadKey(), region.getRiverPolicy());
}
for (IrisBiome biome : dimension.getAllBiomes(data)) {
addSurfacePolicy(policies, "biome:" + biome.getLoadKey(), biome.getRiverPolicy());
}
long fingerprint = 0L;
int maximumSpacing = 0;
for (SurfaceRiverPolicy policy : policies.values()) {
fingerprint = HydrologyHash.mix(fingerprint, policy.hashCode(), HydrologyHash.text(policy.areaKey()));
maximumSpacing = Math.max(maximumSpacing, policy.sourceSpacing(0));
}
return new HydrologyPlannerSettings.SurfacePolicyBounds(fingerprint, maximumSpacing);
}
private static void addSurfacePolicy(Map<String, SurfaceRiverPolicy> policies, String key, IrisRiverPolicy policy) {
if (policy == null) {
return;
}
SurfaceRiverPolicy surface = policy.surfacePolicy(key);
if (surface.configured()) {
policies.put(key, surface);
}
}
/**
* The widest shore band any policy of the dimension asks for, whether as a shore biome band or as a
* geometric shore width, never below the dimension's geometric shore width, so the publication
* envelope reaches every column a river can give shore content to.
*/
static double widestShoreBiomeWidth(IrisDimension dimension, DataProvider data, double shoreWidth) {
double widest = Math.max(0D, shoreWidth);
widest = Math.max(widest, policyShoreReach(dimension.getRiverPolicy()));
for (IrisRegion region : dimension.getAllRegions(data)) {
widest = Math.max(widest, policyShoreReach(region.getRiverPolicy()));
}
for (IrisBiome biome : dimension.getAllBiomes(data)) {
widest = Math.max(widest, policyShoreReach(biome.getRiverPolicy()));
}
return widest;
}
private static double policyShoreReach(IrisRiverPolicy policy) {
if (policy == null) {
return 0D;
}
double shoreBiomeWidth = policy.getShoreBiomeWidth() == null ? 0D : policy.getShoreBiomeWidth();
double shoreWidth = policy.getShoreWidth() == null ? 0D : policy.getShoreWidth();
return Math.max(shoreBiomeWidth, shoreWidth);
}
private static List<HydrologyPlannerSettings.SurfacePool> surfacePools(List<IrisSurfacePoolConfig> configurations) {
ArrayList<HydrologyPlannerSettings.SurfacePool> pools = new ArrayList<>();
for (IrisSurfacePoolConfig configuration : configurations) {
pools.add(new HydrologyPlannerSettings.SurfacePool(
configuration.getId(),
configuration.getDensity() > 0D,
configuration.getDensity(),
configuration.getSpacing(),
configuration.getMinimumRadius(),
configuration.getMaximumRadius(),
configuration.getDepth(),
Math.min(64, maximumSources(configuration.getDensity(), 0)),
configuration.getBiome()
));
}
return List.copyOf(pools);
}
private static HydrologyPlannerSettings.Geometry geometry(
IrisRiverGeometryConfig geometry,
HydrologyPlannerSettings.ChannelShape surfaceShape
) {
IrisRiverMeanderConfig meanders = geometry.getMeanders();
IrisRiverDropShapeConfig drops = geometry.getDrops();
return new HydrologyPlannerSettings.Geometry(
new HydrologyPlannerSettings.Meanders(
meanders.getPrimaryWavelength(),
meanders.getDetailWavelength(),
meanders.getPrimaryStrength(),
meanders.getDetailStrength(),
meanders.getMaximumOffsetRatio(),
meanders.getSmoothingPasses(),
meanders.getMaximumTurnDegrees()
),
surfaceShape,
channelShape(geometry.getUnderground()),
channelShape(geometry.getGrottos()),
new HydrologyPlannerSettings.Drops(
drops.getCascadeRunPerBlock(),
drops.getCascadeExponent(),
drops.getMaximumCascadeStep(),
drops.getFlowWidthRatio(),
drops.getMaximumFlowDepth(),
drops.getBasinWidthRatio(),
drops.getMaximumBasinDepth(),
drops.getUndergroundCascadeRunPerBlock()
)
);
}
private static HydrologyPlannerSettings.ChannelShape channelShape(IrisRiverChannelShapeConfig shape) {
return new HydrologyPlannerSettings.ChannelShape(
shape.getBedRoundness(),
shape.getBedRoughness(),
shape.getWallRoughness(),
shape.getRoughnessWavelength(),
shape.getRadialBase(),
shape.getRadialMinimum(),
shape.getRadialMaximum(),
shape.getPrimaryLobeStrength(),
shape.getDetailLobeStrength(),
shape.getCeilingRoughness(),
shape.getAspectMinimum(),
shape.getAspectRange()
);
}
// The surface shape's roughness fields are optional and fall back to the channel roughness.
private static HydrologyPlannerSettings.ChannelShape surfaceShape(
IrisSurfaceRiverShapeConfig shape,
IrisSurfaceRiverChannelConfig channel
) {
return new HydrologyPlannerSettings.ChannelShape(
shape.getBedRoundness(),
shape.getBedRoughness() == null ? channel.getRoughness() : shape.getBedRoughness(),
shape.getWallRoughness() == null ? channel.getRoughness() : shape.getWallRoughness(),
shape.getRoughnessWavelength() == null ? channel.getRoughnessWavelength() : shape.getRoughnessWavelength(),
shape.getRadialBase(),
shape.getRadialMinimum(),
shape.getRadialMaximum(),
shape.getPrimaryLobeStrength(),
shape.getDetailLobeStrength(),
shape.getCeilingRoughness(),
shape.getAspectMinimum(),
shape.getAspectRange()
);
}
private static HydrologyPlannerSettings.Grotto grotto(
boolean enabled,
int horizontalRadius,
int verticalRadius,
int headroom,
int maximumVolume
) {
return new HydrologyPlannerSettings.Grotto(
enabled,
horizontalRadius,
verticalRadius,
headroom,
maximumVolume
);
}
// A sea cave is a coastal grotto without a river, so it needs the coastal grotto itself enabled.
private static HydrologyPlannerSettings.SeaCaves seaCaves(IrisCoastalRiverGrottoConfig coastal) {
IrisSeaCaveConfig seaCaves = coastal.getSeaCaves();
return new HydrologyPlannerSettings.SeaCaves(
coastal.isEnabled() && seaCaves.isEnabled(),
seaCaves.getMaximumPerTile(),
seaCaves.getMinimumSpacing(),
seaCaves.getMinimumCoastHeight(),
seaCaves.getDepth(),
seaCaves.getSweepJitterDegrees()
);
}
private static List<HydrologyPlannerSettings.DeepFluid> deepFluids(
List<IrisDeepFluidConfig> configurations,
int minimumWorldY,
IrisRiverRoutingConfig routing,
int refinementSpacing
) {
ArrayList<HydrologyPlannerSettings.DeepFluid> deepFluids = new ArrayList<>();
for (IrisDeepFluidConfig configuration : configurations) {
int radius = configuration.getHorizontalRadius();
int verticalRadius = configuration.getVerticalRadius();
int maximumVolume = boundedVolume(radius, verticalRadius, configuration.getHeadroom());
int maximumPerTile = Math.min(64, maximumSources(configuration.getDensity(), 0));
int maximumChannelLength = maximumDeepChannelLength(configuration, routing);
int minimumChannelLength = maximumChannelLength == 0
? 0
: Math.min(maximumChannelLength, Math.max(refinementSpacing, radius));
deepFluids.add(new HydrologyPlannerSettings.DeepFluid(
configuration.getId(),
configuration.getDensity() > 0D
&& (configuration.isContainedPools() || configuration.isShortChannels()),
configuration.getDensity(),
configuration.getSpacing(),
minimumInt(configuration.getHeight()) - minimumWorldY,
maximumInt(configuration.getHeight()) - minimumWorldY,
radius,
radius,
verticalRadius,
verticalRadius,
minimumChannelLength,
maximumChannelLength,
configuration.getChannelWidth(),
configuration.getDepth(),
configuration.getHeadroom(),
maximumVolume,
maximumPerTile,
configuration.isContainedPools(),
configuration.isShortChannels()
));
}
return List.copyOf(deepFluids);
}
static int maximumDeepChannelLength(
IrisDeepFluidConfig configuration,
IrisRiverRoutingConfig routing
) {
if (!configuration.isShortChannels()) {
return 0;
}
int configuredLength = Math.max(
HydrologyPlannerSettings.Routing.refinementSpacing(routing.getSampleSpacing()),
configuration.getSpacing() / 3);
return Math.min(configuredLength, routing.getTileSize() / 2);
}
private static int maximumRouteNodes(IrisRiverRoutingConfig routing) {
int halo = Math.min(routing.getMaximumRouteLength(), routing.getTileSize() / 2);
int alignedHalo = Math.floorDiv(halo, routing.getSampleSpacing()) * routing.getSampleSpacing();
int width = routing.getTileSize() / routing.getSampleSpacing() + 1
+ alignedHalo * 2 / routing.getSampleSpacing();
long nodes = (long) width * width;
if (nodes > 1_000_000L) {
throw new IllegalArgumentException("Hydrology routing lattice exceeds one million nodes.");
}
return (int) nodes;
}
private static int tileCoordinate(long blockCoordinate, int tileSize) {
return Math.toIntExact(Math.floorDiv(blockCoordinate, tileSize));
}
private static int maximumSources(double density, int minimumPerTile) {
int expected = (int) Math.ceil(Math.max(0D, density));
return Math.min(64, Math.max(minimumPerTile, expected));
}
private static int boundedVolume(int radius, int verticalRadius, int headroom) {
long diameter = radius * 2L + 1L;
long volume = diameter * diameter * (verticalRadius + headroom + 1L);
return (int) Math.min(1_048_576L, Math.max(64L, volume));
}
private static HydrologyPlannerSettings.Pond pond(IrisSurfaceRiverPondConfig config) {
return new HydrologyPlannerSettings.Pond(
config.isEnabled(),
config.getMinimumRadius(),
config.getMaximumRadius(),
config.getDepth()
);
}
private static int minimumInt(IrisStyledRange range) {
return (int) StrictMath.floor(Math.min(range.getMin(), range.getMax()));
}
private static int maximumInt(IrisStyledRange range) {
return (int) StrictMath.ceil(Math.max(range.getMin(), range.getMax()));
}
private static Set<String> profileKeys(List<IrisRiverProfile> profiles) {
LinkedHashSet<String> keys = new LinkedHashSet<>();
for (IrisRiverProfile profile : profiles) {
@@ -0,0 +1,499 @@
package art.arcane.iris.engine.hydrology.runtime;
import art.arcane.iris.engine.hydrology.HydrologyHash;
import art.arcane.iris.engine.hydrology.HydrologyPlannerSettings;
import art.arcane.iris.engine.hydrology.policy.SurfaceRiverPolicy;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisCoastalRiverGrottoConfig;
import art.arcane.iris.engine.object.IrisDeepFluidConfig;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisHydrology;
import art.arcane.iris.engine.object.IrisInlandRiverGrottoConfig;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.engine.object.IrisRiverChannelShapeConfig;
import art.arcane.iris.engine.object.IrisRiverDropShapeConfig;
import art.arcane.iris.engine.object.IrisRiverGeometryConfig;
import art.arcane.iris.engine.object.IrisRiverGrottoConfig;
import art.arcane.iris.engine.object.IrisRiverHydrology;
import art.arcane.iris.engine.object.IrisRiverInlandOutlet;
import art.arcane.iris.engine.object.IrisRiverMeanderConfig;
import art.arcane.iris.engine.object.IrisRiverMouthConfig;
import art.arcane.iris.engine.object.IrisRiverPolicy;
import art.arcane.iris.engine.object.IrisRiverRoutingConfig;
import art.arcane.iris.engine.object.IrisSeaCaveConfig;
import art.arcane.iris.engine.object.IrisStyledRange;
import art.arcane.iris.engine.object.IrisSurfacePoolConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverBankConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverChannelConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverErosionConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverFlowConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverPondConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverShapeConfig;
import art.arcane.iris.engine.object.IrisSurfaceRiverSourceConfig;
import art.arcane.iris.engine.object.IrisUndergroundRiverConfig;
import art.arcane.iris.engine.object.IrisUndergroundRiverSourceConfig;
import art.arcane.iris.util.common.data.DataProvider;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
final class IrisHydrologySettingsCompiler {
private IrisHydrologySettingsCompiler() {
}
static HydrologyPlannerSettings compile(
IrisDimension dimension,
IrisHydrology hydrology,
DataProvider data
) {
IrisRiverHydrology rivers = hydrology.getRivers();
IrisRiverRoutingConfig routing = rivers.getRouting();
IrisSurfaceRiverConfig surface = rivers.getSurface();
IrisSurfaceRiverSourceConfig surfaceSources = surface.getSources();
IrisSurfaceRiverChannelConfig channel = surface.getChannel();
IrisSurfaceRiverBankConfig banks = surface.getBanks();
IrisSurfaceRiverErosionConfig erosion = surface.getErosion();
IrisSurfaceRiverFlowConfig flow = surface.getFlow();
IrisRiverMouthConfig mouths = surface.getMouths();
IrisUndergroundRiverConfig underground = rivers.getUnderground();
IrisUndergroundRiverSourceConfig undergroundSources = underground.getSources();
int routeNodes = maximumRouteNodes(routing);
int minimumWorldY = dimension.getMinHeight();
boolean riversEnabled = rivers.isEnabled();
boolean surfaceEnabled = riversEnabled && surface.isEnabled();
boolean undergroundEnabled = riversEnabled && underground.isEnabled();
HydrologyPlannerSettings.Routing plannerRouting = new HydrologyPlannerSettings.Routing(
routing.getTileSize(),
routing.getSampleSpacing(),
routeNodes,
routing.getMaximumRouteLength(),
routing.getMinimumSurfaceCourseLength(),
routing.getMinimumUndergroundCourseLength(),
routing.getValleyPreference(),
routing.getUphillPenalty(),
routing.getSlopePenalty(),
routing.getConfluenceAttraction(),
routing.getLengthPreference(),
routing.getTributaries(),
new HydrologyPlannerSettings.Regional(
routing.getRegional().isEnabled(),
routing.getRegional().getSampleSpacing(),
routing.getRegional().getMinimumLength(),
routing.getRegional().getMaximumTrunks(),
routing.getRegional().getMaximumCachedBasins(),
routing.getRegional().getMaximumCachedStations(),
routing.getRegional().isCoastalChannels(),
routing.getRegional().getCoastalChannelChance(),
routing.getRegional().getMaximumCoastalIncision()
)
);
HydrologyPlannerSettings.Source plannerSurfaceSources = new HydrologyPlannerSettings.Source(
surfaceEnabled,
surfaceSources.getDensity(),
surfaceSources.getMinimumElevation() - minimumWorldY,
surfaceSources.getMinimumPerTile(),
maximumSources(surfaceSources.getDensity(), surfaceSources.getMinimumPerTile()),
surfaceSources.getMinimumSpacing()
);
HydrologyPlannerSettings.Surface plannerSurface = new HydrologyPlannerSettings.Surface(
surfaceEnabled,
plannerSurfaceSources,
minimumInt(channel.getWidth()),
maximumInt(channel.getWidth()),
minimumInt(channel.getDepth()),
maximumInt(channel.getDepth()),
channel.getMaximumIncision(),
banks.getShoreWidth(),
new HydrologyPlannerSettings.Banks(
channel.getSink(),
banks.getBlendSlope(),
banks.getMinimumBlendWidth(),
banks.getMaximumBlendWidth(),
channel.getRoughness(),
channel.getRoughnessWavelength(),
flow.getCascadeRun(),
flow.getWaterfallMinimumDrop(),
mouths.getFlareRatio(),
new HydrologyPlannerSettings.Inlet(
mouths.getInletLength(),
mouths.getInletDepth(),
mouths.getMaximumIncision(),
mouths.getInletCourseFraction(),
mouths.getInletRampSlope()
),
channel.getSpringWidthRatio(),
channel.getSpringLength(),
banks.isExposeCutStrata(),
new HydrologyPlannerSettings.Erosion(
erosion.isEnabled(),
erosion.getSmoothingRadius(),
erosion.getThalwegFraction(),
erosion.getBlendCurve(),
erosion.getBedNoise(),
erosion.getStyle(),
erosion.getTerraceSteps(),
erosion.getCliffFraction(),
erosion.getBedProfile(),
banks.getShoreRise(),
banks.getBlendBaseWidth(),
new HydrologyPlannerSettings.Excavation(
banks.getExcavation().getMaximumDepth(),
banks.getExcavation().getMaximumWidth(),
banks.getExcavation().getMaximumVolumePerBlock()
)
),
new HydrologyPlannerSettings.Ponds(
pond(surface.getPonds().getSource()),
pond(surface.getPonds().getTerminal())
),
new HydrologyPlannerSettings.Channel(
channel.getSmoothingRadius(),
channel.getOutlineMinimumRatio(),
channel.getOutlineMaximumRatio(),
channel.getSpringExtraDepth()
),
new HydrologyPlannerSettings.Flow(
flow.getWaterfallThalwegFraction(),
flow.getPlungeBasinMinimumDrop(),
flow.getPlungeBasinLengthRatio(),
flow.getPlungeBasinDepth()
)
)
);
HydrologyPlannerSettings.Hydraulics plannerHydraulics =
new HydrologyPlannerSettings.Hydraulics(flow.getWaterfallMinimumDrop());
HydrologyPlannerSettings.Source plannerUndergroundSources = new HydrologyPlannerSettings.Source(
undergroundEnabled,
undergroundSources.getDensity(),
Integer.MIN_VALUE,
undergroundSources.getMinimumPerTile(),
maximumSources(undergroundSources.getDensity(), undergroundSources.getMinimumPerTile()),
undergroundSources.getMinimumSpacing()
);
HydrologyPlannerSettings.Underground plannerUnderground = new HydrologyPlannerSettings.Underground(
undergroundEnabled,
plannerUndergroundSources,
minimumInt(underground.getFluidLevel()) - minimumWorldY,
maximumInt(underground.getFluidLevel()) - minimumWorldY,
minimumInt(underground.getChannelWidth()),
maximumInt(underground.getChannelWidth()),
minimumInt(underground.getDepth()),
maximumInt(underground.getDepth()),
minimumInt(underground.getHeadroom()),
maximumInt(underground.getHeadroom()),
underground.isConnectToExistingCaves(),
underground.getTributaries(),
underground.getMinimumRockCover(),
underground.getMinimumFloorCover(),
underground.getWideningSources()
);
IrisRiverGrottoConfig grottos = rivers.getGrottos();
IrisCoastalRiverGrottoConfig coastal = grottos.getCoastal();
IrisInlandRiverGrottoConfig inland = grottos.getInland();
boolean inlandEnabled = inland.isEnabled()
&& routing.getInlandOutlets().contains(IrisRiverInlandOutlet.SINKHOLE_GROTTO);
HydrologyPlannerSettings.Outlets plannerOutlets = new HydrologyPlannerSettings.Outlets(
routing.isOceanOutlets(),
grotto(coastal.isEnabled(), coastal.getHorizontalRadius(), coastal.getVerticalRadius(),
coastal.getHeadroom(), coastal.getMaximumVolume()),
grotto(inlandEnabled, inland.getHorizontalRadius(), inland.getVerticalRadius(),
inland.getHeadroom(), inland.getMaximumVolume()),
inlandEnabled && inland.isConnectSurfaceRivers(),
coastal.getCliffMinimumHeight() == null
? Math.max(4, coastal.getVerticalRadius())
: coastal.getCliffMinimumHeight(),
underground.getMouthLevelingDistance(),
mouths.getMaximumOceanApron(),
routing.getMaximumOutletsPerTile(),
routing.getMaximumCoastalOutletsPerTile(),
coastal.getCliffSlopeFactor()
);
HydrologyPlannerSettings.Geometry plannerGeometry = geometry(
rivers.getGeometry(),
surfaceShape(rivers.getGeometry().getSurface(), channel)
);
return new HydrologyPlannerSettings(
dimension.getFluidHeight(),
plannerRouting,
plannerSurface,
plannerHydraulics,
plannerUnderground,
plannerOutlets,
plannerGeometry,
deepFluids(hydrology.getDeepFluids(), minimumWorldY, routing, plannerRouting.refinementSpacing()),
surfacePools(hydrology.getSurfacePools()),
widestShoreBiomeWidth(dimension, data, banks.getShoreWidth()),
seaCaves(coastal),
surfacePolicyBounds(dimension, data)
);
}
static HydrologyPlannerSettings.SurfacePolicyBounds surfacePolicyBounds(IrisDimension dimension, DataProvider data) {
TreeMap<String, SurfaceRiverPolicy> policies = new TreeMap<>();
addSurfacePolicy(policies, "dimension", dimension.getRiverPolicy());
for (IrisRegion region : dimension.getAllRegions(data)) {
addSurfacePolicy(policies, "region:" + region.getLoadKey(), region.getRiverPolicy());
}
for (IrisBiome biome : dimension.getAllBiomes(data)) {
addSurfacePolicy(policies, "biome:" + biome.getLoadKey(), biome.getRiverPolicy());
}
long fingerprint = 0L;
int maximumSpacing = 0;
for (SurfaceRiverPolicy policy : policies.values()) {
fingerprint = HydrologyHash.mix(fingerprint, policy.hashCode(), HydrologyHash.text(policy.areaKey()));
maximumSpacing = Math.max(maximumSpacing, policy.sourceSpacing(0));
}
return new HydrologyPlannerSettings.SurfacePolicyBounds(fingerprint, maximumSpacing);
}
private static void addSurfacePolicy(Map<String, SurfaceRiverPolicy> policies, String key, IrisRiverPolicy policy) {
if (policy == null) {
return;
}
SurfaceRiverPolicy surface = policy.surfacePolicy(key);
if (surface.configured()) {
policies.put(key, surface);
}
}
/**
* The widest shore band any policy of the dimension asks for, whether as a shore biome band or as a
* geometric shore width, never below the dimension's geometric shore width, so the publication
* envelope reaches every column a river can give shore content to.
*/
static double widestShoreBiomeWidth(IrisDimension dimension, DataProvider data, double shoreWidth) {
double widest = Math.max(0D, shoreWidth);
widest = Math.max(widest, policyShoreReach(dimension.getRiverPolicy()));
for (IrisRegion region : dimension.getAllRegions(data)) {
widest = Math.max(widest, policyShoreReach(region.getRiverPolicy()));
}
for (IrisBiome biome : dimension.getAllBiomes(data)) {
widest = Math.max(widest, policyShoreReach(biome.getRiverPolicy()));
}
return widest;
}
private static double policyShoreReach(IrisRiverPolicy policy) {
if (policy == null) {
return 0D;
}
double shoreBiomeWidth = policy.getShoreBiomeWidth() == null ? 0D : policy.getShoreBiomeWidth();
double shoreWidth = policy.getShoreWidth() == null ? 0D : policy.getShoreWidth();
return Math.max(shoreBiomeWidth, shoreWidth);
}
private static List<HydrologyPlannerSettings.SurfacePool> surfacePools(List<IrisSurfacePoolConfig> configurations) {
ArrayList<HydrologyPlannerSettings.SurfacePool> pools = new ArrayList<>();
for (IrisSurfacePoolConfig configuration : configurations) {
pools.add(new HydrologyPlannerSettings.SurfacePool(
configuration.getId(),
configuration.getDensity() > 0D,
configuration.getDensity(),
configuration.getSpacing(),
configuration.getMinimumRadius(),
configuration.getMaximumRadius(),
configuration.getDepth(),
Math.min(64, maximumSources(configuration.getDensity(), 0)),
configuration.getBiome()
));
}
return List.copyOf(pools);
}
private static HydrologyPlannerSettings.Geometry geometry(
IrisRiverGeometryConfig geometry,
HydrologyPlannerSettings.ChannelShape surfaceShape
) {
IrisRiverMeanderConfig meanders = geometry.getMeanders();
IrisRiverDropShapeConfig drops = geometry.getDrops();
return new HydrologyPlannerSettings.Geometry(
new HydrologyPlannerSettings.Meanders(
meanders.getPrimaryWavelength(),
meanders.getDetailWavelength(),
meanders.getPrimaryStrength(),
meanders.getDetailStrength(),
meanders.getMaximumOffsetRatio(),
meanders.getSmoothingPasses(),
meanders.getMaximumTurnDegrees()
),
surfaceShape,
channelShape(geometry.getUnderground()),
channelShape(geometry.getGrottos()),
new HydrologyPlannerSettings.Drops(
drops.getCascadeRunPerBlock(),
drops.getCascadeExponent(),
drops.getMaximumCascadeStep(),
drops.getFlowWidthRatio(),
drops.getMaximumFlowDepth(),
drops.getBasinWidthRatio(),
drops.getMaximumBasinDepth(),
drops.getUndergroundCascadeRunPerBlock()
)
);
}
private static HydrologyPlannerSettings.ChannelShape channelShape(IrisRiverChannelShapeConfig shape) {
return new HydrologyPlannerSettings.ChannelShape(
shape.getBedRoundness(),
shape.getBedRoughness(),
shape.getWallRoughness(),
shape.getRoughnessWavelength(),
shape.getRadialBase(),
shape.getRadialMinimum(),
shape.getRadialMaximum(),
shape.getPrimaryLobeStrength(),
shape.getDetailLobeStrength(),
shape.getCeilingRoughness(),
shape.getAspectMinimum(),
shape.getAspectRange()
);
}
// The surface shape's roughness fields are optional and fall back to the channel roughness.
private static HydrologyPlannerSettings.ChannelShape surfaceShape(
IrisSurfaceRiverShapeConfig shape,
IrisSurfaceRiverChannelConfig channel
) {
return new HydrologyPlannerSettings.ChannelShape(
shape.getBedRoundness(),
shape.getBedRoughness() == null ? channel.getRoughness() : shape.getBedRoughness(),
shape.getWallRoughness() == null ? channel.getRoughness() : shape.getWallRoughness(),
shape.getRoughnessWavelength() == null ? channel.getRoughnessWavelength() : shape.getRoughnessWavelength(),
shape.getRadialBase(),
shape.getRadialMinimum(),
shape.getRadialMaximum(),
shape.getPrimaryLobeStrength(),
shape.getDetailLobeStrength(),
shape.getCeilingRoughness(),
shape.getAspectMinimum(),
shape.getAspectRange()
);
}
private static HydrologyPlannerSettings.Grotto grotto(
boolean enabled,
int horizontalRadius,
int verticalRadius,
int headroom,
int maximumVolume
) {
return new HydrologyPlannerSettings.Grotto(
enabled,
horizontalRadius,
verticalRadius,
headroom,
maximumVolume
);
}
// A sea cave is a coastal grotto without a river, so it needs the coastal grotto itself enabled.
private static HydrologyPlannerSettings.SeaCaves seaCaves(IrisCoastalRiverGrottoConfig coastal) {
IrisSeaCaveConfig seaCaves = coastal.getSeaCaves();
return new HydrologyPlannerSettings.SeaCaves(
coastal.isEnabled() && seaCaves.isEnabled(),
seaCaves.getMaximumPerTile(),
seaCaves.getMinimumSpacing(),
seaCaves.getMinimumCoastHeight(),
seaCaves.getDepth(),
seaCaves.getSweepJitterDegrees()
);
}
private static List<HydrologyPlannerSettings.DeepFluid> deepFluids(
List<IrisDeepFluidConfig> configurations,
int minimumWorldY,
IrisRiverRoutingConfig routing,
int refinementSpacing
) {
ArrayList<HydrologyPlannerSettings.DeepFluid> deepFluids = new ArrayList<>();
for (IrisDeepFluidConfig configuration : configurations) {
int radius = configuration.getHorizontalRadius();
int verticalRadius = configuration.getVerticalRadius();
int maximumVolume = boundedVolume(radius, verticalRadius, configuration.getHeadroom());
int maximumPerTile = Math.min(64, maximumSources(configuration.getDensity(), 0));
int maximumChannelLength = maximumDeepChannelLength(configuration, routing);
int minimumChannelLength = maximumChannelLength == 0
? 0
: Math.min(maximumChannelLength, Math.max(refinementSpacing, radius));
deepFluids.add(new HydrologyPlannerSettings.DeepFluid(
configuration.getId(),
configuration.getDensity() > 0D
&& (configuration.isContainedPools() || configuration.isShortChannels()),
configuration.getDensity(),
configuration.getSpacing(),
minimumInt(configuration.getHeight()) - minimumWorldY,
maximumInt(configuration.getHeight()) - minimumWorldY,
radius,
radius,
verticalRadius,
verticalRadius,
minimumChannelLength,
maximumChannelLength,
configuration.getChannelWidth(),
configuration.getDepth(),
configuration.getHeadroom(),
maximumVolume,
maximumPerTile,
configuration.isContainedPools(),
configuration.isShortChannels()
));
}
return List.copyOf(deepFluids);
}
static int maximumDeepChannelLength(
IrisDeepFluidConfig configuration,
IrisRiverRoutingConfig routing
) {
if (!configuration.isShortChannels()) {
return 0;
}
int configuredLength = Math.max(
HydrologyPlannerSettings.Routing.refinementSpacing(routing.getSampleSpacing()),
configuration.getSpacing() / 3);
return Math.min(configuredLength, routing.getTileSize() / 2);
}
private static int maximumRouteNodes(IrisRiverRoutingConfig routing) {
int halo = Math.min(routing.getMaximumRouteLength(), routing.getTileSize() / 2);
int alignedHalo = Math.floorDiv(halo, routing.getSampleSpacing()) * routing.getSampleSpacing();
int width = routing.getTileSize() / routing.getSampleSpacing() + 1
+ alignedHalo * 2 / routing.getSampleSpacing();
long nodes = (long) width * width;
if (nodes > 1_000_000L) {
throw new IllegalArgumentException("Hydrology routing lattice exceeds one million nodes.");
}
return (int) nodes;
}
private static int maximumSources(double density, int minimumPerTile) {
int expected = (int) Math.ceil(Math.max(0D, density));
return Math.min(64, Math.max(minimumPerTile, expected));
}
private static int boundedVolume(int radius, int verticalRadius, int headroom) {
long diameter = radius * 2L + 1L;
long volume = diameter * diameter * (verticalRadius + headroom + 1L);
return (int) Math.min(1_048_576L, Math.max(64L, volume));
}
private static HydrologyPlannerSettings.Pond pond(IrisSurfaceRiverPondConfig config) {
return new HydrologyPlannerSettings.Pond(
config.isEnabled(),
config.getMinimumRadius(),
config.getMaximumRadius(),
config.getDepth()
);
}
private static int minimumInt(IrisStyledRange range) {
return (int) StrictMath.floor(Math.min(range.getMin(), range.getMax()));
}
private static int maximumInt(IrisStyledRange range) {
return (int) StrictMath.ceil(Math.max(range.getMin(), range.getMax()));
}
}
@@ -316,10 +316,7 @@ public final class IrisObjectIO {
private static void writeValidated(IrisObject self, OutputStream o) throws IOException {
DataOutputStream dos = new DataOutputStream(o);
dos.writeInt(self.w);
dos.writeInt(self.h);
dos.writeInt(self.d);
dos.writeUTF("Iris V2 IOB;");
writeHeader(self, dos);
Palette palette = buildPalette(self);
dos.writeShort(palette.size());
@@ -330,21 +327,13 @@ public final class IrisObjectIO {
dos.writeInt(self.blocks.size());
for (var entry : self.blocks) {
var i = entry.getKey();
dos.writeShort(i.getBlockX());
dos.writeShort(i.getBlockY());
dos.writeShort(i.getBlockZ());
dos.writeShort(palette.indexOf(entry.getValue().key()));
for (Map.Entry<IrisBlockVector, PlatformBlockState> entry : self.blocks) {
writeBlock(dos, palette, entry);
}
dos.writeInt(self.states.size());
for (var entry : self.states) {
var i = entry.getKey();
dos.writeShort(i.getBlockX());
dos.writeShort(i.getBlockY());
dos.writeShort(i.getBlockZ());
entry.getValue().toBinary(dos);
for (Map.Entry<IrisBlockVector, TileData> entry : self.states) {
writeState(dos, entry);
}
}
@@ -369,10 +358,7 @@ public final class IrisObjectIO {
public void execute() {
try {
DataOutputStream dos = new DataOutputStream(o);
dos.writeInt(self.w);
dos.writeInt(self.h);
dos.writeInt(self.d);
dos.writeUTF("Iris V2 IOB;");
writeHeader(self, dos);
Palette palette = buildPalette(self);
c += self.blocks.size();
@@ -387,22 +373,14 @@ public final class IrisObjectIO {
dos.writeInt(self.blocks.size());
for (var entry : self.blocks) {
var i = entry.getKey();
dos.writeShort(i.getBlockX());
dos.writeShort(i.getBlockY());
dos.writeShort(i.getBlockZ());
dos.writeShort(palette.indexOf(entry.getValue().key()));
for (Map.Entry<IrisBlockVector, PlatformBlockState> entry : self.blocks) {
writeBlock(dos, palette, entry);
++c;
}
dos.writeInt(self.states.size());
for (var entry : self.states) {
var i = entry.getKey();
dos.writeShort(i.getBlockX());
dos.writeShort(i.getBlockY());
dos.writeShort(i.getBlockZ());
entry.getValue().toBinary(dos);
for (Map.Entry<IrisBlockVector, TileData> entry : self.states) {
writeState(dos, entry);
++c;
}
} catch (IOException e) {
@@ -436,6 +414,31 @@ public final class IrisObjectIO {
throw ref.get();
}
private static void writeHeader(IrisObject self, DataOutputStream output) throws IOException {
output.writeInt(self.w);
output.writeInt(self.h);
output.writeInt(self.d);
output.writeUTF(V2_HEADER);
}
private static void writeBlock(DataOutputStream output, Palette palette,
Map.Entry<IrisBlockVector, PlatformBlockState> entry) throws IOException {
IrisBlockVector position = entry.getKey();
output.writeShort(position.getBlockX());
output.writeShort(position.getBlockY());
output.writeShort(position.getBlockZ());
output.writeShort(palette.indexOf(entry.getValue().key()));
}
private static void writeState(DataOutputStream output,
Map.Entry<IrisBlockVector, TileData> entry) throws IOException {
IrisBlockVector position = entry.getKey();
output.writeShort(position.getBlockX());
output.writeShort(position.getBlockY());
output.writeShort(position.getBlockZ());
entry.getValue().toBinary(output);
}
static void write(IrisObject self, File file) throws IOException {
if (file == null) {
return;
@@ -33,32 +33,27 @@ public final class TreeBlockResolver {
}
public static PlatformBlockState resolve(IrisProceduralTree tree, IrisData data, TreeBlockCanvas.Cell cell, TreeBlockCanvas.Vec pos, RNG paletteRng) {
switch (cell.role()) {
case TRUNK -> {
PlatformBlockState state = resolveBlock(tree.getTrunk(), tree.getTrunkPalette(), data, pos, paletteRng);
return finishTrunk(state, cell);
}
return switch (cell.role()) {
case TRUNK -> finishTrunk(resolveBlock(tree.getTrunk(), tree.getTrunkPalette(), data, pos, paletteRng), cell);
case SECONDARY_TRUNK -> {
PlatformBlockState state = resolveBlock(tree.getSecondaryTrunk(), tree.getSecondaryTrunkPalette(), data, pos, paletteRng);
if (state == null) {
state = resolveBlock(tree.getTrunk(), tree.getTrunkPalette(), data, pos, paletteRng);
}
return finishTrunk(state, cell);
}
case LEAF -> {
return resolveBlock(tree.getLeaves(), tree.getLeavesPalette(), data, pos, paletteRng);
yield finishTrunk(state, cell);
}
case LEAF -> resolveBlock(tree.getLeaves(), tree.getLeavesPalette(), data, pos, paletteRng);
case SECONDARY_LEAF -> {
PlatformBlockState state = resolveSecondaryLeaf(tree, data, pos, paletteRng);
if (state == null) {
state = resolveBlock(tree.getLeaves(), tree.getLeavesPalette(), data, pos, paletteRng);
}
return state;
yield state;
}
case DECORATOR -> {
int index = cell.decoratorIndex();
if (index < 0 || tree.getDecorators() == null || index >= tree.getDecorators().size()) {
return null;
yield null;
}
IrisTreeDecorator dec = tree.getDecorators().get(index);
PlatformBlockState state = resolveBlock(dec.getBlock(), dec.getPalette(), data, pos, paletteRng);
@@ -68,12 +63,10 @@ public final class TreeBlockResolver {
} catch (IllegalArgumentException ignored) {
}
}
return state;
yield state;
}
default -> {
return null;
}
}
default -> null;
};
}
private static PlatformBlockState finishTrunk(PlatformBlockState state, TreeBlockCanvas.Cell cell) {
@@ -93,7 +86,7 @@ public final class TreeBlockResolver {
}
private static PlatformBlockState resolveSecondaryLeaf(IrisProceduralTree tree, IrisData data, TreeBlockCanvas.Vec pos, RNG paletteRng) {
if (TreeTrunkBuilder.paletteSet(tree.getSecondaryLeavesPalette())) {
if (IrisProceduralBlocks.paletteSet(tree.getSecondaryLeavesPalette())) {
return tree.getSecondaryLeavesPalette().get(paletteRng, pos.x(), pos.y(), pos.z(), data);
}
if (tree.getWeightedSecondaryLeaves() != null && !tree.getWeightedSecondaryLeaves().isEmpty()) {
@@ -126,7 +119,7 @@ public final class TreeBlockResolver {
}
private static PlatformBlockState resolveBlock(String block, IrisMaterialPalette palette, IrisData data, TreeBlockCanvas.Vec pos, RNG paletteRng) {
if (TreeTrunkBuilder.paletteSet(palette)) {
if (IrisProceduralBlocks.paletteSet(palette)) {
return palette.get(paletteRng, pos.x(), pos.y(), pos.z(), data);
}
if (block != null && !block.isEmpty()) {
@@ -18,6 +18,7 @@
package art.arcane.iris.engine.object.tree;
import art.arcane.iris.engine.object.IrisProceduralBlocks;
import art.arcane.iris.engine.object.IrisProceduralTree;
import art.arcane.iris.engine.object.IrisTreeBranches;
import art.arcane.iris.engine.object.IrisTreeCanopy;
@@ -245,7 +246,7 @@ public final class TreeCanopyBuilder {
}
private static TreeBlockCanvas.Role resolveLeaf(IrisProceduralTree tree, RNG rng) {
boolean hasSecondary = TreeTrunkBuilder.paletteSet(tree.getSecondaryLeavesPalette())
boolean hasSecondary = IrisProceduralBlocks.paletteSet(tree.getSecondaryLeavesPalette())
|| (tree.getWeightedSecondaryLeaves() != null && !tree.getWeightedSecondaryLeaves().isEmpty())
|| (tree.getSecondaryLeaves() != null && !tree.getSecondaryLeaves().isEmpty());
if (!hasSecondary || tree.getSecondaryLeafFraction() <= 0.0) {
@@ -18,6 +18,7 @@
package art.arcane.iris.engine.object.tree;
import art.arcane.iris.engine.object.IrisProceduralBlocks;
import art.arcane.iris.engine.object.IrisProceduralTree;
import art.arcane.iris.engine.object.IrisTreeDecorator;
import art.arcane.volmlib.util.collection.KList;
@@ -40,7 +41,7 @@ public final class TreeDecoratorApplier {
for (int idx = 0; idx < decorators.size(); idx++) {
IrisTreeDecorator dec = decorators.get(idx);
boolean hasBlock = (dec.getBlock() != null && !dec.getBlock().isEmpty()) || TreeTrunkBuilder.paletteSet(dec.getPalette());
boolean hasBlock = (dec.getBlock() != null && !dec.getBlock().isEmpty()) || IrisProceduralBlocks.paletteSet(dec.getPalette());
if (!hasBlock) {
continue;
}
@@ -18,7 +18,7 @@
package art.arcane.iris.engine.object.tree;
import art.arcane.iris.engine.object.IrisMaterialPalette;
import art.arcane.iris.engine.object.IrisProceduralBlocks;
import art.arcane.iris.engine.object.IrisProceduralTree;
import art.arcane.iris.engine.object.IrisTreeAzimuthMode;
@@ -146,16 +146,12 @@ public final class TreeTrunkBuilder {
}
private static boolean hasSecondaryTrunk(IrisProceduralTree tree) {
if (paletteSet(tree.getSecondaryTrunkPalette())) {
if (IrisProceduralBlocks.paletteSet(tree.getSecondaryTrunkPalette())) {
return true;
}
return tree.getSecondaryTrunk() != null && !tree.getSecondaryTrunk().isEmpty();
}
static boolean paletteSet(IrisMaterialPalette palette) {
return palette != null && palette.getPalette() != null && !palette.getPalette().isEmpty();
}
private static double[] leanOffset(IrisProceduralTree tree, int y, int height) {
if (tree.getLeanAngle() == 0.0 || height <= 1) {
return new double[]{0.0, 0.0};
@@ -0,0 +1,47 @@
package art.arcane.iris.platform;
import java.util.LinkedHashMap;
import java.util.Map;
public final class BlockStateKey {
private BlockStateKey() {
}
public static String namespace(String key) {
String base = key;
int bracket = base.indexOf('[');
if (bracket >= 0) {
base = base.substring(0, bracket);
}
int colon = base.indexOf(':');
return colon >= 0 ? base.substring(0, colon) : "minecraft";
}
public static String withProperty(String key, String name, String value) {
int bracket = key.indexOf('[');
if (bracket < 0) {
return key + "[" + name + "=" + value + "]";
}
String base = key.substring(0, bracket);
String body = key.substring(bracket + 1, key.lastIndexOf(']'));
LinkedHashMap<String, String> properties = new LinkedHashMap<>();
for (String entry : body.split(",")) {
int equals = entry.indexOf('=');
if (equals < 0) {
continue;
}
properties.put(entry.substring(0, equals).trim(), entry.substring(equals + 1).trim());
}
properties.put(name, value);
StringBuilder merged = new StringBuilder(base).append('[');
boolean first = true;
for (Map.Entry<String, String> property : properties.entrySet()) {
if (!first) {
merged.append(',');
}
merged.append(property.getKey()).append('=').append(property.getValue());
first = false;
}
return merged.append(']').toString();
}
}
@@ -23,6 +23,7 @@ import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.core.nms.container.Pair;
import art.arcane.iris.core.service.ExternalDataSVC;
import art.arcane.iris.engine.object.IrisObjectRotation;
import art.arcane.iris.platform.BlockStateKey;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.common.data.IrisCustomData;
import art.arcane.iris.util.common.math.IrisBlockVector;
@@ -33,7 +34,6 @@ import org.bukkit.Tag;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -84,7 +84,7 @@ public final class BukkitBlockState implements PlatformBlockState {
private BukkitBlockState(BlockData data, String key) {
this.data = data;
this.key = key;
this.namespace = parseNamespace(key);
this.namespace = BlockStateKey.namespace(key);
}
public static BukkitBlockState of(BlockData data) {
@@ -175,44 +175,6 @@ public final class BukkitBlockState implements PlatformBlockState {
return key.hashCode();
}
private static String parseNamespace(String key) {
String base = key;
int bracket = base.indexOf('[');
if (bracket >= 0) {
base = base.substring(0, bracket);
}
int colon = base.indexOf(':');
return colon >= 0 ? base.substring(0, colon) : "minecraft";
}
private static String mergeProperty(String key, String name, String value) {
int bracket = key.indexOf('[');
if (bracket < 0) {
return key + "[" + name + "=" + value + "]";
}
String base = key.substring(0, bracket);
String body = key.substring(bracket + 1, key.lastIndexOf(']'));
LinkedHashMap<String, String> properties = new LinkedHashMap<>();
for (String entry : body.split(",")) {
int equals = entry.indexOf('=');
if (equals < 0) {
continue;
}
properties.put(entry.substring(0, equals).trim(), entry.substring(equals + 1).trim());
}
properties.put(name, value);
StringBuilder merged = new StringBuilder(base).append('[');
boolean first = true;
for (Map.Entry<String, String> property : properties.entrySet()) {
if (!first) {
merged.append(',');
}
merged.append(property.getKey()).append('=').append(property.getValue());
first = false;
}
return merged.append(']').toString();
}
@Override
public String key() {
return key;
@@ -448,7 +410,7 @@ public final class BukkitBlockState implements PlatformBlockState {
public PlatformBlockState withProperty(String name, String value) {
if (data instanceof IrisCustomData custom) {
if (ExternalDataSVC.parseState(custom.getCustom()).getB().containsKey(name)) {
String merged = mergeProperty(key, name, value);
String merged = BlockStateKey.withProperty(key, name, value);
BlockData resolved = BukkitBlockResolution.resolveOrNull(merged);
if (!(resolved instanceof IrisCustomData)) {
throw new IllegalArgumentException("Cannot resolve custom block state " + merged);
@@ -457,11 +419,11 @@ public final class BukkitBlockState implements PlatformBlockState {
}
// Re-attach the custom identity (as the proxy's own merge/clone cases do) after
// editing the base block, so auto-waterlogging cannot turn custom blocks into vanilla.
String merged = mergeProperty(custom.getBase().getAsString(), name, value);
String merged = BlockStateKey.withProperty(custom.getBase().getAsString(), name, value);
BlockData resolved = Bukkit.createBlockData(merged);
return of(IrisCustomData.of(resolved, custom.getCustom()));
}
return of(Bukkit.createBlockData(mergeProperty(key, name, value)));
return of(Bukkit.createBlockData(BlockStateKey.withProperty(key, name, value)));
}
@Override
@@ -37,18 +37,11 @@ public class ObjectTargetHandler implements DirectorParameterHandler<String> {
IrisData data = data();
if (data != null) {
for (String k : data.getObjectLoader().getPossibleKeys()) {
out.add(k);
collectPrefixes(k, prefixes);
}
collectObjectKeys(data, out, prefixes);
} else {
File packsFolder = IrisPlatforms.get().packsFolder();
for (File pack : PackDirectoryResolver.listVisiblePackDirectories(packsFolder)) {
IrisData d = IrisData.get(pack);
for (String k : d.getObjectLoader().getPossibleKeys()) {
out.add(k);
collectPrefixes(k, prefixes);
}
collectObjectKeys(IrisData.get(pack), out, prefixes);
}
}
@@ -58,6 +51,13 @@ public class ObjectTargetHandler implements DirectorParameterHandler<String> {
return out;
}
private static void collectObjectKeys(IrisData data, KList<String> out, Set<String> prefixes) {
for (String key : data.getObjectLoader().getPossibleKeys()) {
out.add(key);
collectPrefixes(key, prefixes);
}
}
private static void collectPrefixes(String key, Set<String> prefixes) {
int idx = 0;
while ((idx = key.indexOf('/', idx)) >= 0) {
@@ -43,22 +43,12 @@ public class NullSafeStream<T> extends BasicStream<T> implements ProceduralStrea
@Override
public T get(double x, double z) {
T t = stream.get(x, z);
if (t == null) {
return ifNull;
}
return t;
return t == null ? ifNull : t;
}
@Override
public T get(double x, double y, double z) {
T t = stream.get(x, y, z);
if (t == null) {
return ifNull;
}
return t;
return t == null ? ifNull : t;
}
}