mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
Patching Cleanup
This commit is contained in:
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;
|
||||
}
|
||||
|
||||
|
||||
+18
-28
@@ -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(
|
||||
|
||||
+36
-36
@@ -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,
|
||||
|
||||
+2
-484
@@ -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) {
|
||||
|
||||
+499
@@ -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
|
||||
|
||||
+9
-9
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user