mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 21:40:05 +00:00
Patching Cleanup
This commit is contained in:
1 parent
bf12f30d26
commit
29a31a69e0
44 files changed
+1340
-970
No files matched your search
+2
-26
@@ -2,6 +2,7 @@ package art.arcane.iris.core.nms.v26_2_R1;
|
|||||||
|
|
||||||
import art.arcane.iris.engine.DimensionStackContext;
|
import art.arcane.iris.engine.DimensionStackContext;
|
||||||
import art.arcane.iris.engine.DimensionStackLayout;
|
import art.arcane.iris.engine.DimensionStackLayout;
|
||||||
|
import art.arcane.iris.nativegen.NativeGenerationWriteGuard;
|
||||||
import art.arcane.iris.engine.framework.Engine;
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
import art.arcane.iris.engine.framework.NativeFeatureGenerationPolicy;
|
import art.arcane.iris.engine.framework.NativeFeatureGenerationPolicy;
|
||||||
import art.arcane.iris.engine.object.IrisBiome;
|
import art.arcane.iris.engine.object.IrisBiome;
|
||||||
@@ -44,7 +45,6 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.IntBinaryOperator;
|
import java.util.function.IntBinaryOperator;
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bukkit twin of the modded imported-feature stage. Same control, same semantics, same seeds: with
|
* Bukkit twin of the modded imported-feature stage. Same control, same semantics, same seeds: with
|
||||||
@@ -299,7 +299,7 @@ final class ImportedFeatureStage {
|
|||||||
surfaceFirstFreeY,
|
surfaceFirstFreeY,
|
||||||
floorFirstFreeY,
|
floorFirstFreeY,
|
||||||
stackContext != null,
|
stackContext != null,
|
||||||
importedFeatureProtection(staticObjects, stackContext, staticMinY));
|
NativeGenerationWriteGuard.protectedPositions(staticObjects, stackContext, staticMinY));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (int stepIndex = 0; stepIndex < steps.size(); stepIndex++) {
|
for (int stepIndex = 0; stepIndex < steps.size(); stepIndex++) {
|
||||||
@@ -317,30 +317,6 @@ final class ImportedFeatureStage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Predicate<BlockPos> importedFeatureProtection(
|
|
||||||
IrisStaticObjectLayer staticObjects,
|
|
||||||
DimensionStackContext stackContext,
|
|
||||||
int minimumY
|
|
||||||
) {
|
|
||||||
Map<Long, DimensionStackLayout> layouts = new ConcurrentHashMap<>();
|
|
||||||
return position -> {
|
|
||||||
if (!staticObjects.isEmpty() && staticObjects.contains(
|
|
||||||
position.getX(), position.getY() - minimumY, position.getZ())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (stackContext == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
long columnKey = ((long) position.getX() << 32)
|
|
||||||
^ (position.getZ() & 0xFFFFFFFFL);
|
|
||||||
DimensionStackLayout layout = layouts.computeIfAbsent(
|
|
||||||
columnKey,
|
|
||||||
ignored -> stackContext.sample(position.getX(), position.getZ())
|
|
||||||
);
|
|
||||||
return layout.isHostFeatureProtectedY(position.getY() - minimumY);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void placeStep(WorldGenLevel level, FeatureTable table, FeatureSorter.StepFeatureData stepData,
|
private void placeStep(WorldGenLevel level, FeatureTable table, FeatureSorter.StepFeatureData stepData,
|
||||||
Registry<PlacedFeature> featureRegistry, Set<Holder<Biome>> chunkBiomes,
|
Registry<PlacedFeature> featureRegistry, Set<Holder<Biome>> chunkBiomes,
|
||||||
ChunkGenerator owner, WorldgenRandom random, long decorationSeed,
|
ChunkGenerator owner, WorldgenRandom random, long decorationSeed,
|
||||||
|
|||||||
+5
-26
@@ -7,7 +7,6 @@ import java.util.Optional;
|
|||||||
import art.arcane.iris.nativegen.NativeGenerationWriteGuard;
|
import art.arcane.iris.nativegen.NativeGenerationWriteGuard;
|
||||||
import java.util.function.LongPredicate;
|
import java.util.function.LongPredicate;
|
||||||
import art.arcane.iris.engine.DimensionStackContext;
|
import art.arcane.iris.engine.DimensionStackContext;
|
||||||
import art.arcane.iris.engine.DimensionStackLayout;
|
|
||||||
import art.arcane.iris.engine.framework.Engine;
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
import art.arcane.iris.engine.framework.EngineLifecycleTasks;
|
import art.arcane.iris.engine.framework.EngineLifecycleTasks;
|
||||||
import art.arcane.iris.engine.framework.GenerationSessionException;
|
import art.arcane.iris.engine.framework.GenerationSessionException;
|
||||||
@@ -1125,7 +1124,8 @@ public class IrisChunkGenerator extends CustomChunkGenerator implements LongPred
|
|||||||
chunkPos.x(), chunkPos.z(), error);
|
chunkPos.x(), chunkPos.z(), error);
|
||||||
}
|
}
|
||||||
IrisStaticObjectLayer staticObjects = engine.getDimension().getStaticObjectLayer(engine.getData());
|
IrisStaticObjectLayer staticObjects = engine.getDimension().getStaticObjectLayer(engine.getData());
|
||||||
Predicate<BlockPos> protectedPosition = nativeStructureProtection(staticObjects);
|
Predicate<BlockPos> protectedPosition = NativeGenerationWriteGuard.protectedPositions(
|
||||||
|
staticObjects, engine.getDimensionStackContext(), engine.getMinHeight());
|
||||||
WorldGenLevel boundedWorld = NativeStructureWorldgenAccess.create(
|
WorldGenLevel boundedWorld = NativeStructureWorldgenAccess.create(
|
||||||
world, chunkPos, hostWorldgenSurfaceHeight(), hostWorldgenFloorHeight(),
|
world, chunkPos, hostWorldgenSurfaceHeight(), hostWorldgenFloorHeight(),
|
||||||
engine.getDimensionStackContext() != null,
|
engine.getDimensionStackContext() != null,
|
||||||
@@ -1216,8 +1216,9 @@ public class IrisChunkGenerator extends CustomChunkGenerator implements LongPred
|
|||||||
hostWorldgenSurfaceHeight(),
|
hostWorldgenSurfaceHeight(),
|
||||||
hostWorldgenFloorHeight(),
|
hostWorldgenFloorHeight(),
|
||||||
engine.getDimensionStackContext() != null,
|
engine.getDimensionStackContext() != null,
|
||||||
nativeStructureProtection(
|
NativeGenerationWriteGuard.protectedPositions(
|
||||||
engine.getDimension().getStaticObjectLayer(engine.getData())));
|
engine.getDimension().getStaticObjectLayer(engine.getData()),
|
||||||
|
engine.getDimensionStackContext(), engine.getMinHeight()));
|
||||||
world.setCurrentlyGenerating(() -> "Iris native structure " + structureId);
|
world.setCurrentlyGenerating(() -> "Iris native structure " + structureId);
|
||||||
try {
|
try {
|
||||||
NativeStructurePostProcessor.place(boundedWorld, structureManager, this, random, area, chunkPos,
|
NativeStructurePostProcessor.place(boundedWorld, structureManager, this, random, area, chunkPos,
|
||||||
@@ -1228,28 +1229,6 @@ public class IrisChunkGenerator extends CustomChunkGenerator implements LongPred
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Predicate<BlockPos> nativeStructureProtection(IrisStaticObjectLayer staticObjects) {
|
|
||||||
DimensionStackContext stackContext = engine.getDimensionStackContext();
|
|
||||||
int minimumY = engine.getMinHeight();
|
|
||||||
Map<Long, DimensionStackLayout> layouts = new ConcurrentHashMap<>();
|
|
||||||
return position -> {
|
|
||||||
if (!staticObjects.isEmpty() && staticObjects.contains(
|
|
||||||
position.getX(), position.getY() - minimumY, position.getZ())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (stackContext == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
long columnKey = ((long) position.getX() << 32)
|
|
||||||
^ (position.getZ() & 0xFFFFFFFFL);
|
|
||||||
DimensionStackLayout layout = layouts.computeIfAbsent(
|
|
||||||
columnKey,
|
|
||||||
ignored -> stackContext.sample(position.getX(), position.getZ())
|
|
||||||
);
|
|
||||||
return layout.isHostFeatureProtectedY(position.getY() - minimumY);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<List<Structure>> structuresByStep(Registry<Structure> registry) {
|
private List<List<Structure>> structuresByStep(Registry<Structure> registry) {
|
||||||
int runtimeId = engine.getCacheID();
|
int runtimeId = engine.getCacheID();
|
||||||
StructureStepCache cached = structureStepCache;
|
StructureStepCache cached = structureStepCache;
|
||||||
|
|||||||
+47
-1
@@ -1,13 +1,16 @@
|
|||||||
package art.arcane.iris.nativegen;
|
package art.arcane.iris.nativegen;
|
||||||
|
|
||||||
|
import art.arcane.iris.engine.DimensionStackContext;
|
||||||
|
import art.arcane.iris.engine.DimensionStackLayout;
|
||||||
import art.arcane.iris.engine.IrisComplex;
|
import art.arcane.iris.engine.IrisComplex;
|
||||||
import art.arcane.iris.engine.IrisEngine;
|
import art.arcane.iris.engine.IrisEngine;
|
||||||
import art.arcane.iris.engine.history.GenerationActivation;
|
import art.arcane.iris.engine.history.GenerationActivation;
|
||||||
import art.arcane.iris.engine.history.GenerationHistory;
|
import art.arcane.iris.engine.history.GenerationHistory;
|
||||||
import art.arcane.iris.engine.history.GenerationHistoryRuntimeRouter;
|
import art.arcane.iris.engine.history.GenerationHistoryRuntimeRouter;
|
||||||
import java.util.Optional;
|
|
||||||
import art.arcane.iris.engine.framework.Engine;
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.object.IrisStaticObjectLayer;
|
||||||
import net.minecraft.SharedConstants;
|
import net.minecraft.SharedConstants;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.server.Bootstrap;
|
import net.minecraft.server.Bootstrap;
|
||||||
import net.minecraft.world.level.ChunkPos;
|
import net.minecraft.world.level.ChunkPos;
|
||||||
import net.minecraft.world.level.WorldGenLevel;
|
import net.minecraft.world.level.WorldGenLevel;
|
||||||
@@ -16,10 +19,17 @@ import net.minecraft.world.level.chunk.status.ChunkStatus;
|
|||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public final class NativeGenerationWriteGuardTest {
|
public final class NativeGenerationWriteGuardTest {
|
||||||
@@ -29,6 +39,42 @@ public final class NativeGenerationWriteGuardTest {
|
|||||||
Bootstrap.bootStrap();
|
Bootstrap.bootStrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void staticObjectsProtectRelativeHeightBeforeStackSampling() {
|
||||||
|
IrisStaticObjectLayer objects = mock(IrisStaticObjectLayer.class);
|
||||||
|
DimensionStackContext stack = mock(DimensionStackContext.class);
|
||||||
|
when(objects.contains(-3, 74, -7)).thenReturn(true);
|
||||||
|
|
||||||
|
Predicate<BlockPos> protectedPositions = NativeGenerationWriteGuard.protectedPositions(objects, stack, -64);
|
||||||
|
|
||||||
|
assertTrue(protectedPositions.test(new BlockPos(-3, 10, -7)));
|
||||||
|
verifyNoInteractions(stack);
|
||||||
|
assertFalse(NativeGenerationWriteGuard.protectedPositions(objects, null, -64)
|
||||||
|
.test(new BlockPos(-3, 11, -7)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void emptyStaticLayerSkipsContainsAndCachesEachSignedColumn() {
|
||||||
|
IrisStaticObjectLayer objects = mock(IrisStaticObjectLayer.class);
|
||||||
|
DimensionStackContext stack = mock(DimensionStackContext.class);
|
||||||
|
DimensionStackLayout negative = mock(DimensionStackLayout.class);
|
||||||
|
DimensionStackLayout positive = mock(DimensionStackLayout.class);
|
||||||
|
when(objects.isEmpty()).thenReturn(true);
|
||||||
|
when(stack.sample(-1, -1)).thenReturn(negative);
|
||||||
|
when(stack.sample(-1, 1)).thenReturn(positive);
|
||||||
|
when(negative.isHostFeatureProtectedY(64)).thenReturn(true);
|
||||||
|
when(positive.isHostFeatureProtectedY(65)).thenReturn(true);
|
||||||
|
|
||||||
|
Predicate<BlockPos> protectedPositions = NativeGenerationWriteGuard.protectedPositions(objects, stack, -64);
|
||||||
|
|
||||||
|
assertTrue(protectedPositions.test(new BlockPos(-1, 0, -1)));
|
||||||
|
assertFalse(protectedPositions.test(new BlockPos(-1, 1, -1)));
|
||||||
|
assertTrue(protectedPositions.test(new BlockPos(-1, 1, 1)));
|
||||||
|
verify(stack, times(1)).sample(-1, -1);
|
||||||
|
verify(stack, times(1)).sample(-1, 1);
|
||||||
|
verify(objects, never()).contains(anyInt(), anyInt(), anyInt());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preTerrainSavedStampDistinguishesEarlierNativeStarts() {
|
public void preTerrainSavedStampDistinguishesEarlierNativeStarts() {
|
||||||
IrisEngine engine = mock(IrisEngine.class);
|
IrisEngine engine = mock(IrisEngine.class);
|
||||||
|
|||||||
+32
@@ -1,14 +1,22 @@
|
|||||||
package art.arcane.iris.nativegen;
|
package art.arcane.iris.nativegen;
|
||||||
|
|
||||||
|
import art.arcane.iris.engine.DimensionStackContext;
|
||||||
|
import art.arcane.iris.engine.DimensionStackLayout;
|
||||||
import art.arcane.iris.engine.framework.Engine;
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
import art.arcane.iris.engine.IrisEngine;
|
import art.arcane.iris.engine.IrisEngine;
|
||||||
import art.arcane.iris.engine.history.GenerationHistoryRuntimeRouter;
|
import art.arcane.iris.engine.history.GenerationHistoryRuntimeRouter;
|
||||||
|
import art.arcane.iris.engine.object.IrisStaticObjectLayer;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.world.level.ChunkPos;
|
import net.minecraft.world.level.ChunkPos;
|
||||||
import net.minecraft.world.level.WorldGenLevel;
|
import net.minecraft.world.level.WorldGenLevel;
|
||||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||||
import net.minecraft.world.level.chunk.status.ChunkPyramid;
|
import net.minecraft.world.level.chunk.status.ChunkPyramid;
|
||||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
public final class NativeGenerationWriteGuard {
|
public final class NativeGenerationWriteGuard {
|
||||||
private NativeGenerationWriteGuard() {
|
private NativeGenerationWriteGuard() {
|
||||||
}
|
}
|
||||||
@@ -43,4 +51,28 @@ public final class NativeGenerationWriteGuard {
|
|||||||
&& !chunk.getPersistedStatus().isOrAfter(stage)
|
&& !chunk.getPersistedStatus().isOrAfter(stage)
|
||||||
&& !engine.getComplex().allowsMantleChunkWrite(chunk.getPos().x(), chunk.getPos().z());
|
&& !engine.getComplex().allowsMantleChunkWrite(chunk.getPos().x(), chunk.getPos().z());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Predicate<BlockPos> protectedPositions(
|
||||||
|
IrisStaticObjectLayer staticObjects,
|
||||||
|
DimensionStackContext stackContext,
|
||||||
|
int minimumY
|
||||||
|
) {
|
||||||
|
Map<Long, DimensionStackLayout> layouts = new ConcurrentHashMap<>();
|
||||||
|
return position -> {
|
||||||
|
if (!staticObjects.isEmpty() && staticObjects.contains(
|
||||||
|
position.getX(), position.getY() - minimumY, position.getZ())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (stackContext == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
long columnKey = ((long) position.getX() << 32)
|
||||||
|
^ (position.getZ() & 0xFFFFFFFFL);
|
||||||
|
DimensionStackLayout layout = layouts.computeIfAbsent(
|
||||||
|
columnKey,
|
||||||
|
ignored -> stackContext.sample(position.getX(), position.getZ())
|
||||||
|
);
|
||||||
|
return layout.isHostFeatureProtectedY(position.getY() - minimumY);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -18,13 +18,13 @@
|
|||||||
|
|
||||||
package art.arcane.iris.modded;
|
package art.arcane.iris.modded;
|
||||||
|
|
||||||
|
import art.arcane.iris.platform.BlockStateKey;
|
||||||
import art.arcane.iris.spi.PlatformBlockState;
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
import net.minecraft.core.registries.BuiltInRegistries;
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
import net.minecraft.tags.BlockTags;
|
import net.minecraft.tags.BlockTags;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.level.block.state.properties.Property;
|
import net.minecraft.world.level.block.state.properties.Property;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@@ -52,7 +52,7 @@ public final class ModdedBlockState implements PlatformBlockState {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
this.parsedProperties = parsedProperties;
|
this.parsedProperties = parsedProperties;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.namespace = parseNamespace(key);
|
this.namespace = BlockStateKey.namespace(key);
|
||||||
this.deferredPlacementKey = deferredPlacementKey;
|
this.deferredPlacementKey = deferredPlacementKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,44 +101,6 @@ public final class ModdedBlockState implements PlatformBlockState {
|
|||||||
return Objects.hash(state, deferredPlacementKey);
|
return Objects.hash(state, deferredPlacementKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
@Override
|
||||||
public String key() {
|
public String key() {
|
||||||
return key;
|
return key;
|
||||||
@@ -338,7 +300,7 @@ public final class ModdedBlockState implements PlatformBlockState {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PlatformBlockState withProperty(String name, String value) {
|
public PlatformBlockState withProperty(String name, String value) {
|
||||||
String merged = mergeProperty(key, name, value);
|
String merged = BlockStateKey.withProperty(key, name, value);
|
||||||
ModdedBlockState resolved = ModdedBlockResolution.strictParse(merged);
|
ModdedBlockState resolved = ModdedBlockResolution.strictParse(merged);
|
||||||
return withHandle(resolved.handle(), resolved.parsedProperties());
|
return withHandle(resolved.handle(), resolved.parsedProperties());
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-26
@@ -20,6 +20,7 @@ package art.arcane.iris.modded;
|
|||||||
|
|
||||||
import art.arcane.iris.engine.DimensionStackContext;
|
import art.arcane.iris.engine.DimensionStackContext;
|
||||||
import art.arcane.iris.engine.DimensionStackLayout;
|
import art.arcane.iris.engine.DimensionStackLayout;
|
||||||
|
import art.arcane.iris.nativegen.NativeGenerationWriteGuard;
|
||||||
import art.arcane.iris.engine.framework.Engine;
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
import art.arcane.iris.engine.framework.GenerationSessionLease;
|
import art.arcane.iris.engine.framework.GenerationSessionLease;
|
||||||
import art.arcane.iris.engine.framework.NativeFeatureGenerationPolicy;
|
import art.arcane.iris.engine.framework.NativeFeatureGenerationPolicy;
|
||||||
@@ -62,7 +63,6 @@ import java.util.Set;
|
|||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.function.IntBinaryOperator;
|
import java.util.function.IntBinaryOperator;
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Native placed-feature passthrough for one Iris dimension, gated on {@code importedFeatures.enabled}.
|
* Native placed-feature passthrough for one Iris dimension, gated on {@code importedFeatures.enabled}.
|
||||||
@@ -353,7 +353,7 @@ final class ModdedImportedFeatureStage {
|
|||||||
surfaceFirstFreeY,
|
surfaceFirstFreeY,
|
||||||
floorFirstFreeY,
|
floorFirstFreeY,
|
||||||
stackContext != null,
|
stackContext != null,
|
||||||
importedFeatureProtection(staticObjects, stackContext, staticMinY));
|
NativeGenerationWriteGuard.protectedPositions(staticObjects, stackContext, staticMinY));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (int stepIndex = 0; stepIndex < steps.size(); stepIndex++) {
|
for (int stepIndex = 0; stepIndex < steps.size(); stepIndex++) {
|
||||||
@@ -401,30 +401,6 @@ final class ModdedImportedFeatureStage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Predicate<BlockPos> importedFeatureProtection(
|
|
||||||
IrisStaticObjectLayer staticObjects,
|
|
||||||
DimensionStackContext stackContext,
|
|
||||||
int minimumY
|
|
||||||
) {
|
|
||||||
Map<Long, DimensionStackLayout> layouts = new ConcurrentHashMap<>();
|
|
||||||
return position -> {
|
|
||||||
if (!staticObjects.isEmpty() && staticObjects.contains(
|
|
||||||
position.getX(), position.getY() - minimumY, position.getZ())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (stackContext == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
long columnKey = ((long) position.getX() << 32)
|
|
||||||
^ (position.getZ() & 0xFFFFFFFFL);
|
|
||||||
DimensionStackLayout layout = layouts.computeIfAbsent(
|
|
||||||
columnKey,
|
|
||||||
ignored -> stackContext.sample(position.getX(), position.getZ())
|
|
||||||
);
|
|
||||||
return layout.isHostFeatureProtectedY(position.getY() - minimumY);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void evictRuntime(int runtimeIdentity) {
|
void evictRuntime(int runtimeIdentity) {
|
||||||
featureTables.remove(runtimeIdentity);
|
featureTables.remove(runtimeIdentity);
|
||||||
inertGenerations.remove(runtimeIdentity);
|
inertGenerations.remove(runtimeIdentity);
|
||||||
|
|||||||
+5
-30
@@ -23,7 +23,6 @@ import net.minecraft.world.level.chunk.status.ChunkStatus;
|
|||||||
import art.arcane.iris.nativegen.NativeGenerationWriteGuard;
|
import art.arcane.iris.nativegen.NativeGenerationWriteGuard;
|
||||||
|
|
||||||
import art.arcane.iris.engine.DimensionStackContext;
|
import art.arcane.iris.engine.DimensionStackContext;
|
||||||
import art.arcane.iris.engine.DimensionStackLayout;
|
|
||||||
import art.arcane.iris.engine.framework.Engine;
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
import art.arcane.iris.engine.framework.IrisStructureLocator;
|
||||||
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
|
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
|
||||||
@@ -397,7 +396,8 @@ final class ModdedNativeStructureStage {
|
|||||||
chunkPos.x(), chunkPos.z(), error);
|
chunkPos.x(), chunkPos.z(), error);
|
||||||
}
|
}
|
||||||
IrisStaticObjectLayer staticObjects = current.getDimension().getStaticObjectLayer(current.getData());
|
IrisStaticObjectLayer staticObjects = current.getDimension().getStaticObjectLayer(current.getData());
|
||||||
Predicate<BlockPos> protectedPosition = nativeStructureProtection(current, staticObjects);
|
Predicate<BlockPos> protectedPosition = NativeGenerationWriteGuard.protectedPositions(
|
||||||
|
staticObjects, current.getDimensionStackContext(), current.getMinHeight());
|
||||||
WorldGenLevel boundedWorld = ModdedNativeStructureWorldgenAccess.create(
|
WorldGenLevel boundedWorld = ModdedNativeStructureWorldgenAccess.create(
|
||||||
world, chunkPos, worldgenSurfaceHeight(current, world.getMinY()), worldgenFloorHeight(current, world.getMinY()),
|
world, chunkPos, worldgenSurfaceHeight(current, world.getMinY()), worldgenFloorHeight(current, world.getMinY()),
|
||||||
current.getDimensionStackContext() != null,
|
current.getDimensionStackContext() != null,
|
||||||
@@ -480,9 +480,9 @@ final class ModdedNativeStructureStage {
|
|||||||
worldgenSurfaceHeight(current, world.getMinY()),
|
worldgenSurfaceHeight(current, world.getMinY()),
|
||||||
worldgenFloorHeight(current, world.getMinY()),
|
worldgenFloorHeight(current, world.getMinY()),
|
||||||
current.getDimensionStackContext() != null,
|
current.getDimensionStackContext() != null,
|
||||||
nativeStructureProtection(
|
NativeGenerationWriteGuard.protectedPositions(
|
||||||
current,
|
current.getDimension().getStaticObjectLayer(current.getData()),
|
||||||
current.getDimension().getStaticObjectLayer(current.getData())));
|
current.getDimensionStackContext(), current.getMinHeight()));
|
||||||
world.setCurrentlyGenerating(() -> "Iris native structure " + structureId);
|
world.setCurrentlyGenerating(() -> "Iris native structure " + structureId);
|
||||||
try {
|
try {
|
||||||
NativeStructurePostProcessor.place(
|
NativeStructurePostProcessor.place(
|
||||||
@@ -494,31 +494,6 @@ final class ModdedNativeStructureStage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Predicate<BlockPos> nativeStructureProtection(
|
|
||||||
Engine engine,
|
|
||||||
IrisStaticObjectLayer staticObjects
|
|
||||||
) {
|
|
||||||
DimensionStackContext stackContext = engine.getDimensionStackContext();
|
|
||||||
int minimumY = engine.getMinHeight();
|
|
||||||
Map<Long, DimensionStackLayout> layouts = new ConcurrentHashMap<>();
|
|
||||||
return position -> {
|
|
||||||
if (!staticObjects.isEmpty() && staticObjects.contains(
|
|
||||||
position.getX(), position.getY() - minimumY, position.getZ())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (stackContext == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
long columnKey = ((long) position.getX() << 32)
|
|
||||||
^ (position.getZ() & 0xFFFFFFFFL);
|
|
||||||
DimensionStackLayout layout = layouts.computeIfAbsent(
|
|
||||||
columnKey,
|
|
||||||
ignored -> stackContext.sample(position.getX(), position.getZ())
|
|
||||||
);
|
|
||||||
return layout.isHostFeatureProtectedY(position.getY() - minimumY);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<List<Structure>> structuresByStep(Registry<Structure> registry) {
|
private List<List<Structure>> structuresByStep(Registry<Structure> registry) {
|
||||||
StructureStepCache cached = structureStepCache;
|
StructureStepCache cached = structureStepCache;
|
||||||
if (cached != null && cached.registry() == registry) {
|
if (cached != null && cached.registry() == registry) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package art.arcane.iris.modded;
|
|||||||
import art.arcane.iris.engine.framework.Engine;
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HexFormat;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -91,13 +92,7 @@ public final class ModdedWorldgenIds {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String encode(String value) {
|
private static String encode(String value) {
|
||||||
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
|
return HexFormat.of().formatHex(value.getBytes(StandardCharsets.UTF_8));
|
||||||
StringBuilder encoded = new StringBuilder(bytes.length * 2);
|
|
||||||
for (byte current : bytes) {
|
|
||||||
encoded.append(Character.forDigit((current >>> 4) & 0xF, 16));
|
|
||||||
encoded.append(Character.forDigit(current & 0xF, 16));
|
|
||||||
}
|
|
||||||
return encoded.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String decode(String value) {
|
private static String decode(String value) {
|
||||||
|
|||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
package art.arcane.iris.modded;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class ModdedWorldgenIdsTest {
|
||||||
|
@Test
|
||||||
|
public void scopedIdsRetainExactUtf8HexEncoding() {
|
||||||
|
assertEquals("irisworldgen:packs/5061636b2f412d5f/dimensions/c389e5b1b1/preset",
|
||||||
|
ModdedWorldgenIds.presetRef("Pack/A-_", "É山"));
|
||||||
|
assertEquals("irisworldgen:packs//dimensions//preset", ModdedWorldgenIds.presetRef("", ""));
|
||||||
|
assertEquals("irisworldgen:packs/3f/dimensions/f0a08080/dimension_type",
|
||||||
|
ModdedWorldgenIds.dimensionTypeRef("\uD800", "\uD840\uDC00"));
|
||||||
|
assertEquals(new ModdedWorldgenIds.ScopedDimension("Pack/A-_", "É山"),
|
||||||
|
ModdedWorldgenIds.scopedDimensionType(
|
||||||
|
ModdedWorldgenIds.dimensionTypeRef("Pack/A-_", "É山")).orElseThrow());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void malformedIdentifiersRetainRejectionBehavior() {
|
||||||
|
assertTrue(ModdedWorldgenIds.scopedDimensionType(
|
||||||
|
"irisworldgen:packs/7/dimensions/61/dimension_type").isEmpty());
|
||||||
|
assertTrue(ModdedWorldgenIds.scopedDimensionType(
|
||||||
|
"irisworldgen:packs/gg/dimensions/61/dimension_type").isEmpty());
|
||||||
|
assertThrows(NullPointerException.class, () -> ModdedWorldgenIds.presetRef(null, "dimension"));
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-1
@@ -1,3 +1,4 @@
|
|||||||
|
import art.arcane.volmit.packaging.PackagingMode
|
||||||
import org.gradle.api.file.CopySpec
|
import org.gradle.api.file.CopySpec
|
||||||
import org.gradle.api.file.DeleteSpec
|
import org.gradle.api.file.DeleteSpec
|
||||||
import org.gradle.api.file.FileSystemOperations
|
import org.gradle.api.file.FileSystemOperations
|
||||||
@@ -169,6 +170,7 @@ nmsBindings.each { key, value ->
|
|||||||
transitive = false
|
transitive = false
|
||||||
}
|
}
|
||||||
testRuntimeOnly(project(':core'))
|
testRuntimeOnly(project(':core'))
|
||||||
|
testRuntimeOnly(rootProject.libs.caffeine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -790,7 +792,9 @@ pluginPackaging {
|
|||||||
policyName = 'Iris Bukkit'
|
policyName = 'Iris Bukkit'
|
||||||
stripDirectories = true
|
stripDirectories = true
|
||||||
stripLocalVariables = true
|
stripLocalVariables = true
|
||||||
releaseCompression = providers.gradleProperty('compactRelease').map { it.toBoolean() }.getOrElse(false)
|
releaseCompression = providers.gradleProperty('compactRelease')
|
||||||
|
.map { String value -> value.toBoolean() }
|
||||||
|
.getOrElse(!PackagingMode.resolve(providers).development())
|
||||||
prunePrefixes = ['art/arcane/volmlib/']
|
prunePrefixes = ['art/arcane/volmlib/']
|
||||||
keepPrefixes = ['art/arcane/volmlib/util/matter/slices/']
|
keepPrefixes = ['art/arcane/volmlib/util/matter/slices/']
|
||||||
shrinkRules = [file('packaging/shrink.pro')]
|
shrinkRules = [file('packaging/shrink.pro')]
|
||||||
|
|||||||
@@ -53,22 +53,16 @@ public record CompatFinding(CompatRegistry registry,
|
|||||||
/** {@code <key> (<registry>): <action> <subjectType> <subjectKey> at <detail>} */
|
/** {@code <key> (<registry>): <action> <subjectType> <subjectKey> at <detail>} */
|
||||||
public String line() {
|
public String line() {
|
||||||
StringBuilder sb = new StringBuilder(96);
|
StringBuilder sb = new StringBuilder(96);
|
||||||
sb.append(key).append(" (").append(registry.label()).append("): ").append(action.label());
|
sb.append(key).append(" (").append(registry.label()).append("): ");
|
||||||
if (!subjectType.isEmpty()) {
|
return appendSubject(sb);
|
||||||
sb.append(' ').append(subjectType);
|
|
||||||
}
|
|
||||||
if (!subjectKey.isEmpty()) {
|
|
||||||
sb.append(' ').append(subjectKey);
|
|
||||||
}
|
|
||||||
if (!detail.isEmpty()) {
|
|
||||||
sb.append(" at ").append(detail);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The part after the key, for lines already grouped under the key. */
|
/** The part after the key, for lines already grouped under the key. */
|
||||||
public String subjectLine() {
|
public String subjectLine() {
|
||||||
StringBuilder sb = new StringBuilder(64);
|
return appendSubject(new StringBuilder(64));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String appendSubject(StringBuilder sb) {
|
||||||
sb.append(action.label());
|
sb.append(action.label());
|
||||||
if (!subjectType.isEmpty()) {
|
if (!subjectType.isEmpty()) {
|
||||||
sb.append(' ').append(subjectType);
|
sb.append(' ').append(subjectType);
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import art.arcane.iris.spi.IrisLogging;
|
|||||||
import art.arcane.iris.core.IrisSettings;
|
import art.arcane.iris.core.IrisSettings;
|
||||||
import art.arcane.iris.engine.image.IrisImageMapCompiler;
|
import art.arcane.iris.engine.image.IrisImageMapCompiler;
|
||||||
import art.arcane.iris.engine.object.IrisImage;
|
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.collection.KSet;
|
||||||
import art.arcane.volmlib.util.data.KCache;
|
import art.arcane.volmlib.util.data.KCache;
|
||||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||||
@@ -143,7 +142,7 @@ public class ImageResourceLoader extends ResourceLoader<IrisImage> {
|
|||||||
|
|
||||||
for (File file : listedFiles) {
|
for (File file : listedFiles) {
|
||||||
if (file.isFile() && file.getName().endsWith(".png")) {
|
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()) {
|
} else if (file.isDirectory()) {
|
||||||
getPNGFiles(file, prefix + file.getName() + "/", m, visitedDirectories);
|
getPNGFiles(file, prefix + file.getName() + "/", m, visitedDirectories);
|
||||||
}
|
}
|
||||||
@@ -164,8 +163,7 @@ public class ImageResourceLoader extends ResourceLoader<IrisImage> {
|
|||||||
getPNGFiles(i, "", m, visitedDirectories);
|
getPNGFiles(i, "", m, visitedDirectories);
|
||||||
}
|
}
|
||||||
|
|
||||||
KList<String> v = new KList<>(m);
|
possibleKeys = m.toArray(new String[0]);
|
||||||
possibleKeys = v.toArray(new String[0]);
|
|
||||||
return possibleKeys;
|
return possibleKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ package art.arcane.iris.core.loader;
|
|||||||
import art.arcane.iris.spi.IrisLogging;
|
import art.arcane.iris.spi.IrisLogging;
|
||||||
import art.arcane.iris.core.IrisSettings;
|
import art.arcane.iris.core.IrisSettings;
|
||||||
import art.arcane.iris.engine.object.matter.IrisMatterObject;
|
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.collection.KSet;
|
||||||
import art.arcane.volmlib.util.data.KCache;
|
import art.arcane.volmlib.util.data.KCache;
|
||||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||||
@@ -84,7 +83,7 @@ public class MatterObjectResourceLoader extends ResourceLoader<IrisMatterObject>
|
|||||||
|
|
||||||
for (File file : listedFiles) {
|
for (File file : listedFiles) {
|
||||||
if (file.isFile() && file.getName().endsWith(".mat")) {
|
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()) {
|
} else if (file.isDirectory()) {
|
||||||
findMatFiles(file, prefix + file.getName() + "/", m, visitedDirectories);
|
findMatFiles(file, prefix + file.getName() + "/", m, visitedDirectories);
|
||||||
}
|
}
|
||||||
@@ -104,8 +103,7 @@ public class MatterObjectResourceLoader extends ResourceLoader<IrisMatterObject>
|
|||||||
findMatFiles(folder, "", m, visitedDirectories);
|
findMatFiles(folder, "", m, visitedDirectories);
|
||||||
}
|
}
|
||||||
|
|
||||||
KList<String> v = new KList<>(m);
|
possibleKeys = m.toArray(new String[0]);
|
||||||
possibleKeys = v.toArray(new String[0]);
|
|
||||||
return possibleKeys;
|
return possibleKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -397,12 +397,11 @@ public class ResourceLoader<T extends IrisRegistrant> implements MeteredCache {
|
|||||||
HashSet<String> m = new HashSet<>();
|
HashSet<String> m = new HashSet<>();
|
||||||
for (File i : files) {
|
for (File i : files) {
|
||||||
for (File j : matchAllFiles(i, (f) -> f.getName().endsWith(".json"))) {
|
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 = m.toArray(new String[0]);
|
||||||
possibleKeys = v.toArray(new String[0]);
|
|
||||||
return possibleKeys;
|
return possibleKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-28
@@ -345,20 +345,7 @@ public final class StructureTransactionWriter {
|
|||||||
if (owner != null) {
|
if (owner != null) {
|
||||||
verifyRecoveryClaim(transactionRoot, token.transactionId(), owner);
|
verifyRecoveryClaim(transactionRoot, token.transactionId(), owner);
|
||||||
}
|
}
|
||||||
Path journalPath = recoveryJournalPath(transactionRoot);
|
StructureTransactionJournal journal = readPreparedRemovalJournal(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);
|
|
||||||
}
|
|
||||||
if (!journal.transactionId().equals(token.transactionId())) {
|
if (!journal.transactionId().equals(token.transactionId())) {
|
||||||
throw new IOException("Prepared removal journal id does not match " + 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 {
|
private void verifyPreparedRemovalAuthority(Path transactionRoot, UUID transactionId) throws IOException {
|
||||||
Path journalPath = recoveryJournalPath(transactionRoot);
|
StructureTransactionJournal journal = readPreparedRemovalJournal(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);
|
|
||||||
}
|
|
||||||
if (!journal.transactionId().equals(transactionId)
|
if (!journal.transactionId().equals(transactionId)
|
||||||
|| journal.phase() != StructureTransactionJournal.Phase.PREPARED) {
|
|| journal.phase() != StructureTransactionJournal.Phase.PREPARED) {
|
||||||
throw new IOException("Prepared removal journal does not match its datapack coordinator");
|
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 {
|
private RecoveryClaim readRecoveryClaim(Path claimPath) throws IOException {
|
||||||
if (!files.isRegularFile(claimPath)) {
|
if (!files.isRegularFile(claimPath)) {
|
||||||
throw new IOException("Invalid external recovery claim " + claimPath);
|
throw new IOException("Invalid external recovery claim " + claimPath);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package art.arcane.iris.engine.data.cache;
|
package art.arcane.iris.engine.data.cache;
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@@ -35,9 +34,7 @@ public final class LazyBoundedCache<K, V> {
|
|||||||
}
|
}
|
||||||
entries.put(requiredKey, computed);
|
entries.put(requiredKey, computed);
|
||||||
if (entries.size() > maximumSize) {
|
if (entries.size() > maximumSize) {
|
||||||
Iterator<K> iterator = entries.keySet().iterator();
|
entries.sequencedKeySet().removeFirst();
|
||||||
iterator.next();
|
|
||||||
iterator.remove();
|
|
||||||
}
|
}
|
||||||
return computed;
|
return computed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2692,15 +2692,7 @@ public final class GenerationSemanticIndex {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
byte[] body = bodyBytes.toByteArray();
|
return withChecksum(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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Summary(
|
private record Summary(
|
||||||
|
|||||||
+36
-36
@@ -283,26 +283,12 @@ final class HydrologyCaveCourseFilter {
|
|||||||
|| !compatibleSharedCandidates(firstCandidate, secondCandidate, coursesById)) {
|
|| !compatibleSharedCandidates(firstCandidate, secondCandidate, coursesById)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
LinkedHashMap<CavePosition, HydrologyCaveAction> firstActions = null;
|
TerminalActions actions = alignTerminalActions(first.actions(), second.actions());
|
||||||
LinkedHashMap<CavePosition, HydrologyCaveAction> secondActions = null;
|
if (actions == 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) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
aligned.set(firstIndex, withActions(first, firstActions));
|
aligned.set(firstIndex, withActions(first, actions.first()));
|
||||||
aligned.set(secondIndex, withActions(second, secondActions));
|
aligned.set(secondIndex, withActions(second, actions.second()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return List.copyOf(aligned);
|
return List.copyOf(aligned);
|
||||||
@@ -336,26 +322,12 @@ final class HydrologyCaveCourseFilter {
|
|||||||
&& !sharesTerminalGrotto(firstCourse, secondCourse, first)) {
|
&& !sharesTerminalGrotto(firstCourse, secondCourse, first)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
LinkedHashMap<CavePosition, HydrologyCaveAction> firstActions = null;
|
TerminalActions actions = alignTerminalActions(first.actions(), second.actions());
|
||||||
LinkedHashMap<CavePosition, HydrologyCaveAction> secondActions = null;
|
if (actions == 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) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
HydrologyCaveCandidate alignedFirst = withActions(first, firstActions);
|
HydrologyCaveCandidate alignedFirst = withActions(first, actions.first());
|
||||||
HydrologyCaveCandidate alignedSecond = withActions(second, secondActions);
|
HydrologyCaveCandidate alignedSecond = withActions(second, actions.second());
|
||||||
replaceExposureCandidate(exposureValidatedCandidates, first, alignedFirst);
|
replaceExposureCandidate(exposureValidatedCandidates, first, alignedFirst);
|
||||||
replaceExposureCandidate(exposureValidatedCandidates, second, alignedSecond);
|
replaceExposureCandidate(exposureValidatedCandidates, second, alignedSecond);
|
||||||
candidates.set(firstIndex, alignedFirst);
|
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 sharedTerminalAction(
|
||||||
HydrologyCaveAction first,
|
HydrologyCaveAction first,
|
||||||
HydrologyCaveAction second
|
HydrologyCaveAction second
|
||||||
@@ -1251,6 +1245,12 @@ final class HydrologyCaveCourseFilter {
|
|||||||
return List.copyOf(openings);
|
return List.copyOf(openings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record TerminalActions(
|
||||||
|
LinkedHashMap<CavePosition, HydrologyCaveAction> first,
|
||||||
|
LinkedHashMap<CavePosition, HydrologyCaveAction> second
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
private record GeneratedChannelView(CaveVoxelView terrain) implements CaveVoxelView {
|
private record GeneratedChannelView(CaveVoxelView terrain) implements CaveVoxelView {
|
||||||
@Override
|
@Override
|
||||||
public boolean isInWorld(CavePosition position) {
|
public boolean isInWorld(CavePosition position) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package art.arcane.iris.engine.hydrology;
|
package art.arcane.iris.engine.hydrology;
|
||||||
|
|
||||||
|
import art.arcane.iris.engine.hydrology.surface.SurfaceNoise;
|
||||||
|
|
||||||
final class HydrologyChannelGeometry {
|
final class HydrologyChannelGeometry {
|
||||||
private final HydrologyFootprintCompiler compiler;
|
private final HydrologyFootprintCompiler compiler;
|
||||||
|
|
||||||
@@ -149,7 +151,7 @@ final class HydrologyChannelGeometry {
|
|||||||
if (maximum == 0) {
|
if (maximum == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
double sampled = organicNoise(seed, x, z, 12);
|
double sampled = SurfaceNoise.value(seed, x, z, 12);
|
||||||
return (int) StrictMath.round(sampled * maximum);
|
return (int) StrictMath.round(sampled * maximum);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,13 +224,13 @@ final class HydrologyChannelGeometry {
|
|||||||
)) * StrictMath.PI * 2D;
|
)) * StrictMath.PI * 2D;
|
||||||
double firstLobe = 0.5D + 0.5D * StrictMath.sin(angle * 3D + firstPhase);
|
double firstLobe = 0.5D + 0.5D * StrictMath.sin(angle * 3D + firstPhase);
|
||||||
double secondLobe = 0.5D + 0.5D * StrictMath.sin(angle * 5D + secondPhase);
|
double secondLobe = 0.5D + 0.5D * StrictMath.sin(angle * 5D + secondPhase);
|
||||||
double coherent = signedOrganicNoise(
|
double coherent = SurfaceNoise.signed(
|
||||||
segment.courseId(),
|
segment.courseId(),
|
||||||
worldX,
|
worldX,
|
||||||
worldZ,
|
worldZ,
|
||||||
channelShape.roughnessWavelength()
|
channelShape.roughnessWavelength()
|
||||||
);
|
);
|
||||||
double detail = signedOrganicNoise(
|
double detail = SurfaceNoise.signed(
|
||||||
HydrologyHash.mix(segment.courseId(), ORGANIC_SHAPE_SECOND_PHASE_SALT),
|
HydrologyHash.mix(segment.courseId(), ORGANIC_SHAPE_SECOND_PHASE_SALT),
|
||||||
worldX,
|
worldX,
|
||||||
worldZ,
|
worldZ,
|
||||||
@@ -262,14 +264,14 @@ final class HydrologyChannelGeometry {
|
|||||||
}
|
}
|
||||||
double signedCross = (deltaX * -flowZ + deltaZ * flowX) / flowLength;
|
double signedCross = (deltaX * -flowZ + deltaZ * flowX) / flowLength;
|
||||||
HydrologyPlannerSettings.ChannelShape channelShape = compiler.settings.geometry().surface();
|
HydrologyPlannerSettings.ChannelShape channelShape = compiler.settings.geometry().surface();
|
||||||
double thalweg = signedOrganicNoise(
|
double thalweg = SurfaceNoise.signed(
|
||||||
HydrologyHash.mix(segment.courseId(), ORGANIC_BED_VARIATION_SALT),
|
HydrologyHash.mix(segment.courseId(), ORGANIC_BED_VARIATION_SALT),
|
||||||
worldX,
|
worldX,
|
||||||
worldZ,
|
worldZ,
|
||||||
channelShape.roughnessWavelength()
|
channelShape.roughnessWavelength()
|
||||||
) * shape.channelRadius() * channelShape.wallRoughness();
|
) * shape.channelRadius() * channelShape.wallRoughness();
|
||||||
long bankSeed = HydrologyHash.mix(segment.courseId(), ORGANIC_SHAPE_SECOND_PHASE_SALT);
|
long bankSeed = HydrologyHash.mix(segment.courseId(), ORGANIC_SHAPE_SECOND_PHASE_SALT);
|
||||||
double bankNoise = signedOrganicNoise(
|
double bankNoise = SurfaceNoise.signed(
|
||||||
bankSeed,
|
bankSeed,
|
||||||
worldX,
|
worldX,
|
||||||
worldZ,
|
worldZ,
|
||||||
@@ -297,10 +299,6 @@ final class HydrologyChannelGeometry {
|
|||||||
return compiler.settings.geometry().surface();
|
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(
|
double deepPoolDistance(
|
||||||
FootprintLayerShape shape,
|
FootprintLayerShape shape,
|
||||||
HydraulicSegment segment,
|
HydraulicSegment segment,
|
||||||
@@ -331,32 +329,4 @@ final class HydrologyChannelGeometry {
|
|||||||
);
|
);
|
||||||
return normalizedDistance * radius / radialScale;
|
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.SurfaceBounds;
|
||||||
import art.arcane.iris.engine.hydrology.surface.SurfaceFootprintCompiler;
|
import art.arcane.iris.engine.hydrology.surface.SurfaceFootprintCompiler;
|
||||||
import art.arcane.iris.engine.hydrology.surface.SurfaceLayerColumn;
|
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.Long2IntOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||||
@@ -763,7 +764,7 @@ final class HydrologyFootprintCompiler {
|
|||||||
(normalized - thalwegFraction) / (1D - thalwegFraction)
|
(normalized - thalwegFraction) / (1D - thalwegFraction)
|
||||||
);
|
);
|
||||||
int maximumDepth = Math.max(1, shape.fluidHead() - shape.bed());
|
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),
|
HydrologyHash.mix(segment.id(), HydrologyChannelGeometry.ORGANIC_BED_VARIATION_SALT),
|
||||||
worldX,
|
worldX,
|
||||||
worldZ,
|
worldZ,
|
||||||
@@ -802,7 +803,7 @@ final class HydrologyFootprintCompiler {
|
|||||||
return shape.bed();
|
return shape.bed();
|
||||||
}
|
}
|
||||||
int lowerExtent = shape.fluidHead() - 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),
|
HydrologyHash.mix(segment.id(), HydrologyChannelGeometry.ORGANIC_BED_VARIATION_SALT),
|
||||||
worldX,
|
worldX,
|
||||||
worldZ,
|
worldZ,
|
||||||
@@ -833,7 +834,7 @@ final class HydrologyFootprintCompiler {
|
|||||||
int upperExtent = shape.ceiling() - shape.fluidHead();
|
int upperExtent = shape.ceiling() - shape.fluidHead();
|
||||||
double extent = upperExtent * scale;
|
double extent = upperExtent * scale;
|
||||||
if (channelShape.ceilingRoughness() > 0D) {
|
if (channelShape.ceilingRoughness() > 0D) {
|
||||||
double roughness = channelGeometry.signedOrganicNoise(
|
double roughness = SurfaceNoise.signed(
|
||||||
HydrologyHash.mix(segment.id(), HydrologyChannelGeometry.ORGANIC_CEILING_VARIATION_SALT),
|
HydrologyHash.mix(segment.id(), HydrologyChannelGeometry.ORGANIC_CEILING_VARIATION_SALT),
|
||||||
worldX,
|
worldX,
|
||||||
worldZ,
|
worldZ,
|
||||||
|
|||||||
+2
-484
@@ -1,8 +1,5 @@
|
|||||||
package art.arcane.iris.engine.hydrology.runtime;
|
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.IrisData;
|
||||||
import art.arcane.iris.core.loader.ResourceLoader;
|
import art.arcane.iris.core.loader.ResourceLoader;
|
||||||
import art.arcane.iris.engine.hydrology.HydrologyColumnLayer;
|
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.HydrologyTile;
|
||||||
import art.arcane.iris.engine.hydrology.HydrologyTileCache;
|
import art.arcane.iris.engine.hydrology.HydrologyTileCache;
|
||||||
import art.arcane.iris.engine.hydrology.HydrologyTileKey;
|
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.cave.HydrologyCavePlan;
|
||||||
import art.arcane.iris.engine.hydrology.policy.EffectiveRiverPolicy;
|
import art.arcane.iris.engine.hydrology.policy.EffectiveRiverPolicy;
|
||||||
import art.arcane.iris.engine.hydrology.policy.RiverConfinement;
|
import art.arcane.iris.engine.hydrology.policy.RiverConfinement;
|
||||||
import art.arcane.iris.engine.hydrology.policy.RiverPolicyResolver;
|
import art.arcane.iris.engine.hydrology.policy.RiverPolicyResolver;
|
||||||
import art.arcane.iris.engine.object.IrisBiome;
|
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.IrisDeepFluidConfig;
|
||||||
import art.arcane.iris.engine.object.IrisDimension;
|
|
||||||
import art.arcane.iris.engine.object.IrisHydrology;
|
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.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.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.IrisRiverProfile;
|
||||||
import art.arcane.iris.engine.object.IrisRiverRoutingConfig;
|
|
||||||
import art.arcane.iris.engine.object.IrisRiverRoutingMode;
|
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.IrisStyledRange;
|
||||||
import art.arcane.iris.engine.object.IrisSurfacePoolConfig;
|
|
||||||
import art.arcane.iris.engine.object.IrisSurfaceRiverBankConfig;
|
import art.arcane.iris.engine.object.IrisSurfaceRiverBankConfig;
|
||||||
import art.arcane.iris.engine.object.IrisSurfaceRiverChannelConfig;
|
import art.arcane.iris.engine.object.IrisSurfaceRiverChannelConfig;
|
||||||
import art.arcane.iris.engine.object.IrisSurfaceRiverConfig;
|
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.IrisUndergroundRiverConfig;
|
||||||
import art.arcane.iris.engine.object.IrisUndergroundRiverSourceConfig;
|
|
||||||
import art.arcane.iris.spi.IrisLogging;
|
import art.arcane.iris.spi.IrisLogging;
|
||||||
import art.arcane.iris.spi.IrisPlatforms;
|
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.common.parallel.MultiBurst;
|
||||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
import com.github.benmanes.caffeine.cache.Cache;
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.function.IntConsumer;
|
import java.util.function.IntConsumer;
|
||||||
import java.util.function.Predicate;
|
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 int BIOME_PATCH_SCALE = 32;
|
||||||
private static final long SURFACE_WIDTH_SALT = 0x5355524657494454L;
|
private static final long SURFACE_WIDTH_SALT = 0x5355524657494454L;
|
||||||
private static final long SURFACE_DEPTH_SALT = 0x5355524644455054L;
|
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_LEVEL_SALT = 0x554e4445524c564cL;
|
||||||
private static final long UNDERGROUND_WIDTH_SALT = 0x554e444552574944L;
|
private static final long UNDERGROUND_WIDTH_SALT = 0x554e444552574944L;
|
||||||
private static final long UNDERGROUND_DEPTH_SALT = 0x554e444552444550L;
|
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.defaultProfileKey = profileKeys.isEmpty() ? "default" : profileKeys.iterator().next();
|
||||||
this.terrainSampleLock = new Object();
|
this.terrainSampleLock = new Object();
|
||||||
this.terrainSamples = new LinkedHashMap<>(MAXIMUM_TERRAIN_SAMPLES, 0.75F, true);
|
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);
|
HydrologyGeometrySampler geometrySampler = geometrySampler(context, hydrology);
|
||||||
IrisHydrologyRoutingTerrainSampler.Sources terrainSources = new IrisHydrologyRoutingTerrainSampler.Sources(
|
IrisHydrologyRoutingTerrainSampler.Sources terrainSources = new IrisHydrologyRoutingTerrainSampler.Sources(
|
||||||
this::createTerrainBasis,
|
this::createTerrainBasis,
|
||||||
@@ -851,463 +822,10 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
|
|||||||
return range.stream(new RNG(context.seed() ^ salt), context.data());
|
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) {
|
private static int tileCoordinate(long blockCoordinate, int tileSize) {
|
||||||
return Math.toIntExact(Math.floorDiv(blockCoordinate, 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) {
|
private static Set<String> profileKeys(List<IrisRiverProfile> profiles) {
|
||||||
LinkedHashSet<String> keys = new LinkedHashSet<>();
|
LinkedHashSet<String> keys = new LinkedHashSet<>();
|
||||||
for (IrisRiverProfile profile : profiles) {
|
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 {
|
private static void writeValidated(IrisObject self, OutputStream o) throws IOException {
|
||||||
DataOutputStream dos = new DataOutputStream(o);
|
DataOutputStream dos = new DataOutputStream(o);
|
||||||
dos.writeInt(self.w);
|
writeHeader(self, dos);
|
||||||
dos.writeInt(self.h);
|
|
||||||
dos.writeInt(self.d);
|
|
||||||
dos.writeUTF("Iris V2 IOB;");
|
|
||||||
Palette palette = buildPalette(self);
|
Palette palette = buildPalette(self);
|
||||||
|
|
||||||
dos.writeShort(palette.size());
|
dos.writeShort(palette.size());
|
||||||
@@ -330,21 +327,13 @@ public final class IrisObjectIO {
|
|||||||
|
|
||||||
dos.writeInt(self.blocks.size());
|
dos.writeInt(self.blocks.size());
|
||||||
|
|
||||||
for (var entry : self.blocks) {
|
for (Map.Entry<IrisBlockVector, PlatformBlockState> entry : self.blocks) {
|
||||||
var i = entry.getKey();
|
writeBlock(dos, palette, entry);
|
||||||
dos.writeShort(i.getBlockX());
|
|
||||||
dos.writeShort(i.getBlockY());
|
|
||||||
dos.writeShort(i.getBlockZ());
|
|
||||||
dos.writeShort(palette.indexOf(entry.getValue().key()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dos.writeInt(self.states.size());
|
dos.writeInt(self.states.size());
|
||||||
for (var entry : self.states) {
|
for (Map.Entry<IrisBlockVector, TileData> entry : self.states) {
|
||||||
var i = entry.getKey();
|
writeState(dos, entry);
|
||||||
dos.writeShort(i.getBlockX());
|
|
||||||
dos.writeShort(i.getBlockY());
|
|
||||||
dos.writeShort(i.getBlockZ());
|
|
||||||
entry.getValue().toBinary(dos);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,10 +358,7 @@ public final class IrisObjectIO {
|
|||||||
public void execute() {
|
public void execute() {
|
||||||
try {
|
try {
|
||||||
DataOutputStream dos = new DataOutputStream(o);
|
DataOutputStream dos = new DataOutputStream(o);
|
||||||
dos.writeInt(self.w);
|
writeHeader(self, dos);
|
||||||
dos.writeInt(self.h);
|
|
||||||
dos.writeInt(self.d);
|
|
||||||
dos.writeUTF("Iris V2 IOB;");
|
|
||||||
|
|
||||||
Palette palette = buildPalette(self);
|
Palette palette = buildPalette(self);
|
||||||
c += self.blocks.size();
|
c += self.blocks.size();
|
||||||
@@ -387,22 +373,14 @@ public final class IrisObjectIO {
|
|||||||
|
|
||||||
dos.writeInt(self.blocks.size());
|
dos.writeInt(self.blocks.size());
|
||||||
|
|
||||||
for (var entry : self.blocks) {
|
for (Map.Entry<IrisBlockVector, PlatformBlockState> entry : self.blocks) {
|
||||||
var i = entry.getKey();
|
writeBlock(dos, palette, entry);
|
||||||
dos.writeShort(i.getBlockX());
|
|
||||||
dos.writeShort(i.getBlockY());
|
|
||||||
dos.writeShort(i.getBlockZ());
|
|
||||||
dos.writeShort(palette.indexOf(entry.getValue().key()));
|
|
||||||
++c;
|
++c;
|
||||||
}
|
}
|
||||||
|
|
||||||
dos.writeInt(self.states.size());
|
dos.writeInt(self.states.size());
|
||||||
for (var entry : self.states) {
|
for (Map.Entry<IrisBlockVector, TileData> entry : self.states) {
|
||||||
var i = entry.getKey();
|
writeState(dos, entry);
|
||||||
dos.writeShort(i.getBlockX());
|
|
||||||
dos.writeShort(i.getBlockY());
|
|
||||||
dos.writeShort(i.getBlockZ());
|
|
||||||
entry.getValue().toBinary(dos);
|
|
||||||
++c;
|
++c;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -436,6 +414,31 @@ public final class IrisObjectIO {
|
|||||||
throw ref.get();
|
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 {
|
static void write(IrisObject self, File file) throws IOException {
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
return;
|
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) {
|
public static PlatformBlockState resolve(IrisProceduralTree tree, IrisData data, TreeBlockCanvas.Cell cell, TreeBlockCanvas.Vec pos, RNG paletteRng) {
|
||||||
switch (cell.role()) {
|
return switch (cell.role()) {
|
||||||
case TRUNK -> {
|
case TRUNK -> finishTrunk(resolveBlock(tree.getTrunk(), tree.getTrunkPalette(), data, pos, paletteRng), cell);
|
||||||
PlatformBlockState state = resolveBlock(tree.getTrunk(), tree.getTrunkPalette(), data, pos, paletteRng);
|
|
||||||
return finishTrunk(state, cell);
|
|
||||||
}
|
|
||||||
case SECONDARY_TRUNK -> {
|
case SECONDARY_TRUNK -> {
|
||||||
PlatformBlockState state = resolveBlock(tree.getSecondaryTrunk(), tree.getSecondaryTrunkPalette(), data, pos, paletteRng);
|
PlatformBlockState state = resolveBlock(tree.getSecondaryTrunk(), tree.getSecondaryTrunkPalette(), data, pos, paletteRng);
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
state = resolveBlock(tree.getTrunk(), tree.getTrunkPalette(), data, pos, paletteRng);
|
state = resolveBlock(tree.getTrunk(), tree.getTrunkPalette(), data, pos, paletteRng);
|
||||||
}
|
}
|
||||||
return finishTrunk(state, cell);
|
yield finishTrunk(state, cell);
|
||||||
}
|
|
||||||
case LEAF -> {
|
|
||||||
return resolveBlock(tree.getLeaves(), tree.getLeavesPalette(), data, pos, paletteRng);
|
|
||||||
}
|
}
|
||||||
|
case LEAF -> resolveBlock(tree.getLeaves(), tree.getLeavesPalette(), data, pos, paletteRng);
|
||||||
case SECONDARY_LEAF -> {
|
case SECONDARY_LEAF -> {
|
||||||
PlatformBlockState state = resolveSecondaryLeaf(tree, data, pos, paletteRng);
|
PlatformBlockState state = resolveSecondaryLeaf(tree, data, pos, paletteRng);
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
state = resolveBlock(tree.getLeaves(), tree.getLeavesPalette(), data, pos, paletteRng);
|
state = resolveBlock(tree.getLeaves(), tree.getLeavesPalette(), data, pos, paletteRng);
|
||||||
}
|
}
|
||||||
return state;
|
yield state;
|
||||||
}
|
}
|
||||||
case DECORATOR -> {
|
case DECORATOR -> {
|
||||||
int index = cell.decoratorIndex();
|
int index = cell.decoratorIndex();
|
||||||
if (index < 0 || tree.getDecorators() == null || index >= tree.getDecorators().size()) {
|
if (index < 0 || tree.getDecorators() == null || index >= tree.getDecorators().size()) {
|
||||||
return null;
|
yield null;
|
||||||
}
|
}
|
||||||
IrisTreeDecorator dec = tree.getDecorators().get(index);
|
IrisTreeDecorator dec = tree.getDecorators().get(index);
|
||||||
PlatformBlockState state = resolveBlock(dec.getBlock(), dec.getPalette(), data, pos, paletteRng);
|
PlatformBlockState state = resolveBlock(dec.getBlock(), dec.getPalette(), data, pos, paletteRng);
|
||||||
@@ -68,12 +63,10 @@ public final class TreeBlockResolver {
|
|||||||
} catch (IllegalArgumentException ignored) {
|
} catch (IllegalArgumentException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return state;
|
yield state;
|
||||||
}
|
}
|
||||||
default -> {
|
default -> null;
|
||||||
return null;
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PlatformBlockState finishTrunk(PlatformBlockState state, TreeBlockCanvas.Cell cell) {
|
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) {
|
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);
|
return tree.getSecondaryLeavesPalette().get(paletteRng, pos.x(), pos.y(), pos.z(), data);
|
||||||
}
|
}
|
||||||
if (tree.getWeightedSecondaryLeaves() != null && !tree.getWeightedSecondaryLeaves().isEmpty()) {
|
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) {
|
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);
|
return palette.get(paletteRng, pos.x(), pos.y(), pos.z(), data);
|
||||||
}
|
}
|
||||||
if (block != null && !block.isEmpty()) {
|
if (block != null && !block.isEmpty()) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package art.arcane.iris.engine.object.tree;
|
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.IrisProceduralTree;
|
||||||
import art.arcane.iris.engine.object.IrisTreeBranches;
|
import art.arcane.iris.engine.object.IrisTreeBranches;
|
||||||
import art.arcane.iris.engine.object.IrisTreeCanopy;
|
import art.arcane.iris.engine.object.IrisTreeCanopy;
|
||||||
@@ -245,7 +246,7 @@ public final class TreeCanopyBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static TreeBlockCanvas.Role resolveLeaf(IrisProceduralTree tree, RNG rng) {
|
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.getWeightedSecondaryLeaves() != null && !tree.getWeightedSecondaryLeaves().isEmpty())
|
||||||
|| (tree.getSecondaryLeaves() != null && !tree.getSecondaryLeaves().isEmpty());
|
|| (tree.getSecondaryLeaves() != null && !tree.getSecondaryLeaves().isEmpty());
|
||||||
if (!hasSecondary || tree.getSecondaryLeafFraction() <= 0.0) {
|
if (!hasSecondary || tree.getSecondaryLeafFraction() <= 0.0) {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package art.arcane.iris.engine.object.tree;
|
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.IrisProceduralTree;
|
||||||
import art.arcane.iris.engine.object.IrisTreeDecorator;
|
import art.arcane.iris.engine.object.IrisTreeDecorator;
|
||||||
import art.arcane.volmlib.util.collection.KList;
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
@@ -40,7 +41,7 @@ public final class TreeDecoratorApplier {
|
|||||||
|
|
||||||
for (int idx = 0; idx < decorators.size(); idx++) {
|
for (int idx = 0; idx < decorators.size(); idx++) {
|
||||||
IrisTreeDecorator dec = decorators.get(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) {
|
if (!hasBlock) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
package art.arcane.iris.engine.object.tree;
|
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.IrisProceduralTree;
|
||||||
import art.arcane.iris.engine.object.IrisTreeAzimuthMode;
|
import art.arcane.iris.engine.object.IrisTreeAzimuthMode;
|
||||||
|
|
||||||
@@ -146,16 +146,12 @@ public final class TreeTrunkBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean hasSecondaryTrunk(IrisProceduralTree tree) {
|
private static boolean hasSecondaryTrunk(IrisProceduralTree tree) {
|
||||||
if (paletteSet(tree.getSecondaryTrunkPalette())) {
|
if (IrisProceduralBlocks.paletteSet(tree.getSecondaryTrunkPalette())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return tree.getSecondaryTrunk() != null && !tree.getSecondaryTrunk().isEmpty();
|
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) {
|
private static double[] leanOffset(IrisProceduralTree tree, int y, int height) {
|
||||||
if (tree.getLeanAngle() == 0.0 || height <= 1) {
|
if (tree.getLeanAngle() == 0.0 || height <= 1) {
|
||||||
return new double[]{0.0, 0.0};
|
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.nms.container.Pair;
|
||||||
import art.arcane.iris.core.service.ExternalDataSVC;
|
import art.arcane.iris.core.service.ExternalDataSVC;
|
||||||
import art.arcane.iris.engine.object.IrisObjectRotation;
|
import art.arcane.iris.engine.object.IrisObjectRotation;
|
||||||
|
import art.arcane.iris.platform.BlockStateKey;
|
||||||
import art.arcane.iris.spi.PlatformBlockState;
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
import art.arcane.iris.util.common.data.IrisCustomData;
|
import art.arcane.iris.util.common.data.IrisCustomData;
|
||||||
import art.arcane.iris.util.common.math.IrisBlockVector;
|
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.BlockFace;
|
||||||
import org.bukkit.block.data.BlockData;
|
import org.bukkit.block.data.BlockData;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -84,7 +84,7 @@ public final class BukkitBlockState implements PlatformBlockState {
|
|||||||
private BukkitBlockState(BlockData data, String key) {
|
private BukkitBlockState(BlockData data, String key) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.namespace = parseNamespace(key);
|
this.namespace = BlockStateKey.namespace(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BukkitBlockState of(BlockData data) {
|
public static BukkitBlockState of(BlockData data) {
|
||||||
@@ -175,44 +175,6 @@ public final class BukkitBlockState implements PlatformBlockState {
|
|||||||
return key.hashCode();
|
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
|
@Override
|
||||||
public String key() {
|
public String key() {
|
||||||
return key;
|
return key;
|
||||||
@@ -448,7 +410,7 @@ public final class BukkitBlockState implements PlatformBlockState {
|
|||||||
public PlatformBlockState withProperty(String name, String value) {
|
public PlatformBlockState withProperty(String name, String value) {
|
||||||
if (data instanceof IrisCustomData custom) {
|
if (data instanceof IrisCustomData custom) {
|
||||||
if (ExternalDataSVC.parseState(custom.getCustom()).getB().containsKey(name)) {
|
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);
|
BlockData resolved = BukkitBlockResolution.resolveOrNull(merged);
|
||||||
if (!(resolved instanceof IrisCustomData)) {
|
if (!(resolved instanceof IrisCustomData)) {
|
||||||
throw new IllegalArgumentException("Cannot resolve custom block state " + merged);
|
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
|
// 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.
|
// 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);
|
BlockData resolved = Bukkit.createBlockData(merged);
|
||||||
return of(IrisCustomData.of(resolved, custom.getCustom()));
|
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
|
@Override
|
||||||
|
|||||||
+9
-9
@@ -37,18 +37,11 @@ public class ObjectTargetHandler implements DirectorParameterHandler<String> {
|
|||||||
|
|
||||||
IrisData data = data();
|
IrisData data = data();
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
for (String k : data.getObjectLoader().getPossibleKeys()) {
|
collectObjectKeys(data, out, prefixes);
|
||||||
out.add(k);
|
|
||||||
collectPrefixes(k, prefixes);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
File packsFolder = IrisPlatforms.get().packsFolder();
|
File packsFolder = IrisPlatforms.get().packsFolder();
|
||||||
for (File pack : PackDirectoryResolver.listVisiblePackDirectories(packsFolder)) {
|
for (File pack : PackDirectoryResolver.listVisiblePackDirectories(packsFolder)) {
|
||||||
IrisData d = IrisData.get(pack);
|
collectObjectKeys(IrisData.get(pack), out, prefixes);
|
||||||
for (String k : d.getObjectLoader().getPossibleKeys()) {
|
|
||||||
out.add(k);
|
|
||||||
collectPrefixes(k, prefixes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +51,13 @@ public class ObjectTargetHandler implements DirectorParameterHandler<String> {
|
|||||||
return out;
|
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) {
|
private static void collectPrefixes(String key, Set<String> prefixes) {
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
while ((idx = key.indexOf('/', idx)) >= 0) {
|
while ((idx = key.indexOf('/', idx)) >= 0) {
|
||||||
|
|||||||
@@ -43,22 +43,12 @@ public class NullSafeStream<T> extends BasicStream<T> implements ProceduralStrea
|
|||||||
@Override
|
@Override
|
||||||
public T get(double x, double z) {
|
public T get(double x, double z) {
|
||||||
T t = stream.get(x, z);
|
T t = stream.get(x, z);
|
||||||
|
return t == null ? ifNull : t;
|
||||||
if (t == null) {
|
|
||||||
return ifNull;
|
|
||||||
}
|
|
||||||
|
|
||||||
return t;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T get(double x, double y, double z) {
|
public T get(double x, double y, double z) {
|
||||||
T t = stream.get(x, y, z);
|
T t = stream.get(x, y, z);
|
||||||
|
return t == null ? ifNull : t;
|
||||||
if (t == null) {
|
|
||||||
return ifNull;
|
|
||||||
}
|
|
||||||
|
|
||||||
return t;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,8 +64,27 @@ public class PackCompatReportTest {
|
|||||||
public void findingLineAndDedupKey() {
|
public void findingLineAndDedupKey() {
|
||||||
CompatFinding finding = finding(CompatAction.EXCLUDED, "cave/sulfur", "layers[0].palette[1]");
|
CompatFinding finding = finding(CompatAction.EXCLUDED, "cave/sulfur", "layers[0].palette[1]");
|
||||||
assertEquals("minecraft:sulfur (block): excluded Biome cave/sulfur at layers[0].palette[1]", finding.line());
|
assertEquals("minecraft:sulfur (block): excluded Biome cave/sulfur at layers[0].palette[1]", finding.line());
|
||||||
|
assertEquals("excluded Biome cave/sulfur at layers[0].palette[1]", finding.subjectLine());
|
||||||
assertEquals("BLOCK:minecraft:sulfur|EXCLUDED|Biome|cave/sulfur", finding.dedupKey());
|
assertEquals("BLOCK:minecraft:sulfur|EXCLUDED|Biome|cave/sulfur", finding.dedupKey());
|
||||||
CompatFinding blank = new CompatFinding(CompatRegistry.ENTITY, "minecraft:camel", CompatAction.DROPPED, null, null, null);
|
CompatFinding blank = new CompatFinding(CompatRegistry.ENTITY, "minecraft:camel", CompatAction.DROPPED, null, null, null);
|
||||||
assertEquals("minecraft:camel (entity): dropped", blank.line());
|
assertEquals("minecraft:camel (entity): dropped", blank.line());
|
||||||
|
assertEquals("dropped", blank.subjectLine());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findingRendersEachOptionalSubjectFieldIndependently() {
|
||||||
|
CompatFinding typeOnly = new CompatFinding(
|
||||||
|
CompatRegistry.BLOCK, "minecraft:sulfur", CompatAction.EXCLUDED, "Biome", "", "");
|
||||||
|
CompatFinding keyOnly = new CompatFinding(
|
||||||
|
CompatRegistry.BLOCK, "minecraft:sulfur", CompatAction.EXCLUDED, "", "cave/sulfur", "");
|
||||||
|
CompatFinding detailOnly = new CompatFinding(
|
||||||
|
CompatRegistry.BLOCK, "minecraft:sulfur", CompatAction.EXCLUDED, "", "", "layers[0]");
|
||||||
|
|
||||||
|
assertEquals("excluded Biome", typeOnly.subjectLine());
|
||||||
|
assertEquals("excluded cave/sulfur", keyOnly.subjectLine());
|
||||||
|
assertEquals("excluded at layers[0]", detailOnly.subjectLine());
|
||||||
|
assertEquals("minecraft:sulfur (block): excluded Biome", typeOnly.line());
|
||||||
|
assertEquals("minecraft:sulfur (block): excluded cave/sulfur", keyOnly.line());
|
||||||
|
assertEquals("minecraft:sulfur (block): excluded at layers[0]", detailOnly.line());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,9 @@ import java.io.File;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@@ -77,4 +80,29 @@ public class IrisDataAuthoringCacheTest {
|
|||||||
assertEquals(3.0, retained.getZoom(), 0.0);
|
assertEquals(3.0, retained.getZoom(), 0.0);
|
||||||
assertEquals("{\"zoom\":3.0}", Files.readString(epochRoot.resolve("generators/sample.json")));
|
assertEquals("{\"zoom\":3.0}", Files.readString(epochRoot.resolve("generators/sample.json")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resourceKeyDiscoveryPreservesNestedNamesAndCachedArrays() throws Exception {
|
||||||
|
Path root = temporary.newFolder("resource-keys").toPath();
|
||||||
|
for (String name : List.of(
|
||||||
|
"generators/plain.json", "generators/nested.json/ridge.json.alt.json", "generators/ignored.JSON",
|
||||||
|
"images/plain.png", "images/nested.png/ridge.png.alt.png", "images/ignored.PNG",
|
||||||
|
"matter/plain.mat", "matter/nested.mat/ridge.mat.alt.mat", "matter/ignored.MAT")) {
|
||||||
|
Path file = root.resolve(name);
|
||||||
|
Files.createDirectories(file.getParent());
|
||||||
|
Files.write(file, new byte[0]);
|
||||||
|
}
|
||||||
|
source = IrisData.get(root.toFile());
|
||||||
|
Map<ResourceLoader<?>, Set<String>> expected = Map.of(
|
||||||
|
source.getGeneratorLoader(), Set.of("plain", "nested/ridge.alt"),
|
||||||
|
source.getImageLoader(), Set.of("plain", "nested.png/ridge.alt"),
|
||||||
|
source.getMatterLoader(), Set.of("plain", "nested.mat/ridge.alt"));
|
||||||
|
|
||||||
|
for (Map.Entry<ResourceLoader<?>, Set<String>> entry : expected.entrySet()) {
|
||||||
|
String[] keys = entry.getKey().getPossibleKeys();
|
||||||
|
assertEquals(entry.getValue(), Set.of(keys));
|
||||||
|
assertEquals(entry.getValue().size(), keys.length);
|
||||||
|
assertSame(keys, entry.getKey().getPossibleKeys());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+27
@@ -703,6 +703,33 @@ public class StructureTransactionWriterTest {
|
|||||||
assertFalse(Files.exists(transactionRoot(root, token.transactionId())));
|
assertFalse(Files.exists(transactionRoot(root, token.transactionId())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void preparedRemovalRejectsMissingAndInvalidJournalsWithoutDeletingRecoveryData() throws IOException {
|
||||||
|
Path root = temporaryFolder.newFolder("owned-removal-invalid-journal").toPath();
|
||||||
|
StructureTransactionWriter writer = new StructureTransactionWriter(root);
|
||||||
|
StructureTransactionWriter.PreparedRemovalToken token =
|
||||||
|
new StructureTransactionWriter.PreparedRemovalToken(root, UUID.randomUUID());
|
||||||
|
Path transactionRoot = transactionRoot(token.packRoot(), token.transactionId());
|
||||||
|
Path journalPath = transactionRoot.resolve(StructureTransactionJournal.FILE_NAME);
|
||||||
|
Files.createDirectories(transactionRoot);
|
||||||
|
|
||||||
|
for (boolean commit : new boolean[]{false, true}) {
|
||||||
|
IOException failure = assertThrows(
|
||||||
|
IOException.class, () -> writer.resolvePreparedRemoval(token, commit));
|
||||||
|
assertEquals("Missing prepared removal journal at " + transactionRoot, failure.getMessage());
|
||||||
|
assertTrue(Files.isDirectory(transactionRoot));
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.writeString(journalPath, "not-json", StandardCharsets.UTF_8);
|
||||||
|
for (boolean commit : new boolean[]{false, true}) {
|
||||||
|
IOException failure = assertThrows(
|
||||||
|
IOException.class, () -> writer.resolvePreparedRemoval(token, commit));
|
||||||
|
assertEquals("Invalid prepared removal journal at " + journalPath, failure.getMessage());
|
||||||
|
assertTrue(failure.getCause() instanceof RuntimeException);
|
||||||
|
assertEquals("not-json", Files.readString(journalPath, StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void symbolicLinkAncestorsCannotEscapeThePackRoot() throws IOException {
|
public void symbolicLinkAncestorsCannotEscapeThePackRoot() throws IOException {
|
||||||
Path root = temporaryFolder.newFolder("symlink-pack").toPath();
|
Path root = temporaryFolder.newFolder("symlink-pack").toPath();
|
||||||
|
|||||||
+191
@@ -0,0 +1,191 @@
|
|||||||
|
package art.arcane.iris.engine.hydrology;
|
||||||
|
|
||||||
|
import art.arcane.iris.engine.hydrology.cave.CavePosition;
|
||||||
|
import art.arcane.iris.engine.hydrology.cave.CaveVoxel;
|
||||||
|
import art.arcane.iris.engine.hydrology.cave.CaveVoxelPrecondition;
|
||||||
|
import art.arcane.iris.engine.hydrology.cave.CaveVoxelView;
|
||||||
|
import art.arcane.iris.engine.hydrology.cave.HydrologyCaveAction;
|
||||||
|
import art.arcane.iris.engine.hydrology.cave.HydrologyCaveCandidate;
|
||||||
|
import art.arcane.iris.engine.hydrology.cave.HydrologyCaveFluidPolicy;
|
||||||
|
import art.arcane.iris.engine.hydrology.cave.HydrologyCaveGrottoShape;
|
||||||
|
import art.arcane.iris.engine.hydrology.cave.HydrologyCaveMode;
|
||||||
|
import art.arcane.iris.engine.hydrology.cave.HydrologyCavePlan;
|
||||||
|
import art.arcane.iris.engine.hydrology.cave.HydrologyCavePlannerSettings;
|
||||||
|
import art.arcane.iris.engine.hydrology.cave.HydrologyCaveRejection;
|
||||||
|
import art.arcane.iris.engine.hydrology.cave.HydrologyCaveSource;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.OptionalLong;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotSame;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
public class HydrologyCaveTerminalAlignmentTest {
|
||||||
|
private static final CavePosition SHARED = new CavePosition(0, 20, 0);
|
||||||
|
private static final List<HydrologyCaveAction> PRIORITY = List.of(
|
||||||
|
HydrologyCaveAction.FALLING_FLUID,
|
||||||
|
HydrologyCaveAction.WET_SOURCE,
|
||||||
|
HydrologyCaveAction.DRY_AIR,
|
||||||
|
HydrologyCaveAction.SEAL_GUARD
|
||||||
|
);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void candidatesPreserveActionOrderAndExposureIdentityForEveryPriorityPair() {
|
||||||
|
HydrologyCaveCourseFilter filter = filter();
|
||||||
|
Map<Long, RiverCourse> courses = Map.of(1L, course(1L, 10L), 2L, course(2L, 10L));
|
||||||
|
for (int firstPriority = 0; firstPriority < PRIORITY.size(); firstPriority++) {
|
||||||
|
for (int secondPriority = 0; secondPriority < PRIORITY.size(); secondPriority++) {
|
||||||
|
HydrologyCaveCandidate first = candidate(1L, PRIORITY.get(firstPriority));
|
||||||
|
HydrologyCaveCandidate second = candidate(2L, PRIORITY.get(secondPriority));
|
||||||
|
ArrayList<HydrologyCaveCandidate> candidates = new ArrayList<>(List.of(first, second));
|
||||||
|
Set<HydrologyCaveCandidate> exposed = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||||
|
exposed.add(first);
|
||||||
|
|
||||||
|
filter.alignSharedTerminalCandidateActions(candidates, courses, exposed);
|
||||||
|
|
||||||
|
HydrologyCaveAction expected = PRIORITY.get(Math.min(firstPriority, secondPriority));
|
||||||
|
assertEquals(expected, candidates.getFirst().actions().get(SHARED));
|
||||||
|
assertEquals(expected, candidates.getLast().actions().get(SHARED));
|
||||||
|
assertEquals(List.copyOf(first.actions().keySet()), List.copyOf(candidates.getFirst().actions().keySet()));
|
||||||
|
assertEquals(List.copyOf(second.actions().keySet()), List.copyOf(candidates.getLast().actions().keySet()));
|
||||||
|
assertEquals(PRIORITY.get(firstPriority), first.actions().get(SHARED));
|
||||||
|
assertEquals(PRIORITY.get(secondPriority), second.actions().get(SHARED));
|
||||||
|
assertEquals(1, exposed.size());
|
||||||
|
assertTrue(exposed.contains(candidates.getFirst()));
|
||||||
|
assertFalse(exposed.contains(candidates.getLast()));
|
||||||
|
if (firstPriority == secondPriority) {
|
||||||
|
assertSame(first, candidates.getFirst());
|
||||||
|
assertSame(second, candidates.getLast());
|
||||||
|
} else {
|
||||||
|
assertNotSame(first, candidates.getFirst());
|
||||||
|
assertNotSame(second, candidates.getLast());
|
||||||
|
assertFalse(exposed.contains(first));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void plansPreserveActionOrderAndBaselineForEveryPriorityPair() {
|
||||||
|
HydrologyCaveCourseFilter filter = filter();
|
||||||
|
Map<Long, RiverCourse> courses = Map.of(1L, course(1L, 10L), 2L, course(2L, 10L));
|
||||||
|
for (int firstPriority = 0; firstPriority < PRIORITY.size(); firstPriority++) {
|
||||||
|
for (int secondPriority = 0; secondPriority < PRIORITY.size(); secondPriority++) {
|
||||||
|
HydrologyCaveCandidate firstCandidate = candidate(1L, PRIORITY.get(firstPriority));
|
||||||
|
HydrologyCaveCandidate secondCandidate = candidate(2L, PRIORITY.get(secondPriority));
|
||||||
|
HydrologyCavePlan first = plan(firstCandidate);
|
||||||
|
HydrologyCavePlan second = plan(secondCandidate);
|
||||||
|
|
||||||
|
List<HydrologyCavePlan> aligned = filter.alignSharedTerminalPlanActions(
|
||||||
|
List.of(first, second), List.of(firstCandidate, secondCandidate), courses);
|
||||||
|
|
||||||
|
HydrologyCaveAction expected = PRIORITY.get(Math.min(firstPriority, secondPriority));
|
||||||
|
assertEquals(expected, aligned.getFirst().actions().get(SHARED));
|
||||||
|
assertEquals(expected, aligned.getLast().actions().get(SHARED));
|
||||||
|
assertEquals(List.copyOf(first.actions().keySet()), List.copyOf(aligned.getFirst().actions().keySet()));
|
||||||
|
assertEquals(List.copyOf(second.actions().keySet()), List.copyOf(aligned.getLast().actions().keySet()));
|
||||||
|
assertEquals(first.baselinePreconditions(), aligned.getFirst().baselinePreconditions());
|
||||||
|
assertEquals(second.baselinePreconditions(), aligned.getLast().baselinePreconditions());
|
||||||
|
assertEquals(List.copyOf(first.baselinePreconditions().keySet()),
|
||||||
|
List.copyOf(aligned.getFirst().baselinePreconditions().keySet()));
|
||||||
|
assertEquals(List.copyOf(second.baselinePreconditions().keySet()),
|
||||||
|
List.copyOf(aligned.getLast().baselinePreconditions().keySet()));
|
||||||
|
assertEquals(PRIORITY.get(firstPriority), first.actions().get(SHARED));
|
||||||
|
assertEquals(PRIORITY.get(secondPriority), second.actions().get(SHARED));
|
||||||
|
if (firstPriority == secondPriority) {
|
||||||
|
assertSame(first, aligned.getFirst());
|
||||||
|
assertSame(second, aligned.getLast());
|
||||||
|
} else {
|
||||||
|
assertNotSame(first, aligned.getFirst());
|
||||||
|
assertNotSame(second, aligned.getLast());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void unrelatedOutletsRetainCandidateIdentity() {
|
||||||
|
HydrologyCaveCandidate first = candidate(1L, HydrologyCaveAction.WET_SOURCE);
|
||||||
|
HydrologyCaveCandidate second = candidate(2L, HydrologyCaveAction.DRY_AIR);
|
||||||
|
ArrayList<HydrologyCaveCandidate> candidates = new ArrayList<>(List.of(first, second));
|
||||||
|
|
||||||
|
filter().alignSharedTerminalCandidateActions(candidates,
|
||||||
|
Map.of(1L, course(1L, 10L), 2L, course(2L, 20L)), Set.of());
|
||||||
|
|
||||||
|
assertSame(first, candidates.getFirst());
|
||||||
|
assertSame(second, candidates.getLast());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void incompatibleOrRejectedPlansRetainIdentity() {
|
||||||
|
HydrologyCaveCandidate firstCandidate = candidate(1L, HydrologyCaveAction.WET_SOURCE);
|
||||||
|
HydrologyCaveCandidate secondCandidate = candidate(2L, HydrologyCaveAction.DRY_AIR);
|
||||||
|
HydrologyCaveCandidate incompatible = new HydrologyCaveCandidate(secondCandidate.source(), "lava",
|
||||||
|
secondCandidate.settings(), false, secondCandidate.actions(), secondCandidate.intentionalOpenings());
|
||||||
|
HydrologyCavePlan first = plan(firstCandidate);
|
||||||
|
HydrologyCavePlan second = plan(secondCandidate);
|
||||||
|
HydrologyCavePlan rejected = new HydrologyCavePlan(secondCandidate.source(), HydrologyCaveRejection.OPEN_SURFACE,
|
||||||
|
Map.of(), Map.of(), OptionalLong.empty());
|
||||||
|
Map<Long, RiverCourse> courses = Map.of(1L, course(1L, 10L), 2L, course(2L, 10L));
|
||||||
|
|
||||||
|
List<HydrologyCavePlan> incompatibleResult = filter().alignSharedTerminalPlanActions(
|
||||||
|
List.of(first, second), List.of(firstCandidate, incompatible), courses);
|
||||||
|
List<HydrologyCavePlan> rejectedResult = filter().alignSharedTerminalPlanActions(
|
||||||
|
List.of(first, rejected), List.of(firstCandidate, secondCandidate), courses);
|
||||||
|
|
||||||
|
assertSame(first, incompatibleResult.getFirst());
|
||||||
|
assertSame(second, incompatibleResult.getLast());
|
||||||
|
assertSame(first, rejectedResult.getFirst());
|
||||||
|
assertSame(rejected, rejectedResult.getLast());
|
||||||
|
}
|
||||||
|
|
||||||
|
private HydrologyCaveCourseFilter filter() {
|
||||||
|
return new HydrologyCaveCourseFilter(mock(CaveVoxelView.class),
|
||||||
|
new HydrologyCaveCourseFilter.Options(false, 8192, 8192));
|
||||||
|
}
|
||||||
|
|
||||||
|
private HydrologyCaveCandidate candidate(long id, HydrologyCaveAction sharedAction) {
|
||||||
|
CavePosition own = new CavePosition((int) id, 20, 0);
|
||||||
|
LinkedHashMap<CavePosition, HydrologyCaveAction> actions = new LinkedHashMap<>();
|
||||||
|
if (id == 2L) {
|
||||||
|
actions.put(SHARED, sharedAction);
|
||||||
|
}
|
||||||
|
actions.put(own, HydrologyCaveAction.DRY_AIR);
|
||||||
|
actions.put(SHARED, sharedAction);
|
||||||
|
return new HydrologyCaveCandidate(
|
||||||
|
new HydrologyCaveSource(id, SHARED, SHARED, 20, HydrologyCaveMode.GENERATED_GROTTO),
|
||||||
|
"water", new HydrologyCavePlannerSettings(8, 8, 32, 1, 1, 1, 1, 0,
|
||||||
|
HydrologyCaveFluidPolicy.REJECT_EXISTING, HydrologyCaveGrottoShape.ELLIPSOID, 8, 8),
|
||||||
|
false, actions, Set.of(own));
|
||||||
|
}
|
||||||
|
|
||||||
|
private HydrologyCavePlan plan(HydrologyCaveCandidate candidate) {
|
||||||
|
LinkedHashMap<CavePosition, CaveVoxelPrecondition> baseline = new LinkedHashMap<>();
|
||||||
|
for (CavePosition position : candidate.actions().keySet()) {
|
||||||
|
baseline.put(position, new CaveVoxelPrecondition(CaveVoxel.SOLID, false));
|
||||||
|
}
|
||||||
|
baseline.put(new CavePosition(-(int) candidate.source().sourceId(), 20, 0),
|
||||||
|
new CaveVoxelPrecondition(CaveVoxel.SOLID, false));
|
||||||
|
return new HydrologyCavePlan(candidate.source(), HydrologyCaveRejection.NONE, candidate.actions(),
|
||||||
|
baseline, OptionalLong.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private RiverCourse course(long id, long outletId) {
|
||||||
|
HydraulicSegment segment = new HydraulicSegment(id, id, HydrologyFeatureType.UNDERGROUND_POOL,
|
||||||
|
20, 20, 2, 2, false, false, List.of(new HydrologyPoint(0, 20, 0)),
|
||||||
|
HydraulicChannelProfile.uniform(2, 2));
|
||||||
|
return new RiverCourse(id, RiverCourseType.UNDERGROUND, OptionalLong.of(id), OptionalLong.of(outletId),
|
||||||
|
"water", 1, List.of(), List.of(segment));
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
-27
@@ -27,7 +27,7 @@ import static org.mockito.ArgumentMatchers.any;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class IrisHydrologyRuntimeSettingsTest {
|
public class IrisHydrologySettingsCompilerTest {
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void regionalSurfaceControlsFingerprintAllScopesAndExpandSpacingReach() {
|
public void regionalSurfaceControlsFingerprintAllScopesAndExpandSpacingReach() {
|
||||||
@@ -47,20 +47,20 @@ public class IrisHydrologyRuntimeSettingsTest {
|
|||||||
when(biomes.loadAll(any(String[].class))).thenReturn(new KList<>(volcanic));
|
when(biomes.loadAll(any(String[].class))).thenReturn(new KList<>(volcanic));
|
||||||
|
|
||||||
HydrologyPlannerSettings baseline = settings(dimension);
|
HydrologyPlannerSettings baseline = settings(dimension);
|
||||||
HydrologyPlannerSettings regional = IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology(), () -> data);
|
HydrologyPlannerSettings regional = IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> data);
|
||||||
assertEquals(HydrologyPlannerSettings.SurfacePolicyBounds.NONE, baseline.surfacePolicyBounds());
|
assertEquals(HydrologyPlannerSettings.SurfacePolicyBounds.NONE, baseline.surfacePolicyBounds());
|
||||||
assertNotEquals(baseline.fingerprint(), regional.fingerprint());
|
assertNotEquals(baseline.fingerprint(), regional.fingerprint());
|
||||||
assertEquals(384, regional.maximumSurfaceSourceSpacing());
|
assertEquals(384, regional.maximumSurfaceSourceSpacing());
|
||||||
|
|
||||||
volcanic.getRiverPolicy().setSurfaceSourceSpacing(1600);
|
volcanic.getRiverPolicy().setSurfaceSourceSpacing(1600);
|
||||||
HydrologyPlannerSettings widened = IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology(), () -> data);
|
HydrologyPlannerSettings widened = IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> data);
|
||||||
assertNotEquals(regional.fingerprint(), widened.fingerprint());
|
assertNotEquals(regional.fingerprint(), widened.fingerprint());
|
||||||
assertEquals(1600, widened.maximumSurfaceSourceSpacing());
|
assertEquals(1600, widened.maximumSurfaceSourceSpacing());
|
||||||
assertTrue(widened.publicationRadius() >= 1600);
|
assertTrue(widened.publicationRadius() >= 1600);
|
||||||
assertEquals(widened.fingerprint(), IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology(), () -> data).fingerprint());
|
assertEquals(widened.fingerprint(), IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> data).fingerprint());
|
||||||
|
|
||||||
dimension.getRiverPolicy().setSurfaceInlandOutlets(7);
|
dimension.getRiverPolicy().setSurfaceInlandOutlets(7);
|
||||||
assertNotEquals(widened.fingerprint(), IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology(), () -> data).fingerprint());
|
assertNotEquals(widened.fingerprint(), IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> data).fingerprint());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -90,7 +90,7 @@ public class IrisHydrologyRuntimeSettingsTest {
|
|||||||
IrisRiverRoutingConfig routing = new IrisRiverRoutingConfig()
|
IrisRiverRoutingConfig routing = new IrisRiverRoutingConfig()
|
||||||
.setTileSize(256);
|
.setTileSize(256);
|
||||||
|
|
||||||
assertEquals(128, IrisHydrologyRuntime.maximumDeepChannelLength(deepFluid, routing));
|
assertEquals(128, IrisHydrologySettingsCompiler.maximumDeepChannelLength(deepFluid, routing));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -101,7 +101,7 @@ public class IrisHydrologyRuntimeSettingsTest {
|
|||||||
IrisRiverRoutingConfig routing = new IrisRiverRoutingConfig()
|
IrisRiverRoutingConfig routing = new IrisRiverRoutingConfig()
|
||||||
.setTileSize(256);
|
.setTileSize(256);
|
||||||
|
|
||||||
assertEquals(0, IrisHydrologyRuntime.maximumDeepChannelLength(deepFluid, routing));
|
assertEquals(0, IrisHydrologySettingsCompiler.maximumDeepChannelLength(deepFluid, routing));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -112,12 +112,12 @@ public class IrisHydrologyRuntimeSettingsTest {
|
|||||||
rivers.getGrottos().getInland().setEnabled(true).setConnectSurfaceRivers(true);
|
rivers.getGrottos().getInland().setEnabled(true).setConnectSurfaceRivers(true);
|
||||||
rivers.getRouting().getInlandOutlets().add(IrisRiverInlandOutlet.SINKHOLE_GROTTO);
|
rivers.getRouting().getInlandOutlets().add(IrisRiverInlandOutlet.SINKHOLE_GROTTO);
|
||||||
|
|
||||||
assertTrue(IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology(), () -> null)
|
assertTrue(IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> null)
|
||||||
.outlets().surfaceSinkholesEnabled());
|
.outlets().surfaceSinkholesEnabled());
|
||||||
|
|
||||||
rivers.getRouting().getInlandOutlets().clear();
|
rivers.getRouting().getInlandOutlets().clear();
|
||||||
|
|
||||||
assertFalse(IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology(), () -> null)
|
assertFalse(IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> null)
|
||||||
.outlets().surfaceSinkholesEnabled());
|
.outlets().surfaceSinkholesEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,13 +132,13 @@ public class IrisHydrologyRuntimeSettingsTest {
|
|||||||
rivers.getUnderground().getSources().setDensity(4.5D);
|
rivers.getUnderground().getSources().setDensity(4.5D);
|
||||||
rivers.getRouting().setSampleSpacing(64);
|
rivers.getRouting().setSampleSpacing(64);
|
||||||
|
|
||||||
assertEquals(5, IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology(), () -> null)
|
assertEquals(5, IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> null)
|
||||||
.surface().sources().maximumPerTile());
|
.surface().sources().maximumPerTile());
|
||||||
assertEquals(384, IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology(), () -> null)
|
assertEquals(384, IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> null)
|
||||||
.surface().sources().minimumSpacing());
|
.surface().sources().minimumSpacing());
|
||||||
assertEquals(5, IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology(), () -> null)
|
assertEquals(5, IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> null)
|
||||||
.underground().sources().maximumPerTile());
|
.underground().sources().maximumPerTile());
|
||||||
assertEquals(512, IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology(), () -> null)
|
assertEquals(512, IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> null)
|
||||||
.underground().sources().minimumSpacing());
|
.underground().sources().minimumSpacing());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ public class IrisHydrologyRuntimeSettingsTest {
|
|||||||
.setSlopePenalty(3D)
|
.setSlopePenalty(3D)
|
||||||
.setConfluenceAttraction(0.4D);
|
.setConfluenceAttraction(0.4D);
|
||||||
|
|
||||||
HydrologyPlannerSettings settings = IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology(), () -> null);
|
HydrologyPlannerSettings settings = IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> null);
|
||||||
HydrologyPlannerSettings.Banks banks = settings.surface().banks();
|
HydrologyPlannerSettings.Banks banks = settings.surface().banks();
|
||||||
|
|
||||||
assertEquals(2, banks.sink());
|
assertEquals(2, banks.sink());
|
||||||
@@ -220,8 +220,7 @@ public class IrisHydrologyRuntimeSettingsTest {
|
|||||||
.setMaximumOutletsPerTile(2)
|
.setMaximumOutletsPerTile(2)
|
||||||
.setMaximumCoastalOutletsPerTile(5);
|
.setMaximumCoastalOutletsPerTile(5);
|
||||||
|
|
||||||
HydrologyPlannerSettings.Outlets outlets = IrisHydrologyRuntime
|
HydrologyPlannerSettings.Outlets outlets = IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> null)
|
||||||
.createSettings(dimension, dimension.getHydrology(), () -> null)
|
|
||||||
.outlets();
|
.outlets();
|
||||||
|
|
||||||
assertEquals(2, outlets.maximumPerTile());
|
assertEquals(2, outlets.maximumPerTile());
|
||||||
@@ -252,8 +251,7 @@ public class IrisHydrologyRuntimeSettingsTest {
|
|||||||
.setBasinWidthRatio(2.1D)
|
.setBasinWidthRatio(2.1D)
|
||||||
.setMaximumBasinDepth(9);
|
.setMaximumBasinDepth(9);
|
||||||
|
|
||||||
HydrologyPlannerSettings.Geometry geometry = IrisHydrologyRuntime
|
HydrologyPlannerSettings.Geometry geometry = IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> null)
|
||||||
.createSettings(dimension, dimension.getHydrology(), () -> null)
|
|
||||||
.geometry();
|
.geometry();
|
||||||
|
|
||||||
assertEquals(47, geometry.meanders().primaryWavelength());
|
assertEquals(47, geometry.meanders().primaryWavelength());
|
||||||
@@ -284,12 +282,12 @@ public class IrisHydrologyRuntimeSettingsTest {
|
|||||||
coastal.setEnabled(true);
|
coastal.setEnabled(true);
|
||||||
coastal.getSeaCaves().setEnabled(true).setMaximumPerTile(5).setMinimumSpacing(200).setMinimumCoastHeight(9).setDepth(20);
|
coastal.getSeaCaves().setEnabled(true).setMaximumPerTile(5).setMinimumSpacing(200).setMinimumCoastHeight(9).setDepth(20);
|
||||||
|
|
||||||
HydrologyPlannerSettings.SeaCaves mapped = IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology(), () -> null)
|
HydrologyPlannerSettings.SeaCaves mapped = IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> null)
|
||||||
.seaCaves();
|
.seaCaves();
|
||||||
assertEquals(HydrologyPlannerSettings.SeaCaves.of(true, 5, 200, 9, 20), mapped);
|
assertEquals(HydrologyPlannerSettings.SeaCaves.of(true, 5, 200, 9, 20), mapped);
|
||||||
|
|
||||||
coastal.setEnabled(false);
|
coastal.setEnabled(false);
|
||||||
HydrologyPlannerSettings.SeaCaves withoutCoastalGrottos = IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology(), () -> null)
|
HydrologyPlannerSettings.SeaCaves withoutCoastalGrottos = IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> null)
|
||||||
.seaCaves();
|
.seaCaves();
|
||||||
assertFalse(withoutCoastalGrottos.enabled());
|
assertFalse(withoutCoastalGrottos.enabled());
|
||||||
assertEquals(5, withoutCoastalGrottos.maximumPerTile());
|
assertEquals(5, withoutCoastalGrottos.maximumPerTile());
|
||||||
@@ -297,7 +295,7 @@ public class IrisHydrologyRuntimeSettingsTest {
|
|||||||
|
|
||||||
coastal.setEnabled(true);
|
coastal.setEnabled(true);
|
||||||
coastal.getSeaCaves().setEnabled(false);
|
coastal.getSeaCaves().setEnabled(false);
|
||||||
assertFalse(IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology(), () -> null).seaCaves().enabled());
|
assertFalse(IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> null).seaCaves().enabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -488,7 +486,7 @@ public class IrisHydrologyRuntimeSettingsTest {
|
|||||||
IrisDimension dimension = new IrisDimension().setRegions(new KList<>("valley"));
|
IrisDimension dimension = new IrisDimension().setRegions(new KList<>("valley"));
|
||||||
dimension.getRiverPolicy().setShoreWidth(4D);
|
dimension.getRiverPolicy().setShoreWidth(4D);
|
||||||
|
|
||||||
assertEquals(4D, IrisHydrologyRuntime.widestShoreBiomeWidth(dimension, () -> null, 1.5D), 0D);
|
assertEquals(4D, IrisHydrologySettingsCompiler.widestShoreBiomeWidth(dimension, () -> null, 1.5D), 0D);
|
||||||
assertEquals(4D, settings(dimension).widestShoreBiomeWidth(), 0D);
|
assertEquals(4D, settings(dimension).widestShoreBiomeWidth(), 0D);
|
||||||
|
|
||||||
IrisRegion valley = new IrisRegion().setRiverPolicy(new IrisRiverPolicy().setShoreBiomeWidth(6D));
|
IrisRegion valley = new IrisRegion().setRiverPolicy(new IrisRiverPolicy().setShoreBiomeWidth(6D));
|
||||||
@@ -503,18 +501,18 @@ public class IrisHydrologyRuntimeSettingsTest {
|
|||||||
when(biomeLoader.getPossibleKeys()).thenReturn(new String[]{"beach", "plain"});
|
when(biomeLoader.getPossibleKeys()).thenReturn(new String[]{"beach", "plain"});
|
||||||
when(biomeLoader.loadAll(any(String[].class))).thenReturn(new KList<>(beach, plain));
|
when(biomeLoader.loadAll(any(String[].class))).thenReturn(new KList<>(beach, plain));
|
||||||
|
|
||||||
assertEquals(9D, IrisHydrologyRuntime.widestShoreBiomeWidth(dimension, () -> data, 1.5D), 0D);
|
assertEquals(9D, IrisHydrologySettingsCompiler.widestShoreBiomeWidth(dimension, () -> data, 1.5D), 0D);
|
||||||
|
|
||||||
beach.getRiverPolicy().setShoreWidth(null).setShoreBiomeWidth(11D);
|
beach.getRiverPolicy().setShoreWidth(null).setShoreBiomeWidth(11D);
|
||||||
assertEquals(11D, IrisHydrologyRuntime.widestShoreBiomeWidth(dimension, () -> data, 1.5D), 0D);
|
assertEquals(11D, IrisHydrologySettingsCompiler.widestShoreBiomeWidth(dimension, () -> data, 1.5D), 0D);
|
||||||
|
|
||||||
valley.getRiverPolicy().setShoreWidth(12D);
|
valley.getRiverPolicy().setShoreWidth(12D);
|
||||||
assertEquals(12D, IrisHydrologyRuntime.widestShoreBiomeWidth(dimension, () -> data, 1.5D), 0D);
|
assertEquals(12D, IrisHydrologySettingsCompiler.widestShoreBiomeWidth(dimension, () -> data, 1.5D), 0D);
|
||||||
|
|
||||||
dimension.getRiverPolicy().setShoreWidth(null);
|
dimension.getRiverPolicy().setShoreWidth(null);
|
||||||
valley.getRiverPolicy().setShoreWidth(null).setShoreBiomeWidth(null);
|
valley.getRiverPolicy().setShoreWidth(null).setShoreBiomeWidth(null);
|
||||||
beach.getRiverPolicy().setShoreBiomeWidth(null);
|
beach.getRiverPolicy().setShoreBiomeWidth(null);
|
||||||
assertEquals(1.5D, IrisHydrologyRuntime.widestShoreBiomeWidth(dimension, () -> data, 1.5D), 0D);
|
assertEquals(1.5D, IrisHydrologySettingsCompiler.widestShoreBiomeWidth(dimension, () -> data, 1.5D), 0D);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -537,6 +535,6 @@ public class IrisHydrologyRuntimeSettingsTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static HydrologyPlannerSettings settings(IrisDimension dimension) {
|
private static HydrologyPlannerSettings settings(IrisDimension dimension) {
|
||||||
return IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology(), () -> null);
|
return IrisHydrologySettingsCompiler.compile(dimension, dimension.getHydrology(), () -> null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,39 @@ import static org.junit.Assert.assertEquals;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class SurfaceNoiseTest {
|
public class SurfaceNoiseTest {
|
||||||
|
@Test
|
||||||
|
public void scalarBitsPreserveChannelGeometry() {
|
||||||
|
long[][] samples = {
|
||||||
|
{0L, 0, 0, 1, 0x3fc9ff45ea7ce2bcL, 0xbfe3005d0ac18ea2L},
|
||||||
|
{77L, -40, 39, 1, 0x3fe1608c5d9f966eL, 0x3fb608c5d9f966e0L},
|
||||||
|
{77L, -40, 39, 2, 0x3fd12fd3b2255266L, 0xbfdda0589bb55b34L},
|
||||||
|
{77L, -40, 39, 3, 0x3fde59c9633f9d3eL, 0xbfaa6369cc062c20L},
|
||||||
|
{77L, -40, 39, 7, 0x3febee1ee702b638L, 0x3fe7dc3dce056c70L},
|
||||||
|
{77L, -40, 39, 12, 0x3fe58a3d8c6a3a26L, 0x3fd628f631a8e898L},
|
||||||
|
{77L, -40, 39, 16, 0x3fe379956dd75dfbL, 0x3fcbccab6ebaefd8L},
|
||||||
|
{77L, -40, 39, 31, 0x3fdd9757689aa0dcL, 0xbfb34544bb2af920L},
|
||||||
|
{77L, -40, 39, 64, 0x3fe0968a66559de7L, 0x3fa2d14ccab3bce0L},
|
||||||
|
{0L, -12, -12, 12, 0x3fa2ed0fbd6e4760L, 0xbfeda25e08523714L},
|
||||||
|
{0L, -13, -11, 12, 0x3fabab89552088a4L, 0xbfec8a8ed55beeecL},
|
||||||
|
{-1L, 12, -12, 12, 0x3fc3b99ed5ce495cL, 0xbfe623309518db52L},
|
||||||
|
{Long.MIN_VALUE, -1, 1, 31, 0x3fe213950b5ed096L, 0x3fc09ca85af684b0L},
|
||||||
|
{Long.MAX_VALUE, 255, -257, 256, 0x3fcb1a5a870174baL, 0xbfe272d2bc7f45a3L},
|
||||||
|
{1337L, -12345, 67890, 8192, 0x3fd4e8d04021f6aaL, 0xbfd62e5f7fbc12acL},
|
||||||
|
{1337L, Integer.MIN_VALUE, Integer.MAX_VALUE, 1, 0x3fdc5d75fb3dcf0cL, 0xbfbd1450261187a0L},
|
||||||
|
{1337L, Integer.MIN_VALUE, Integer.MAX_VALUE, 12, 0x3feb8585bd4cd270L, 0x3fe70b0b7a99a4e0L},
|
||||||
|
{1337L, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, 0x3fed6ff84c27d2e8L, 0x3feadff0984fa5d0L}
|
||||||
|
};
|
||||||
|
for (long[] sample : samples) {
|
||||||
|
long seed = sample[0];
|
||||||
|
int x = (int) sample[1];
|
||||||
|
int z = (int) sample[2];
|
||||||
|
int wavelength = (int) sample[3];
|
||||||
|
String context = "seed=" + seed + ", x=" + x + ", z=" + z + ", wavelength=" + wavelength;
|
||||||
|
assertEquals(context, sample[4], Double.doubleToLongBits(SurfaceNoise.value(seed, x, z, wavelength)));
|
||||||
|
assertEquals(context, sample[5], Double.doubleToLongBits(SurfaceNoise.signed(seed, x, z, wavelength)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void valuesStayInRangeAndAreDeterministic() {
|
public void valuesStayInRangeAndAreDeterministic() {
|
||||||
for (int x = -40; x < 40; x++) {
|
for (int x = -40; x < 40; x++) {
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ package art.arcane.iris.engine.object;
|
|||||||
|
|
||||||
import art.arcane.iris.spi.PlatformBlockState;
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
import art.arcane.iris.util.common.math.IrisBlockVector;
|
import art.arcane.iris.util.common.math.IrisBlockVector;
|
||||||
|
import art.arcane.volmlib.util.collection.KMap;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -51,6 +54,39 @@ public class IrisObjectIoWriteLimitsTest {
|
|||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void preservesV2HeaderSignedCoordinatesAndTilePayload() throws IOException {
|
||||||
|
IrisObject object = new IrisObject(7, 9, 11);
|
||||||
|
object.blocks.put(new IrisBlockVector(-3, 2, -1), state("minecraft:chest"));
|
||||||
|
KMap<String, Object> properties = new KMap<>();
|
||||||
|
properties.put("marker", "stored");
|
||||||
|
object.states.put(new IrisBlockVector(-3, 2, -1), new TileData("minecraft:chest", properties));
|
||||||
|
ByteArrayOutputStream encoded = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
IrisObjectIO.write(object, encoded);
|
||||||
|
|
||||||
|
try (DataInputStream input = new DataInputStream(new ByteArrayInputStream(encoded.toByteArray()))) {
|
||||||
|
assertEquals(7, input.readInt());
|
||||||
|
assertEquals(9, input.readInt());
|
||||||
|
assertEquals(11, input.readInt());
|
||||||
|
assertEquals("Iris V2 IOB;", input.readUTF());
|
||||||
|
assertEquals(1, input.readShort());
|
||||||
|
assertEquals("minecraft:chest", input.readUTF());
|
||||||
|
assertEquals(1, input.readInt());
|
||||||
|
assertEquals(-3, input.readShort());
|
||||||
|
assertEquals(2, input.readShort());
|
||||||
|
assertEquals(-1, input.readShort());
|
||||||
|
assertEquals(0, input.readShort());
|
||||||
|
assertEquals(1, input.readInt());
|
||||||
|
assertEquals(-3, input.readShort());
|
||||||
|
assertEquals(2, input.readShort());
|
||||||
|
assertEquals(-1, input.readShort());
|
||||||
|
assertEquals("minecraft:chest", input.readUTF());
|
||||||
|
assertEquals("{\"marker\":\"stored\"}", input.readUTF());
|
||||||
|
assertEquals(-1, input.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rejectsPaletteOverflowInsteadOfWrappingTheShort() {
|
public void rejectsPaletteOverflowInsteadOfWrappingTheShort() {
|
||||||
IrisObject object = oversizedPaletteObject;
|
IrisObject object = oversizedPaletteObject;
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package art.arcane.iris.platform;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
|
public class BlockStateKeyTest {
|
||||||
|
@Test
|
||||||
|
public void namespaceIgnoresPropertyValues() {
|
||||||
|
assertEquals("minecraft", BlockStateKey.namespace("stone"));
|
||||||
|
assertEquals("minecraft", BlockStateKey.namespace("stone[variant=custom:stone]"));
|
||||||
|
assertEquals("custom", BlockStateKey.namespace("custom:stone[variant=minecraft:stone]"));
|
||||||
|
assertEquals("", BlockStateKey.namespace(":stone"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addsAndReplacesPropertiesInEncounterOrder() {
|
||||||
|
assertEquals("minecraft:stairs[facing=east]",
|
||||||
|
BlockStateKey.withProperty("minecraft:stairs", "facing", "east"));
|
||||||
|
assertEquals("minecraft:stairs[facing=east,half=top]",
|
||||||
|
BlockStateKey.withProperty("minecraft:stairs[facing=north,half=top]", "facing", "east"));
|
||||||
|
assertEquals("minecraft:stairs[facing=north,half=top,waterlogged=true]",
|
||||||
|
BlockStateKey.withProperty("minecraft:stairs[facing=north,half=top]", "waterlogged", "true"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void retainsDuplicateAndMalformedEntryHandling() {
|
||||||
|
assertEquals("custom:block[a=last,b=left=right,c=value]",
|
||||||
|
BlockStateKey.withProperty("custom:block[ a = first ,invalid,b=left=right,a=last]", "c", "value"));
|
||||||
|
assertEquals("stone[x=y]", BlockStateKey.withProperty("stone[]", "x", "y"));
|
||||||
|
assertThrows(StringIndexOutOfBoundsException.class,
|
||||||
|
() -> BlockStateKey.withProperty("stone[x=y", "x", "z"));
|
||||||
|
assertThrows(NullPointerException.class, () -> BlockStateKey.namespace(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -211,21 +211,7 @@ public final class GenerationOrderProbe {
|
|||||||
System.out.println(LOG_PREFIX + " FAIL: probe execution failed");
|
System.out.println(LOG_PREFIX + " FAIL: probe execution failed");
|
||||||
failure.printStackTrace(System.out);
|
failure.printStackTrace(System.out);
|
||||||
AggregateSignature unavailable = new AggregateSignature("unavailable", "unavailable", "unavailable");
|
AggregateSignature unavailable = new AggregateSignature("unavailable", "unavailable", "unavailable");
|
||||||
ProbeResult failed = new ProbeResult(
|
ProbeResult failed = result(configuration, "FAIL", unavailable);
|
||||||
"FAIL",
|
|
||||||
configuration.dimensionKey(),
|
|
||||||
configuration.seed(),
|
|
||||||
configuration.chunkCount(),
|
|
||||||
configuration.minimumChunkX(),
|
|
||||||
configuration.maximumChunkX(),
|
|
||||||
configuration.minimumChunkZ(),
|
|
||||||
configuration.maximumChunkZ(),
|
|
||||||
configuration.parallelism(),
|
|
||||||
configuration.shuffleSeed(),
|
|
||||||
configuration.multicore(),
|
|
||||||
configuration.studio(),
|
|
||||||
unavailable
|
|
||||||
);
|
|
||||||
System.out.println(failed.machineLine());
|
System.out.println(failed.machineLine());
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
@@ -264,11 +250,12 @@ public final class GenerationOrderProbe {
|
|||||||
for (Map.Entry<ChunkCoordinate, ChunkHash> entry : ordered.entrySet()) {
|
for (Map.Entry<ChunkCoordinate, ChunkHash> entry : ordered.entrySet()) {
|
||||||
ChunkCoordinate coordinate = entry.getKey();
|
ChunkCoordinate coordinate = entry.getKey();
|
||||||
ChunkHash hash = entry.getValue();
|
ChunkHash hash = entry.getValue();
|
||||||
updateDigest(blocks, coordinate.x() + "," + coordinate.z());
|
byte[] encodedCoordinate = (coordinate.x() + "," + coordinate.z()).getBytes(StandardCharsets.UTF_8);
|
||||||
|
updateDigest(blocks, encodedCoordinate);
|
||||||
updateDigest(blocks, hash.blocks());
|
updateDigest(blocks, hash.blocks());
|
||||||
updateDigest(biomes, coordinate.x() + "," + coordinate.z());
|
updateDigest(biomes, encodedCoordinate);
|
||||||
updateDigest(biomes, hash.biomes());
|
updateDigest(biomes, hash.biomes());
|
||||||
updateDigest(combined, coordinate.x() + "," + coordinate.z());
|
updateDigest(combined, encodedCoordinate);
|
||||||
updateDigest(combined, hash.combined());
|
updateDigest(combined, hash.combined());
|
||||||
}
|
}
|
||||||
return new AggregateSignature(
|
return new AggregateSignature(
|
||||||
@@ -391,8 +378,12 @@ public final class GenerationOrderProbe {
|
|||||||
+ mismatches.size() + " chunk(s).");
|
+ mismatches.size() + " chunk(s).");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result(configuration, "PASS", baseline.signature());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProbeResult result(ProbeConfiguration configuration, String status, AggregateSignature signature) {
|
||||||
return new ProbeResult(
|
return new ProbeResult(
|
||||||
"PASS",
|
status,
|
||||||
configuration.dimensionKey(),
|
configuration.dimensionKey(),
|
||||||
configuration.seed(),
|
configuration.seed(),
|
||||||
configuration.chunkCount(),
|
configuration.chunkCount(),
|
||||||
@@ -404,7 +395,7 @@ public final class GenerationOrderProbe {
|
|||||||
configuration.shuffleSeed(),
|
configuration.shuffleSeed(),
|
||||||
configuration.multicore(),
|
configuration.multicore(),
|
||||||
configuration.studio(),
|
configuration.studio(),
|
||||||
baseline.signature()
|
signature
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,6 +102,11 @@ public final class GenerationOrderProbeTest {
|
|||||||
GenerationOrderProbe.aggregateSignature(forward),
|
GenerationOrderProbe.aggregateSignature(forward),
|
||||||
GenerationOrderProbe.aggregateSignature(reverse)
|
GenerationOrderProbe.aggregateSignature(reverse)
|
||||||
);
|
);
|
||||||
|
assertEquals(new GenerationOrderProbe.AggregateSignature(
|
||||||
|
"f5a500c97351e576fb335317c0a716c48c320b1304a1f4dc9658f1634b80de98",
|
||||||
|
"1bf3a08fecb828a69b7c504459d2a53b7d089aa9b16040000bb5e06f9e1e2b5f",
|
||||||
|
"d667a0482bb425cee8f7384284472ce915cfdd6a1b9a123fa8a58ee26b67f9f9"),
|
||||||
|
GenerationOrderProbe.aggregateSignature(forward));
|
||||||
reverse.put(first, new GenerationOrderProbe.ChunkHash("changed", "b", "changed"));
|
reverse.put(first, new GenerationOrderProbe.ChunkHash("changed", "b", "changed"));
|
||||||
assertNotEquals(
|
assertNotEquals(
|
||||||
GenerationOrderProbe.aggregateSignature(forward),
|
GenerationOrderProbe.aggregateSignature(forward),
|
||||||
|
|||||||
@@ -125,13 +125,7 @@ public final class IrisWireReader {
|
|||||||
* @throws ProtocolException if the length is negative or exceeds the remaining bytes
|
* @throws ProtocolException if the length is negative or exceeds the remaining bytes
|
||||||
*/
|
*/
|
||||||
public String readString() throws ProtocolException {
|
public String readString() throws ProtocolException {
|
||||||
int declaredLength = readVarInt();
|
int declaredLength = readLength("negative string length", "string length exceeds remaining frame bytes");
|
||||||
if (declaredLength < 0) {
|
|
||||||
throw new ProtocolException("negative string length");
|
|
||||||
}
|
|
||||||
if (declaredLength > remaining()) {
|
|
||||||
throw new ProtocolException("string length exceeds remaining frame bytes");
|
|
||||||
}
|
|
||||||
String value = new String(frame, position, declaredLength, StandardCharsets.UTF_8);
|
String value = new String(frame, position, declaredLength, StandardCharsets.UTF_8);
|
||||||
position += declaredLength;
|
position += declaredLength;
|
||||||
return value;
|
return value;
|
||||||
@@ -143,19 +137,24 @@ public final class IrisWireReader {
|
|||||||
* @throws ProtocolException if the length is negative or exceeds the remaining bytes
|
* @throws ProtocolException if the length is negative or exceeds the remaining bytes
|
||||||
*/
|
*/
|
||||||
public byte[] readBytes() throws ProtocolException {
|
public byte[] readBytes() throws ProtocolException {
|
||||||
int declaredLength = readVarInt();
|
int declaredLength = readLength("negative byte array length", "byte array length exceeds remaining frame bytes");
|
||||||
if (declaredLength < 0) {
|
|
||||||
throw new ProtocolException("negative byte array length");
|
|
||||||
}
|
|
||||||
if (declaredLength > remaining()) {
|
|
||||||
throw new ProtocolException("byte array length exceeds remaining frame bytes");
|
|
||||||
}
|
|
||||||
byte[] value = new byte[declaredLength];
|
byte[] value = new byte[declaredLength];
|
||||||
System.arraycopy(frame, position, value, 0, declaredLength);
|
System.arraycopy(frame, position, value, 0, declaredLength);
|
||||||
position += declaredLength;
|
position += declaredLength;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int readLength(String negativeMessage, String oversizedMessage) throws ProtocolException {
|
||||||
|
int declaredLength = readVarInt();
|
||||||
|
if (declaredLength < 0) {
|
||||||
|
throw new ProtocolException(negativeMessage);
|
||||||
|
}
|
||||||
|
if (declaredLength > remaining()) {
|
||||||
|
throw new ProtocolException(oversizedMessage);
|
||||||
|
}
|
||||||
|
return declaredLength;
|
||||||
|
}
|
||||||
|
|
||||||
private int remaining() {
|
private int remaining() {
|
||||||
return limit - position;
|
return limit - position;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,11 +104,7 @@ public final class IrisWireWriter {
|
|||||||
* Writes a varint-length-prefixed UTF-8 string. {@code value} must not be null.
|
* Writes a varint-length-prefixed UTF-8 string. {@code value} must not be null.
|
||||||
*/
|
*/
|
||||||
public void writeString(String value) {
|
public void writeString(String value) {
|
||||||
byte[] encoded = value.getBytes(StandardCharsets.UTF_8);
|
writeBytes(value.getBytes(StandardCharsets.UTF_8));
|
||||||
writeVarInt(encoded.length);
|
|
||||||
ensure(encoded.length);
|
|
||||||
System.arraycopy(encoded, 0, buffer, length, encoded.length);
|
|
||||||
length += encoded.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package art.arcane.iris.spi.protocol;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class IrisWirePayloadTest {
|
||||||
|
@Test
|
||||||
|
public void preservesMixedPayloadEncodingAndIndependentCopies() throws Exception {
|
||||||
|
IrisWireWriter writer = new IrisWireWriter();
|
||||||
|
byte[] payload = {0, (byte) 0xff};
|
||||||
|
writer.writeString("é\u0000");
|
||||||
|
writer.writeBytes(payload);
|
||||||
|
writer.writeString("");
|
||||||
|
writer.writeBytes(new byte[0]);
|
||||||
|
writer.writeInt(0x12345678);
|
||||||
|
payload[0] = 42;
|
||||||
|
|
||||||
|
byte[] frame = writer.toByteArray();
|
||||||
|
assertArrayEquals(new byte[]{3, (byte) 0xc3, (byte) 0xa9, 0, 2, 0, (byte) 0xff,
|
||||||
|
0, 0, 0x12, 0x34, 0x56, 0x78}, frame);
|
||||||
|
IrisWireReader reader = new IrisWireReader(frame);
|
||||||
|
assertEquals("é\u0000", reader.readString());
|
||||||
|
byte[] decoded = reader.readBytes();
|
||||||
|
assertArrayEquals(new byte[]{0, (byte) 0xff}, decoded);
|
||||||
|
decoded[0] = 42;
|
||||||
|
assertEquals(0, frame[5]);
|
||||||
|
assertEquals("", reader.readString());
|
||||||
|
assertArrayEquals(new byte[0], reader.readBytes());
|
||||||
|
assertEquals(0x12345678, reader.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void preservesPayloadLengthErrorsAndConsumesOnlyThePrefix() throws Exception {
|
||||||
|
byte[] negative = {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x0f, 1};
|
||||||
|
byte[] oversized = {2, 1};
|
||||||
|
IrisWireReader negativeString = new IrisWireReader(negative);
|
||||||
|
IrisWireReader negativeBytes = new IrisWireReader(negative);
|
||||||
|
IrisWireReader oversizedString = new IrisWireReader(oversized);
|
||||||
|
IrisWireReader oversizedBytes = new IrisWireReader(oversized);
|
||||||
|
|
||||||
|
assertEquals("negative string length", assertThrows(ProtocolException.class, negativeString::readString).getMessage());
|
||||||
|
assertEquals("negative byte array length", assertThrows(ProtocolException.class, negativeBytes::readBytes).getMessage());
|
||||||
|
assertEquals("string length exceeds remaining frame bytes",
|
||||||
|
assertThrows(ProtocolException.class, oversizedString::readString).getMessage());
|
||||||
|
assertEquals("byte array length exceeds remaining frame bytes",
|
||||||
|
assertThrows(ProtocolException.class, oversizedBytes::readBytes).getMessage());
|
||||||
|
assertTrue(negativeString.readBoolean());
|
||||||
|
assertTrue(negativeBytes.readBoolean());
|
||||||
|
assertTrue(oversizedString.readBoolean());
|
||||||
|
assertTrue(oversizedBytes.readBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void preservesFrameCapAndPrefixWrittenBeforePayloadOverflow() {
|
||||||
|
int maximumPayload = IrisProtocol.MAX_FRAME_BYTES - 3;
|
||||||
|
IrisWireWriter exactString = new IrisWireWriter();
|
||||||
|
IrisWireWriter exactBytes = new IrisWireWriter();
|
||||||
|
exactString.writeString("x".repeat(maximumPayload));
|
||||||
|
exactBytes.writeBytes(new byte[maximumPayload]);
|
||||||
|
assertEquals(IrisProtocol.MAX_FRAME_BYTES, exactString.toByteArray().length);
|
||||||
|
assertEquals(IrisProtocol.MAX_FRAME_BYTES, exactBytes.toByteArray().length);
|
||||||
|
|
||||||
|
IrisWireWriter oversizedString = new IrisWireWriter();
|
||||||
|
IrisWireWriter oversizedBytes = new IrisWireWriter();
|
||||||
|
IrisWireWriter prefix = new IrisWireWriter();
|
||||||
|
prefix.writeVarInt(maximumPayload + 1);
|
||||||
|
String message = "Iris protocol frame exceeds " + IrisProtocol.MAX_FRAME_BYTES + " byte cap";
|
||||||
|
assertEquals(message, assertThrows(IllegalStateException.class,
|
||||||
|
() -> oversizedString.writeString("x".repeat(maximumPayload + 1))).getMessage());
|
||||||
|
assertEquals(message, assertThrows(IllegalStateException.class,
|
||||||
|
() -> oversizedBytes.writeBytes(new byte[maximumPayload + 1])).getMessage());
|
||||||
|
assertArrayEquals(prefix.toByteArray(), oversizedString.toByteArray());
|
||||||
|
assertArrayEquals(prefix.toByteArray(), oversizedBytes.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rejectsNullPayloadsWithoutWriting() {
|
||||||
|
IrisWireWriter writer = new IrisWireWriter();
|
||||||
|
assertThrows(NullPointerException.class, () -> writer.writeString(null));
|
||||||
|
assertThrows(NullPointerException.class, () -> writer.writeBytes(null));
|
||||||
|
assertArrayEquals(new byte[0], writer.toByteArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user