This commit is contained in:
Brian Neumann-Fopiano
2026-07-18 18:12:04 -04:00
parent 0193f75daa
commit ad41b6795c
48 changed files with 1193 additions and 141 deletions
+4
View File
@@ -29,3 +29,7 @@ jobs:
run: ./gradlew :core:check :adapters:bukkit:plugin:test :spi:build :probe:test :probe:run :probe:deserializationProbe -PuseLocalVolmLib=false --console=plain --stacktrace
- name: Run modded shared tests
run: ./gradlew -p adapters/fabric test -PuseLocalVolmLib=false --console=plain --stacktrace
- name: Test modded artifact verifier
run: ./gradlew -p buildSrc test --console=plain --stacktrace
- name: Verify modded artifacts
run: ./gradlew verifyModdedArtifacts --no-parallel -PuseLocalVolmLib=false --console=plain --stacktrace
@@ -0,0 +1,176 @@
package art.arcane.iris.nativegen;
import net.minecraft.SharedConstants;
import net.minecraft.core.HolderSet;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.ScatteredFeaturePiece;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import net.minecraft.world.level.levelgen.structure.structures.JungleTemplePiece;
import net.minecraft.world.level.levelgen.structure.structures.JungleTempleStructure;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutPiece;
import net.minecraft.world.level.levelgen.structure.structures.SwampHutStructure;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class NativeStructurePostProcessorScatteredFeatureTest {
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void desertPyramidUsesTheHighestFootprintSurfaceAndLocksVanillaReanchor() {
TestDesertPyramidPiece piece = new TestDesertPyramidPiece(RandomSource.create(11L), 0, 0);
StructureStart start = desertStart(piece);
BoundingBox cachedBounds = start.getBoundingBox();
BoundingBox footprint = piece.getBoundingBox();
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false,
(x, z) -> x == footprint.maxX() && z == footprint.maxZ() ? 92 : 64);
assertEquals(29, offset);
assertSame(cachedBounds, start.getBoundingBox());
assertEquals(93, piece.getBoundingBox().minY());
assertEquals(93, start.getBoundingBox().minY());
assertTrue(piece.attemptVanillaAlignment(-2));
assertEquals(93, piece.getBoundingBox().minY());
}
@Test
public void jungleTempleHonorsConfiguredShiftAndLocksVanillaReanchor() {
TestJungleTemplePiece piece = new TestJungleTemplePiece(RandomSource.create(19L), 32, -16);
StructureStart start = jungleStart(piece);
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
start, "minecraft:jungle_pyramid", 4, 63, -64, 320, false,
(x, z) -> x == 32 && z == -16 ? 88 : 70);
assertEquals(29, offset);
assertEquals(93, piece.getBoundingBox().minY());
assertTrue(piece.attemptVanillaAlignment());
assertEquals(93, piece.getBoundingBox().minY());
}
@Test
public void repeatedAlignmentIsIdempotent() {
TestDesertPyramidPiece piece = new TestDesertPyramidPiece(RandomSource.create(23L), 0, 0);
StructureStart start = desertStart(piece);
int initialOffset = NativeStructurePostProcessor.applyVerticalPlacement(
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false, (x, z) -> 80);
int repeatedOffset = NativeStructurePostProcessor.applyVerticalPlacement(
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false, (x, z) -> 80);
assertEquals(17, initialOffset);
assertEquals(0, repeatedOffset);
assertEquals(81, piece.getBoundingBox().minY());
}
@Test
public void scatteredAlignmentStaysInsideWorldBounds() {
TestDesertPyramidPiece piece = new TestDesertPyramidPiece(RandomSource.create(29L), 0, 0);
StructureStart start = desertStart(piece);
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
start, "minecraft:desert_pyramid", 0, 63, -64, 320, false, (x, z) -> 318);
assertEquals(241, offset);
assertEquals(305, piece.getBoundingBox().minY());
assertEquals(319, start.getBoundingBox().maxY());
}
@Test
public void onlyPyramidsAndTemplesOptIntoScatteredSurfaceFitting() {
TestDesertPyramidPiece desert = new TestDesertPyramidPiece(RandomSource.create(31L), 0, 0);
TestJungleTemplePiece jungle = new TestJungleTemplePiece(RandomSource.create(37L), 0, 0);
SwampHutPiece swamp = new SwampHutPiece(RandomSource.create(41L), 0, 0);
StructureStart desertStart = desertStart(desert);
StructureStart jungleStart = jungleStart(jungle);
StructureStart swampStart = swampStart(swamp);
assertTrue(NativeStructurePostProcessor.requiresSurfaceTerrain(desertStart));
assertTrue(NativeStructurePostProcessor.requiresSurfaceTerrain(jungleStart));
assertFalse(NativeStructurePostProcessor.requiresSurfaceTerrain(swampStart));
AtomicInteger terrainQueries = new AtomicInteger();
int offset = NativeStructurePostProcessor.applyVerticalPlacement(
swampStart, "minecraft:swamp_hut", 0, 63, -64, 320, false,
(x, z) -> terrainQueries.incrementAndGet());
assertEquals(0, offset);
assertEquals(0, terrainQueries.get());
assertEquals(64, swamp.getBoundingBox().minY());
}
@Test
public void scatteredHeightFieldMatchesTheRuntimeContract() {
Field field = NativeStructurePostProcessor.resolveScatteredHeightPositionField();
assertEquals(ScatteredFeaturePiece.class, field.getDeclaringClass());
assertEquals(int.class, field.getType());
assertTrue(Modifier.isProtected(field.getModifiers()));
assertFalse(Modifier.isFinal(field.getModifiers()));
assertFalse(Modifier.isStatic(field.getModifiers()));
assertTrue(field.trySetAccessible());
}
private static StructureStart desertStart(StructurePiece piece) {
Structure structure = new DesertPyramidStructure(new Structure.StructureSettings(HolderSet.empty()));
return start(structure, piece);
}
private static StructureStart jungleStart(StructurePiece piece) {
Structure structure = new JungleTempleStructure(new Structure.StructureSettings(HolderSet.empty()));
return start(structure, piece);
}
private static StructureStart swampStart(StructurePiece piece) {
Structure structure = new SwampHutStructure(new Structure.StructureSettings(HolderSet.empty()));
return start(structure, piece);
}
private static StructureStart start(Structure structure, StructurePiece piece) {
return new StructureStart(
structure, new ChunkPos(0, 0), 0, new PiecesContainer(List.of(piece)));
}
private static final class TestDesertPyramidPiece extends DesertPyramidPiece {
private TestDesertPyramidPiece(RandomSource random, int west, int north) {
super(random, west, north);
}
private boolean attemptVanillaAlignment(int offset) {
return updateHeightPositionToLowestGroundHeight((LevelAccessor) null, offset);
}
}
private static final class TestJungleTemplePiece extends JungleTemplePiece {
private TestJungleTemplePiece(RandomSource random, int west, int north) {
super(random, west, north);
}
private boolean attemptVanillaAlignment() {
return updateAverageGroundHeight((LevelAccessor) null, null, 0);
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ def mainClass = 'art.arcane.iris.Iris'
def bootstrapperClass = 'art.arcane.iris.IrisBootstrap'
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate')
.orElse('com.github.VolmitSoftware:VolmLib:4c38988b344792a79f925f57f8a675bc85fc1bed')
.orElse('com.github.VolmitSoftware:VolmLib:d9026a7c8ebc391c8109f401ce79a0ce65df3969')
.get()
dependencies {
+4 -5
View File
@@ -31,7 +31,7 @@ file('../../gradle.properties').withInputStream { InputStream stream -> rootProp
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.2'))
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.2'))
String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').getOrElse(rootProperties.getProperty('fabricLoaderVersion', '0.19.3'))
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:4c38988b344792a79f925f57f8a675bc85fc1bed'))
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:d9026a7c8ebc391c8109f401ce79a0ce65df3969'))
Closure<String> irisArtifactName = { String platform, String targetVersion ->
return "Iris v${project.version} [${platform}] ${targetVersion}.jar"
}
@@ -204,6 +204,9 @@ processResources {
tasks.named('shadowJar', ShadowJar).configure {
doFirst {
delete(fileTree(layout.buildDirectory.dir('libs')) {
include('Iris v* [Fabric] *.jar')
})
delete(layout.buildDirectory.file("libs/Iris-${project.version}+mc${minecraftVersion}-fabric.jar"))
}
archiveFileName.set(irisArtifactName('Fabric', "${minecraftVersion}+${fabricLoaderVersion}"))
@@ -217,10 +220,6 @@ tasks.named('shadowJar', ShadowJar).configure {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
relocate('org.objectweb.asm', 'art.arcane.iris.shadow.asm')
relocate('io.sentry', 'art.arcane.iris.shadow.sentry')
relocate('com.sun.jna', 'art.arcane.iris.shadow.jna')
relocate('oshi', 'art.arcane.iris.shadow.oshi')
relocate('net.jpountz', 'art.arcane.iris.shadow.jpountz')
exclude('oshi.properties')
from(project.configurations.named('jij')) {
into('META-INF/jars')
rename { String fileName -> fileName.replaceAll(/-[0-9][^-]*\.jar$/, '.jar') }
Binary file not shown.
+11 -11
View File
@@ -1,5 +1,5 @@
[05:44:33] [Test worker/INFO]: Iris registered custom content provider 'iris_deferred_test'
[05:44:33] [Test worker/ERROR]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
[17:42:11] [Test worker/INFO]: Iris registered custom content provider 'iris_deferred_test'
[17:42:11] [Test worker/ERROR]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
@@ -44,7 +44,7 @@ java.lang.RuntimeException: cleanup failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
[05:44:33] [Test worker/ERROR]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
[17:42:11] [Test worker/ERROR]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
java.lang.RuntimeException: enable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:31)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
@@ -92,10 +92,10 @@ java.lang.RuntimeException: enable failed
Suppressed: java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32)
... 42 more
[05:44:33] [Test worker/ERROR]: [worldcheck] server stop request failed
[17:42:11] [Test worker/ERROR]: [worldcheck] server stop request failed
java.lang.IllegalStateException: stop request failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$stopRequestFailureForcesNonzeroResult$1(ModdedWorldCheckTest.java:235)
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:176)
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:179)
at art.arcane.iris.modded.ModdedWorldCheckTest.stopRequestFailureForcesNonzeroResult(ModdedWorldCheckTest.java:232)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
@@ -139,10 +139,10 @@ java.lang.IllegalStateException: stop request failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
[05:44:33] [Test worker/ERROR]: [worldcheck] waiting for server shutdown failed
[17:42:11] [Test worker/ERROR]: [worldcheck] waiting for server shutdown failed
java.lang.IllegalStateException: shutdown wait failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$shutdownWaitFailureForcesNonzeroExit$0(ModdedWorldCheckTest.java:261)
at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:191)
at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:194)
at art.arcane.iris.modded.ModdedWorldCheckTest.shutdownWaitFailureForcesNonzeroExit(ModdedWorldCheckTest.java:259)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
@@ -186,10 +186,10 @@ java.lang.IllegalStateException: shutdown wait failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
[05:44:33] [Test worker/ERROR]: [worldcheck] check failed
[17:42:11] [Test worker/ERROR]: [worldcheck] check failed
java.lang.IllegalStateException: check failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$thrownCheckStillRequestsStop$0(ModdedWorldCheckTest.java:221)
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:171)
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:174)
at art.arcane.iris.modded.ModdedWorldCheckTest.thrownCheckStillRequestsStop(ModdedWorldCheckTest.java:219)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
@@ -233,8 +233,8 @@ java.lang.IllegalStateException: check failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
[05:44:33] [Test worker/INFO]: Iris registered custom content provider 'iris_discovery_success'
[05:44:33] [Test worker/ERROR]: Iris custom content provider discovery failed
[17:42:11] [Test worker/INFO]: Iris registered custom content provider 'iris_discovery_success'
[17:42:11] [Test worker/ERROR]: Iris custom content provider discovery failed
java.lang.RuntimeException: provider init failed
at art.arcane.iris.modded.api.ModdedCustomContentRegistryTest.failedDiscoveryPublishesNothingAndPreservesTheCause(ModdedCustomContentRegistryTest.java:39)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
@@ -53,6 +53,10 @@ public final class FabricModdedLoader implements ModdedLoader {
return instance instanceof MinecraftServer server ? server : null;
}
@Override
public void invalidateLevelCache(MinecraftServer server) {
}
@Override
public boolean clientEnvironment() {
return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT;
+4 -5
View File
@@ -31,7 +31,7 @@ file('../../gradle.properties').withInputStream { InputStream stream -> rootProp
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.2'))
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.2'))
String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse(rootProperties.getProperty('forgeVersion', '26.2-65.0.4'))
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:4c38988b344792a79f925f57f8a675bc85fc1bed'))
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:d9026a7c8ebc391c8109f401ce79a0ce65df3969'))
Closure<String> loaderDisplayVersion = { String loaderVersion ->
String coordinatePrefix = "${minecraftVersion}-"
if (loaderVersion.startsWith(coordinatePrefix)) {
@@ -217,6 +217,9 @@ processResources {
tasks.named('shadowJar', ShadowJar).configure {
doFirst {
delete(fileTree(layout.buildDirectory.dir('libs')) {
include('Iris v* [Forge] *.jar')
})
delete(layout.buildDirectory.file("libs/Iris-${project.version}+mc${minecraftVersion}-forge.jar"))
}
manifest {
@@ -235,10 +238,6 @@ tasks.named('shadowJar', ShadowJar).configure {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
relocate('org.objectweb.asm', 'art.arcane.iris.shadow.asm')
relocate('io.sentry', 'art.arcane.iris.shadow.sentry')
relocate('com.sun.jna', 'art.arcane.iris.shadow.jna')
relocate('oshi', 'art.arcane.iris.shadow.oshi')
relocate('net.jpountz', 'art.arcane.iris.shadow.jpountz')
exclude('oshi.properties')
exclude('org/jspecify/**')
exclude('com/google/errorprone/**')
exclude('com/google/j2objc/**')
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+14 -14
View File
@@ -1,8 +1,8 @@
[16Jul2026 05:44:24.794] [Test worker/DEBUG] [io.netty.util.internal.logging.InternalLoggerFactory/]: Using SLF4J as the default logging framework
[16Jul2026 05:44:24.795] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.level: simple
[16Jul2026 05:44:24.795] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.targetRecords: 4
[16Jul2026 05:44:26.716] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test'
[16Jul2026 05:44:26.771] [Test worker/ERROR] [Iris/]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
[18Jul2026 17:42:16.031] [Test worker/DEBUG] [io.netty.util.internal.logging.InternalLoggerFactory/]: Using SLF4J as the default logging framework
[18Jul2026 17:42:16.033] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.level: simple
[18Jul2026 17:42:16.033] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.targetRecords: 4
[18Jul2026 17:42:18.043] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test'
[18Jul2026 17:42:18.104] [Test worker/ERROR] [Iris/]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -47,7 +47,7 @@ java.lang.RuntimeException: cleanup failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[16Jul2026 05:44:26.779] [Test worker/ERROR] [Iris/]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
[18Jul2026 17:42:18.110] [Test worker/ERROR] [Iris/]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
java.lang.RuntimeException: enable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:31) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -95,10 +95,10 @@ java.lang.RuntimeException: enable failed
Suppressed: java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
... 42 more
[16Jul2026 05:44:26.814] [Test worker/ERROR] [Iris/]: [worldcheck] server stop request failed
[18Jul2026 17:42:18.144] [Test worker/ERROR] [Iris/]: [worldcheck] server stop request failed
java.lang.IllegalStateException: stop request failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$stopRequestFailureForcesNonzeroResult$1(ModdedWorldCheckTest.java:235) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:176) ~[main/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:179) ~[main/:?]
at art.arcane.iris.modded.ModdedWorldCheckTest.stopRequestFailureForcesNonzeroResult(ModdedWorldCheckTest.java:232) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[?:?]
@@ -142,10 +142,10 @@ java.lang.IllegalStateException: stop request failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[16Jul2026 05:44:26.821] [Test worker/ERROR] [Iris/]: [worldcheck] waiting for server shutdown failed
[18Jul2026 17:42:18.147] [Test worker/ERROR] [Iris/]: [worldcheck] waiting for server shutdown failed
java.lang.IllegalStateException: shutdown wait failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$shutdownWaitFailureForcesNonzeroExit$0(ModdedWorldCheckTest.java:261) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:191) ~[main/:?]
at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:194) ~[main/:?]
at art.arcane.iris.modded.ModdedWorldCheckTest.shutdownWaitFailureForcesNonzeroExit(ModdedWorldCheckTest.java:259) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[?:?]
@@ -189,10 +189,10 @@ java.lang.IllegalStateException: shutdown wait failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[16Jul2026 05:44:26.826] [Test worker/ERROR] [Iris/]: [worldcheck] check failed
[18Jul2026 17:42:18.154] [Test worker/ERROR] [Iris/]: [worldcheck] check failed
java.lang.IllegalStateException: check failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$thrownCheckStillRequestsStop$0(ModdedWorldCheckTest.java:221) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:171) ~[main/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:174) ~[main/:?]
at art.arcane.iris.modded.ModdedWorldCheckTest.thrownCheckStillRequestsStop(ModdedWorldCheckTest.java:219) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[?:?]
@@ -236,8 +236,8 @@ java.lang.IllegalStateException: check failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[16Jul2026 05:44:26.832] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success'
[16Jul2026 05:44:26.833] [Test worker/ERROR] [Iris/]: Iris custom content provider discovery failed
[18Jul2026 17:42:18.160] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success'
[18Jul2026 17:42:18.161] [Test worker/ERROR] [Iris/]: Iris custom content provider discovery failed
java.lang.RuntimeException: provider init failed
at art.arcane.iris.modded.api.ModdedCustomContentRegistryTest.failedDiscoveryPublishesNothingAndPreservesTheCause(ModdedCustomContentRegistryTest.java:39) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
+11 -11
View File
@@ -1,5 +1,5 @@
[16Jul2026 05:44:26.716] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test'
[16Jul2026 05:44:26.771] [Test worker/ERROR] [Iris/]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
[18Jul2026 17:42:18.043] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test'
[18Jul2026 17:42:18.104] [Test worker/ERROR] [Iris/]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -44,7 +44,7 @@ java.lang.RuntimeException: cleanup failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[16Jul2026 05:44:26.779] [Test worker/ERROR] [Iris/]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
[18Jul2026 17:42:18.110] [Test worker/ERROR] [Iris/]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
java.lang.RuntimeException: enable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:31) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -92,10 +92,10 @@ java.lang.RuntimeException: enable failed
Suppressed: java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
... 42 more
[16Jul2026 05:44:26.814] [Test worker/ERROR] [Iris/]: [worldcheck] server stop request failed
[18Jul2026 17:42:18.144] [Test worker/ERROR] [Iris/]: [worldcheck] server stop request failed
java.lang.IllegalStateException: stop request failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$stopRequestFailureForcesNonzeroResult$1(ModdedWorldCheckTest.java:235) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:176) ~[main/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:179) ~[main/:?]
at art.arcane.iris.modded.ModdedWorldCheckTest.stopRequestFailureForcesNonzeroResult(ModdedWorldCheckTest.java:232) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[?:?]
@@ -139,10 +139,10 @@ java.lang.IllegalStateException: stop request failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[16Jul2026 05:44:26.821] [Test worker/ERROR] [Iris/]: [worldcheck] waiting for server shutdown failed
[18Jul2026 17:42:18.147] [Test worker/ERROR] [Iris/]: [worldcheck] waiting for server shutdown failed
java.lang.IllegalStateException: shutdown wait failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$shutdownWaitFailureForcesNonzeroExit$0(ModdedWorldCheckTest.java:261) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:191) ~[main/:?]
at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:194) ~[main/:?]
at art.arcane.iris.modded.ModdedWorldCheckTest.shutdownWaitFailureForcesNonzeroExit(ModdedWorldCheckTest.java:259) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[?:?]
@@ -186,10 +186,10 @@ java.lang.IllegalStateException: shutdown wait failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[16Jul2026 05:44:26.826] [Test worker/ERROR] [Iris/]: [worldcheck] check failed
[18Jul2026 17:42:18.154] [Test worker/ERROR] [Iris/]: [worldcheck] check failed
java.lang.IllegalStateException: check failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$thrownCheckStillRequestsStop$0(ModdedWorldCheckTest.java:221) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:171) ~[main/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:174) ~[main/:?]
at art.arcane.iris.modded.ModdedWorldCheckTest.thrownCheckStillRequestsStop(ModdedWorldCheckTest.java:219) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[?:?]
@@ -233,8 +233,8 @@ java.lang.IllegalStateException: check failed
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72) ~[?:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [gradle-worker.jar:?]
[16Jul2026 05:44:26.832] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success'
[16Jul2026 05:44:26.833] [Test worker/ERROR] [Iris/]: Iris custom content provider discovery failed
[18Jul2026 17:42:18.160] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success'
[18Jul2026 17:42:18.161] [Test worker/ERROR] [Iris/]: Iris custom content provider discovery failed
java.lang.RuntimeException: provider init failed
at art.arcane.iris.modded.api.ModdedCustomContentRegistryTest.failedDiscoveryPublishesNothingAndPreservesTheCause(ModdedCustomContentRegistryTest.java:39) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
@@ -53,6 +53,11 @@ public final class ForgeModdedLoader implements ModdedLoader {
return ServerLifecycleHooks.getCurrentServer();
}
@Override
public void invalidateLevelCache(MinecraftServer server) {
server.markWorldsDirty();
}
@Override
public boolean clientEnvironment() {
return FMLEnvironment.dist.isClient();
@@ -22,6 +22,7 @@ import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.PoolElementStructurePiece;
import net.minecraft.world.level.levelgen.structure.ScatteredFeaturePiece;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
@@ -31,6 +32,8 @@ import net.minecraft.world.level.levelgen.structure.pools.ListPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructurePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece;
import net.minecraft.world.level.levelgen.structure.structures.JungleTemplePiece;
import net.minecraft.world.level.levelgen.structure.structures.OceanMonumentPieces;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructurePlaceSettings;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
@@ -46,7 +49,9 @@ import java.util.Objects;
import java.util.function.IntBinaryOperator;
public final class NativeStructurePostProcessor {
private static final String DESERT_PYRAMID_ID = "minecraft:desert_pyramid";
private static final int FOUNDATION_VERTICAL_TOLERANCE = 1;
private static final String JUNGLE_PYRAMID_ID = "minecraft:jungle_pyramid";
private static final int MAX_BURIAL_COLUMNS = 2_000_000;
private static final int MONUMENT_BASE_BELOW_SEA_LEVEL = 24;
private static final String OCEAN_MONUMENT_ID = "minecraft:monument";
@@ -80,10 +85,34 @@ public final class NativeStructurePostProcessor {
return alignOceanMonumentToSeaLevel(
start, requestedOffset, seaLevel, worldMinY, worldMaxYExclusive);
}
if (isRaisedScatteredStructure(structureId)) {
return alignScatteredStructureToSurface(
start, structureId, requestedOffset, worldMinY, worldMaxYExclusive, surfaceHeight);
}
return applyVerticalShift(
start, requestedOffset, worldMinY, worldMaxYExclusive, underground, surfaceHeight);
}
static int alignScatteredStructureToSurface(StructureStart start, String structureId,
int configuredOffset, int worldMinY,
int worldMaxYExclusive,
IntBinaryOperator surfaceHeight) {
Objects.requireNonNull(surfaceHeight, "Scattered native structure requires a terrain height resolver");
ScatteredFeaturePiece piece = requireRaisedScatteredPiece(start, structureId);
BoundingBox bounds = start.getBoundingBox();
BoundingBox pieceBounds = piece.getBoundingBox();
int surfaceY = highestSurfaceY(pieceBounds, surfaceHeight);
int targetMinY = Math.addExact(Math.addExact(surfaceY, 1), configuredOffset);
int requestedMove = Math.subtractExact(targetMinY, pieceBounds.minY());
int offsetY = StructureVerticalBounds.clampOffset(
bounds.minY(), bounds.maxY(), requestedMove, worldMinY, worldMaxYExclusive);
if (offsetY != 0) {
moveStructureStart(start, bounds, offsetY);
}
setScatteredHeightPosition(piece, Math.max(0, piece.getBoundingBox().minY()));
return offsetY;
}
public static int applyVerticalShift(StructureStart start, int requestedOffset, int worldMinY,
int worldMaxYExclusive, boolean underground,
IntBinaryOperator surfaceHeight) {
@@ -137,6 +166,72 @@ public final class NativeStructurePostProcessor {
return OCEAN_MONUMENT_ID.equals(structureId);
}
private static boolean isRaisedScatteredStructure(String structureId) {
return DESERT_PYRAMID_ID.equals(structureId) || JUNGLE_PYRAMID_ID.equals(structureId);
}
private static ScatteredFeaturePiece requireRaisedScatteredPiece(StructureStart start,
String structureId) {
Objects.requireNonNull(start, "Scattered native structure start must not be null");
List<StructurePiece> pieces = start.getPieces();
if (pieces.size() != 1) {
throw new IllegalStateException(structureId + " must contain exactly one scattered piece, found "
+ pieces.size());
}
StructurePiece piece = pieces.get(0);
if (DESERT_PYRAMID_ID.equals(structureId) && piece instanceof DesertPyramidPiece desertPyramid) {
return desertPyramid;
}
if (JUNGLE_PYRAMID_ID.equals(structureId) && piece instanceof JungleTemplePiece jungleTemple) {
return jungleTemple;
}
throw new IllegalStateException(structureId + " contains unexpected piece "
+ piece.getClass().getName());
}
private static int highestSurfaceY(BoundingBox bounds, IntBinaryOperator surfaceHeight) {
int highestY = Integer.MIN_VALUE;
for (int x = bounds.minX(); x <= bounds.maxX(); x++) {
for (int z = bounds.minZ(); z <= bounds.maxZ(); z++) {
highestY = Math.max(highestY, surfaceHeight.applyAsInt(x, z));
}
}
if (highestY == Integer.MIN_VALUE) {
throw new IllegalStateException("Scattered native structure has an empty terrain footprint");
}
return highestY;
}
private static void setScatteredHeightPosition(ScatteredFeaturePiece piece, int heightPosition) {
try {
ScatteredHeightPositionAccess.FIELD.setInt(piece, heightPosition);
} catch (IllegalAccessException error) {
throw new IllegalStateException("Cannot lock scattered native structure height", error);
}
}
static Field resolveScatteredHeightPositionField() {
Field resolved = null;
for (Field field : ScatteredFeaturePiece.class.getDeclaredFields()) {
int modifiers = field.getModifiers();
if (Modifier.isStatic(modifiers) || field.getType() != int.class
|| !Modifier.isProtected(modifiers) || Modifier.isFinal(modifiers)) {
continue;
}
if (resolved != null) {
throw new IllegalStateException("ScatteredFeaturePiece has multiple mutable protected int fields");
}
resolved = field;
}
if (resolved == null) {
throw new IllegalStateException("ScatteredFeaturePiece height-position field is missing");
}
if (!resolved.trySetAccessible()) {
throw new IllegalStateException("ScatteredFeaturePiece height-position field is inaccessible");
}
return resolved;
}
private static OceanMonumentPieces.MonumentBuilding requireOceanMonumentBuilding(StructureStart start) {
Objects.requireNonNull(start, "Ocean monument start must not be null");
List<StructurePiece> pieces = start.getPieces();
@@ -383,11 +478,21 @@ public final class NativeStructurePostProcessor {
return List.copyOf(anchors);
}
private static boolean requiresSurfaceTerrain(StructureStart start) {
static boolean requiresSurfaceTerrain(StructureStart start) {
return start != null
&& start.isValid()
&& shouldPrepareSurfaceTerrain(
start.getStructure().terrainAdaptation(), start.getStructure().step());
&& (shouldPrepareSurfaceTerrain(
start.getStructure().terrainAdaptation(), start.getStructure().step())
|| containsRaisedScatteredPiece(start));
}
private static boolean containsRaisedScatteredPiece(StructureStart start) {
for (StructurePiece piece : start.getPieces()) {
if (piece instanceof DesertPyramidPiece || piece instanceof JungleTemplePiece) {
return true;
}
}
return false;
}
private static void fitSurfaceTerrain(WorldGenLevel world, BoundingBox area,
@@ -927,6 +1032,13 @@ public final class NativeStructurePostProcessor {
}
}
private static final class ScatteredHeightPositionAccess {
private static final Field FIELD = resolveScatteredHeightPositionField();
private ScatteredHeightPositionAccess() {
}
}
private static final class SinglePoolTemplateAccess {
private static final Field FIELD = resolveSinglePoolTemplateField();
@@ -104,7 +104,6 @@ public final class ModdedEngineBootstrap {
ModdedStartup.prepareForStartup();
IrisModdedChunkGenerator.startGenPool();
bindWorldGenerators(server);
ModdedStartup.runOnce(server);
services().enableAll();
ModdedProtocolHandler.start(server);
ModdedSentry.start(loader());
@@ -125,6 +124,7 @@ public final class ModdedEngineBootstrap {
public static void serverStarted(MinecraftServer server) {
bindWorldGenerators(server);
ModdedStartup.runOnce(server);
reconcileSpawn(server);
ModdedWorldCheck.serverStarted(server);
}
@@ -284,7 +284,8 @@ public final class ModdedEngineBootstrap {
IrisPlatforms.bind(created);
rollback.add(() -> restorePlatform(previousPlatform));
ModdedServerAccess previousServerAccess = ModdedDimensionManager.bindAccess(new ModdedServerLevels());
ModdedServerAccess previousServerAccess = ModdedDimensionManager.bindAccess(
new ModdedServerLevels(boundLoader::invalidateLevelCache));
rollback.add(() -> ModdedDimensionManager.restoreAccess(previousServerAccess));
IrisObjectRotation.StateRotator previousRotator = IrisObjectRotation.bindPlatformRotator(new ModdedStateRotator());
@@ -32,6 +32,8 @@ public interface ModdedLoader {
MinecraftServer currentServer();
void invalidateLevelCache(MinecraftServer server);
boolean clientEnvironment();
Path configDir();
@@ -24,9 +24,17 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.LevelStorageSource;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
public final class ModdedServerLevels implements ModdedServerAccess {
private final Consumer<MinecraftServer> levelCacheInvalidator;
public ModdedServerLevels(Consumer<MinecraftServer> levelCacheInvalidator) {
this.levelCacheInvalidator = Objects.requireNonNull(levelCacheInvalidator);
}
@Override
public Executor levelExecutor(MinecraftServer server) {
return server.executor;
@@ -39,17 +47,29 @@ public final class ModdedServerLevels implements ModdedServerAccess {
@Override
public ServerLevel putLevel(MinecraftServer server, ResourceKey<Level> key, ServerLevel level) {
return server.levels.put(key, level);
ServerLevel previous = server.levels.put(key, level);
if (previous != level) {
levelCacheInvalidator.accept(server);
}
return previous;
}
@Override
public ServerLevel putLevelIfAbsent(MinecraftServer server, ResourceKey<Level> key, ServerLevel level) {
return server.levels.putIfAbsent(key, level);
ServerLevel previous = server.levels.putIfAbsent(key, level);
if (previous == null) {
levelCacheInvalidator.accept(server);
}
return previous;
}
@Override
public ServerLevel removeLevel(MinecraftServer server, ResourceKey<Level> key) {
return server.levels.remove(key);
ServerLevel removed = server.levels.remove(key);
if (removed != null) {
levelCacheInvalidator.accept(server);
}
return removed;
}
@Override
@@ -66,7 +66,7 @@ public final class ModdedStartup {
}
public static void runOnce(MinecraftServer server) {
if (server == null) {
if (server == null || server.getPlayerList() == null) {
return;
}
prepareForStartup();
@@ -72,6 +72,44 @@ public class ModdedLifecycleFailureContractTest {
assertTrue(failure.contains(", e);"));
}
@Test
public void persistentReinjectionWaitsForPlayerServicesAndServerStarted() throws IOException {
String bootstrapSource = source("ModdedEngineBootstrap.java");
String start = method(bootstrapSource, "public static void start(");
String serverAboutToStart = method(bootstrapSource, "public static void serverAboutToStart(");
String serverStarted = method(bootstrapSource, "public static void serverStarted(");
assertFalse(start.contains("ModdedStartup.runOnce(server);"));
assertFalse(serverAboutToStart.contains("ModdedStartup.runOnce(server);"));
assertEquals(1, occurrences(serverStarted, "ModdedStartup.runOnce(server);"));
assertBefore(serverStarted, "bindWorldGenerators(server);", "ModdedStartup.runOnce(server);");
assertBefore(serverStarted, "ModdedStartup.runOnce(server);", "reconcileSpawn(server);");
String startupSource = source("ModdedStartup.java");
String runOnce = method(startupSource, "public static void runOnce(");
assertBefore(runOnce, "server.getPlayerList() == null", "STARTED.compareAndSet(false, true)");
}
@Test
public void dynamicLevelMutationsInvalidateLoaderTickCaches() throws IOException {
String levelsSource = source("ModdedServerLevels.java");
String put = method(levelsSource, "public ServerLevel putLevel(");
String putIfAbsent = method(levelsSource, "public ServerLevel putLevelIfAbsent(");
String remove = method(levelsSource, "public ServerLevel removeLevel(");
assertBefore(put, "server.levels.put(key, level);", "levelCacheInvalidator.accept(server);");
assertTrue(put.contains("if (previous != level)"));
assertBefore(putIfAbsent, "server.levels.putIfAbsent(key, level);", "levelCacheInvalidator.accept(server);");
assertTrue(putIfAbsent.contains("if (previous == null)"));
assertBefore(remove, "server.levels.remove(key);", "levelCacheInvalidator.accept(server);");
assertTrue(remove.contains("if (removed != null)"));
String loaderSource = source("ModdedLoader.java");
assertTrue(loaderSource.contains("void invalidateLevelCache(MinecraftServer server);"));
String bootstrapSource = source("ModdedEngineBootstrap.java");
assertTrue(bootstrapSource.contains("new ModdedServerLevels(boundLoader::invalidateLevelCache)"));
}
@Test
public void packDimensionLoadingNeverSuppressesTheFailureAsNull() throws IOException {
String source = source("ModdedDimensionManager.java");
+4 -5
View File
@@ -31,7 +31,7 @@ file('../../gradle.properties').withInputStream { InputStream stream -> rootProp
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.2'))
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.2'))
String neoForgeVersion = providers.gradleProperty('neoForgeVersion').getOrElse(rootProperties.getProperty('neoForgeVersion', '26.2.0.12-beta'))
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:4c38988b344792a79f925f57f8a675bc85fc1bed'))
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate').getOrElse(rootProperties.getProperty('volmLibCoordinate', 'com.github.VolmitSoftware:VolmLib:d9026a7c8ebc391c8109f401ce79a0ce65df3969'))
Closure<String> irisArtifactName = { String platform, String targetVersion ->
return "Iris v${project.version} [${platform}] ${targetVersion}.jar"
}
@@ -201,6 +201,9 @@ processResources {
tasks.named('shadowJar', ShadowJar).configure {
doFirst {
delete(fileTree(layout.buildDirectory.dir('libs')) {
include('Iris v* [NeoForge] *.jar')
})
delete(layout.buildDirectory.file("libs/Iris-${project.version}+mc${minecraftVersion}-neoforge.jar"))
}
archiveFileName.set(irisArtifactName('NeoForge', "${minecraftVersion}+${neoForgeVersion}"))
@@ -215,10 +218,6 @@ tasks.named('shadowJar', ShadowJar).configure {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
relocate('org.objectweb.asm', 'art.arcane.iris.shadow.asm')
relocate('io.sentry', 'art.arcane.iris.shadow.sentry')
relocate('com.sun.jna', 'art.arcane.iris.shadow.jna')
relocate('oshi', 'art.arcane.iris.shadow.oshi')
relocate('net.jpountz', 'art.arcane.iris.shadow.jpountz')
exclude('oshi.properties')
exclude('org/jspecify/**')
exclude('com/google/errorprone/**')
exclude('com/google/j2objc/**')
@@ -53,6 +53,11 @@ public final class NeoForgeModdedLoader implements ModdedLoader {
return ServerLifecycleHooks.getCurrentServer();
}
@Override
public void invalidateLevelCache(MinecraftServer server) {
server.markWorldsDirty();
}
@Override
public boolean clientEnvironment() {
return FMLEnvironment.getDist().isClient();
+71 -2
View File
@@ -52,7 +52,7 @@ String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').get
String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse('26.2-65.0.4')
String neoForgeVersion = providers.gradleProperty('neoForgeVersion').getOrElse('26.2.0.12-beta')
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate')
.orElse('com.github.VolmitSoftware:VolmLib:4c38988b344792a79f925f57f8a675bc85fc1bed')
.orElse('com.github.VolmitSoftware:VolmLib:d9026a7c8ebc391c8109f401ce79a0ce65df3969')
.get()
String useLocalVolmLib = providers.gradleProperty('useLocalVolmLib').getOrElse('true')
String localVolmLibDirectory = providers.gradleProperty('localVolmLibDirectory').getOrNull()
@@ -276,6 +276,75 @@ tasks.named('neoforgeJar').configure {
mustRunAfter(tasks.named('forgeJar'))
}
List<String> commonModdedArtifactEntries = [
'art/arcane/iris/engine/IrisEngine.class',
'art/arcane/iris/spi/IrisPlatform.class',
'art/arcane/volmlib/util/mantle/io/Lz4IOWorkerCodecSupport.class',
'art/arcane/iris/util/common/misc/getHardware.class'
]
tasks.register('verifyFabricArtifact') {
group = 'verification'
dependsOn('fabricJar')
File artifact = layout.projectDirectory.file("adapters/fabric/build/libs/${fabricArtifactName}").asFile
inputs.file(artifact)
doLast {
List<String> requiredEntries = commonModdedArtifactEntries + [
'fabric.mod.json',
'art/arcane/iris/fabric/IrisFabricBootstrap.class'
]
ModdedArtifactVerifier.verify(artifact, requiredEntries)
logger.lifecycle("Verified ${artifact.name} uses Minecraft-provided LZ4, OSHI, and JNA")
}
}
tasks.register('verifyForgeArtifact') {
group = 'verification'
dependsOn('forgeJar')
File artifact = layout.projectDirectory.file("adapters/forge/build/libs/${forgeArtifactName}").asFile
inputs.file(artifact)
doLast {
List<String> requiredEntries = commonModdedArtifactEntries + [
'META-INF/mods.toml',
'art/arcane/iris/forge/IrisForgeBootstrap.class'
]
ModdedArtifactVerifier.verify(artifact, requiredEntries)
logger.lifecycle("Verified ${artifact.name} uses Minecraft-provided LZ4, OSHI, and JNA")
}
}
tasks.register('verifyNeoforgeArtifact') {
group = 'verification'
dependsOn('neoforgeJar')
File artifact = layout.projectDirectory.file("adapters/neoforge/build/libs/${neoForgeArtifactName}").asFile
inputs.file(artifact)
doLast {
List<String> requiredEntries = commonModdedArtifactEntries + [
'META-INF/neoforge.mods.toml',
'art/arcane/iris/neoforge/IrisNeoForgeBootstrap.class'
]
ModdedArtifactVerifier.verify(artifact, requiredEntries)
logger.lifecycle("Verified ${artifact.name} uses Minecraft-provided LZ4, OSHI, and JNA")
}
}
tasks.register('verifyModdedArtifacts') {
group = 'verification'
dependsOn('verifyFabricArtifact', 'verifyForgeArtifact', 'verifyNeoforgeArtifact')
}
tasks.named('buildFabric').configure {
dependsOn('verifyFabricArtifact')
}
tasks.named('buildForge').configure {
dependsOn('verifyForgeArtifact')
}
tasks.named('buildNeoforge').configure {
dependsOn('verifyNeoforgeArtifact')
}
tasks.register('buildAllToOut') {
group = 'iris'
dependsOn('buildBukkit', 'buildFabric', 'buildForge', 'buildNeoforge', ':spi:jar')
@@ -295,7 +364,7 @@ tasks.register('buildAllToOut') {
tasks.register('buildAll') {
group = 'iris'
outputs.upToDateWhen { false }
dependsOn('iris', 'fabricJar', 'forgeJar', 'neoforgeJar')
dependsOn('iris', 'verifyModdedArtifacts')
doLast {
delete(
file("${bukkitConsumerPath}/Iris-Fabric.jar"),
+2
View File
@@ -19,7 +19,9 @@ repositories {
}
dependencies {
implementation(gradleApi())
implementation('org.ow2.asm:asm:9.10.1')
implementation('com.github.VolmitSoftware:NMSTools:c88961416f')
implementation('io.papermc.paperweight:paperweight-userdev:2.0.0-beta.21')
testImplementation('junit:junit:4.13.2')
}
@@ -0,0 +1,181 @@
import org.gradle.api.GradleException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
public final class ModdedArtifactVerifier {
private static final String CODEC_CLASS = "art/arcane/volmlib/util/mantle/io/Lz4IOWorkerCodecSupport.class";
private static final List<String> INTERNAL_LZ4_REFERENCES = List.of(
"net/jpountz/lz4/LZ4BlockInputStream",
"net/jpountz/lz4/LZ4BlockOutputStream"
);
private static final String INTERNAL_OSHI_REFERENCE = "oshi/SystemInfo";
private static final List<String> BUNDLED_RUNTIME_PREFIXES = List.of(
"net/jpountz/",
"oshi/",
"com/sun/jna/",
"art/arcane/iris/shadow/jpountz/",
"art/arcane/iris/shadow/oshi/",
"art/arcane/iris/shadow/jna/"
);
private static final List<String> PRIVATE_REFERENCE_PREFIXES = List.of(
"art/arcane/iris/shadow/jpountz",
"art/arcane/iris/shadow/oshi",
"art/arcane/iris/shadow/jna"
);
private ModdedArtifactVerifier() {
}
public static void verify(File artifact, List<String> requiredEntries) {
if (!artifact.isFile()) {
throw new GradleException("Missing modded Iris artifact: " + artifact.getAbsolutePath());
}
try (JarFile jar = new JarFile(artifact)) {
for (String requiredEntry : requiredEntries) {
if (jar.getJarEntry(requiredEntry) == null) {
throw new GradleException(artifact.getName() + " is missing " + requiredEntry);
}
}
Set<String> bundledRuntimeEntries = new LinkedHashSet<>();
Set<String> privateReferenceClasses = new LinkedHashSet<>();
Set<String> codecReferences = new LinkedHashSet<>();
boolean oshiReference = false;
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (entry.isDirectory()) {
continue;
}
String name = entry.getName();
if (startsWithAny(name, BUNDLED_RUNTIME_PREFIXES)) {
bundledRuntimeEntries.add(name);
}
if (isNestedJar(name)) {
inspectNestedJar(jar, entry, bundledRuntimeEntries, privateReferenceClasses);
}
if (!name.endsWith(".class")) {
continue;
}
String bytecode = readClass(jar, entry);
if (containsAny(bytecode, PRIVATE_REFERENCE_PREFIXES)) {
privateReferenceClasses.add(name);
}
if (bytecode.contains(INTERNAL_OSHI_REFERENCE)) {
oshiReference = true;
}
if (name.equals(CODEC_CLASS)) {
for (String reference : INTERNAL_LZ4_REFERENCES) {
if (bytecode.contains(reference)) {
codecReferences.add(reference);
}
}
}
}
if (!bundledRuntimeEntries.isEmpty()) {
throw new GradleException(artifact.getName() + " duplicates Minecraft runtime libraries: "
+ preview(bundledRuntimeEntries));
}
if (!privateReferenceClasses.isEmpty()) {
throw new GradleException(artifact.getName() + " contains unresolved Iris-private runtime references in: "
+ preview(privateReferenceClasses));
}
if (!codecReferences.containsAll(INTERNAL_LZ4_REFERENCES)) {
throw new GradleException(artifact.getName() + " does not link region storage to Minecraft's LZ4 runtime");
}
if (!oshiReference) {
throw new GradleException(artifact.getName() + " does not link hardware diagnostics to Minecraft's OSHI runtime");
}
} catch (IOException e) {
throw new GradleException("Unable to verify modded Iris artifact " + artifact.getAbsolutePath(), e);
}
}
private static String readClass(JarFile jar, JarEntry entry) throws IOException {
try (InputStream input = jar.getInputStream(entry)) {
return new String(input.readAllBytes(), StandardCharsets.ISO_8859_1);
}
}
private static boolean startsWithAny(String value, List<String> prefixes) {
for (String prefix : prefixes) {
if (value.startsWith(prefix)) {
return true;
}
}
return false;
}
private static boolean containsAny(String value, List<String> fragments) {
for (String fragment : fragments) {
if (value.contains(fragment)) {
return true;
}
}
return false;
}
private static void inspectNestedJar(JarFile jar, JarEntry nestedEntry,
Set<String> bundledRuntimeEntries,
Set<String> privateReferenceClasses) throws IOException {
String nestedName = nestedEntry.getName();
if (isNamedRuntimeLibrary(nestedName)) {
bundledRuntimeEntries.add(nestedName);
}
try (InputStream input = jar.getInputStream(nestedEntry);
JarInputStream nestedJar = new JarInputStream(input)) {
JarEntry entry;
while ((entry = nestedJar.getNextJarEntry()) != null) {
if (entry.isDirectory()) {
continue;
}
String name = entry.getName();
String path = nestedName + "!/" + name;
if (startsWithAny(name, BUNDLED_RUNTIME_PREFIXES)) {
bundledRuntimeEntries.add(path);
}
if (name.endsWith(".class")) {
String bytecode = new String(nestedJar.readAllBytes(), StandardCharsets.ISO_8859_1);
if (containsAny(bytecode, PRIVATE_REFERENCE_PREFIXES)) {
privateReferenceClasses.add(path);
}
}
}
}
}
private static boolean isNestedJar(String name) {
return name.endsWith(".jar")
&& (name.startsWith("META-INF/jars/") || name.startsWith("META-INF/jarjar/"));
}
private static boolean isNamedRuntimeLibrary(String name) {
if (!isNestedJar(name)) {
return false;
}
String fileName = name.substring(name.lastIndexOf('/') + 1).toLowerCase(Locale.ROOT);
return fileName.startsWith("lz4") || fileName.startsWith("oshi") || fileName.startsWith("jna");
}
private static String preview(Set<String> values) {
return String.join(", ", values.stream().limit(8).toList());
}
}
@@ -0,0 +1,130 @@
import org.gradle.api.GradleException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class ModdedArtifactVerifierTest {
private static final String METADATA = "fabric.mod.json";
private static final String CODEC_CLASS = "art/arcane/volmlib/util/mantle/io/Lz4IOWorkerCodecSupport.class";
private static final String HARDWARE_CLASS = "art/arcane/iris/util/common/misc/getHardware.class";
private static final List<String> REQUIRED_ENTRIES = List.of(METADATA, CODEC_CLASS, HARDWARE_CLASS);
private static final byte[] INTERNAL_CODEC = (
"net/jpountz/lz4/LZ4BlockInputStream net/jpountz/lz4/LZ4BlockOutputStream"
).getBytes(StandardCharsets.ISO_8859_1);
@Rule
public final TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
public void acceptsMinecraftRuntimeReferences() throws Exception {
Map<String, byte[]> entries = validEntries();
File artifact = createArtifact(entries);
ModdedArtifactVerifier.verify(artifact, REQUIRED_ENTRIES);
}
@Test
public void rejectsRelocatedRuntimeReference() throws Exception {
Map<String, byte[]> entries = validEntries();
entries.put("art/arcane/iris/Test.class",
"art/arcane/iris/shadow/jpountz/lz4/LZ4BlockInputStream"
.getBytes(StandardCharsets.ISO_8859_1));
File artifact = createArtifact(entries);
GradleException failure = assertThrows(GradleException.class,
() -> ModdedArtifactVerifier.verify(artifact, REQUIRED_ENTRIES));
assertTrue(failure.getMessage().contains("Iris-private runtime references"));
}
@Test
public void rejectsBundledMinecraftRuntimeLibrary() throws Exception {
Map<String, byte[]> entries = validEntries();
entries.put("net/jpountz/lz4/LZ4BlockInputStream.class", new byte[]{0});
File artifact = createArtifact(entries);
GradleException failure = assertThrows(GradleException.class,
() -> ModdedArtifactVerifier.verify(artifact, REQUIRED_ENTRIES));
assertTrue(failure.getMessage().contains("duplicates Minecraft runtime libraries"));
}
@Test
public void rejectsNestedMinecraftRuntimeLibrary() throws Exception {
Map<String, byte[]> entries = validEntries();
entries.put("META-INF/jars/lz4-java.jar", new byte[]{0});
File artifact = createArtifact(entries);
GradleException failure = assertThrows(GradleException.class,
() -> ModdedArtifactVerifier.verify(artifact, REQUIRED_ENTRIES));
assertTrue(failure.getMessage().contains("duplicates Minecraft runtime libraries"));
}
@Test
public void rejectsRenamedJarJarRuntimeLibrary() throws Exception {
Map<String, byte[]> entries = validEntries();
entries.put("META-INF/jarjar/runtime-support.jar", createNestedArtifact(Map.of(
"com/sun/jna/Native.class", new byte[]{0}
)));
File artifact = createArtifact(entries);
GradleException failure = assertThrows(GradleException.class,
() -> ModdedArtifactVerifier.verify(artifact, REQUIRED_ENTRIES));
assertTrue(failure.getMessage().contains("duplicates Minecraft runtime libraries"));
}
@Test
public void rejectsArtifactWithoutPlatformMetadata() throws Exception {
Map<String, byte[]> entries = new LinkedHashMap<>();
entries.put(CODEC_CLASS, INTERNAL_CODEC);
entries.put(HARDWARE_CLASS, "oshi/SystemInfo".getBytes(StandardCharsets.ISO_8859_1));
File artifact = createArtifact(entries);
GradleException failure = assertThrows(GradleException.class,
() -> ModdedArtifactVerifier.verify(artifact, REQUIRED_ENTRIES));
assertTrue(failure.getMessage().contains("is missing " + METADATA));
}
private Map<String, byte[]> validEntries() {
Map<String, byte[]> entries = new LinkedHashMap<>();
entries.put(METADATA, new byte[]{0});
entries.put(CODEC_CLASS, INTERNAL_CODEC);
entries.put(HARDWARE_CLASS, "oshi/SystemInfo".getBytes(StandardCharsets.ISO_8859_1));
return entries;
}
private File createArtifact(Map<String, byte[]> entries) throws Exception {
File artifact = temporaryFolder.newFile("artifact-" + System.nanoTime() + ".jar");
try (JarOutputStream output = new JarOutputStream(new FileOutputStream(artifact))) {
for (Map.Entry<String, byte[]> entry : entries.entrySet()) {
output.putNextEntry(new JarEntry(entry.getKey()));
output.write(entry.getValue());
output.closeEntry();
}
}
return artifact;
}
private byte[] createNestedArtifact(Map<String, byte[]> entries) throws Exception {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (JarOutputStream output = new JarOutputStream(bytes)) {
for (Map.Entry<String, byte[]> entry : entries.entrySet()) {
output.putNextEntry(new JarEntry(entry.getKey()));
output.write(entry.getValue());
output.closeEntry();
}
}
return bytes.toByteArray();
}
}
+1 -1
View File
@@ -37,7 +37,7 @@ plugins {
def lib = 'art.arcane.iris.util'
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate')
.orElse('com.github.VolmitSoftware:VolmLib:4c38988b344792a79f925f57f8a675bc85fc1bed')
.orElse('com.github.VolmitSoftware:VolmLib:d9026a7c8ebc391c8109f401ce79a0ce65df3969')
.get()
String sentryAuthToken = findProperty('sentry.auth.token') as String ?: System.getenv('SENTRY_AUTH_TOKEN')
boolean hasSentryAuthToken = sentryAuthToken != null && !sentryAuthToken.isBlank()
@@ -163,7 +163,6 @@ final class DecoratorCore {
}
data.set(x, height, z, fixFacesForHunk(
decorator.getForceBlock().getBlockData(irisData), data, x, z, realX, height, realZ, mantle));
return;
}
if (!decorator.isForcePlace()) {
@@ -135,36 +135,42 @@ public class IrisStructureComponent extends IrisMantleComponent {
ObjectPlaceMode mode = structure.getPlaceMode();
int failedPieces = 0;
Long2IntOpenHashMap foundationColumns = placement.getStilt() == null
IrisStructureStiltSettings stilt = placement.getStilt();
Long2IntOpenHashMap foundationColumns = stilt == null
? null : new Long2IntOpenHashMap();
boolean supportNonOccluding = stilt != null && stilt.isSupportNonOccluding();
if (placement.isUnderground()) {
ObjectPlaceMode undergroundMode = (mode == ObjectPlaceMode.ORGANIC_STILT || mode == ObjectPlaceMode.CEILING_HANG)
? mode : ObjectPlaceMode.STRUCTURE_PIECE;
for (PlacedStructurePiece p : pieces) {
if (placeObject(writer, structure, p, undergroundMode, p.getY(), rng, foundationColumns) == -1) {
if (placeObject(writer, structure, p, undergroundMode, p.getY(), rng,
foundationColumns, supportNonOccluding) == -1) {
failedPieces++;
}
}
} else if (mode == ObjectPlaceMode.STRUCTURE_PIECE || mode == ObjectPlaceMode.FLOATING) {
for (PlacedStructurePiece p : pieces) {
if (placeObject(writer, structure, p, ObjectPlaceMode.STRUCTURE_PIECE, p.getY(), rng, foundationColumns) == -1) {
if (placeObject(writer, structure, p, ObjectPlaceMode.STRUCTURE_PIECE, p.getY(), rng,
foundationColumns, supportNonOccluding) == -1) {
failedPieces++;
}
}
} else if (pieces.size() == 1) {
if (placeObject(writer, structure, pieces.getFirst(), mode, -1, rng, foundationColumns) == -1) {
if (placeObject(writer, structure, pieces.getFirst(), mode, -1, rng,
foundationColumns, supportNonOccluding) == -1) {
failedPieces++;
}
} else {
for (PlacedStructurePiece p : pieces) {
if (placeObject(writer, structure, p, ObjectPlaceMode.STRUCTURE_PIECE, p.getY(), rng, foundationColumns) == -1) {
if (placeObject(writer, structure, p, ObjectPlaceMode.STRUCTURE_PIECE, p.getY(), rng,
foundationColumns, supportNonOccluding) == -1) {
failedPieces++;
}
}
}
requireAppliedPieces(resolved, cx, cz, failedPieces);
if (failedPieces == 0 && placement.getStilt() != null) {
placeFoundation(writer, foundationColumns, placement.getStilt(), rng, !placement.isUnderground());
if (failedPieces == 0 && stilt != null) {
placeFoundation(writer, foundationColumns, stilt, rng, !placement.isUnderground());
}
}
@@ -533,7 +539,8 @@ public class IrisStructureComponent extends IrisMantleComponent {
}
private int placeObject(MantleWriter writer, IrisStructure structure, PlacedStructurePiece p,
ObjectPlaceMode mode, int y, RNG rng, Long2IntOpenHashMap foundationColumns) {
ObjectPlaceMode mode, int y, RNG rng, Long2IntOpenHashMap foundationColumns,
boolean supportNonOccluding) {
IrisObject object = p.getObject();
String objectKey = object.getLoadKey();
IrisObjectPlacement config = structure.createLootPlacement(objectKey);
@@ -549,7 +556,8 @@ public class IrisStructureComponent extends IrisMantleComponent {
String marker = structurePlacementMarker(structure, p, objectKey);
return object.place(p.getX(), placeY, p.getZ(), writer, config, rng, (position, state) -> {
StructureFoundationPlanner.recordBaseCell(
foundationColumns, position.getX(), position.getY(), position.getZ(), state);
foundationColumns, position.getX(), position.getY(), position.getZ(), state,
supportNonOccluding);
if (marker != null && shouldWriteStructureMarker(state)) {
writer.setData(position.getX(), position.getY(), position.getZ(), marker);
}
@@ -13,8 +13,9 @@ final class StructureFoundationPlanner {
}
static void recordBaseCell(Long2IntOpenHashMap columns, int x, int y, int z,
PlatformBlockState state) {
if (columns == null || state == null || !state.isOccluding()) {
PlatformBlockState state, boolean supportNonOccluding) {
if (columns == null || state == null
|| !(supportNonOccluding ? state.isSolid() : state.isOccluding())) {
return;
}
long columnKey = pack(x, z);
@@ -24,6 +24,7 @@ import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisDepositGenerator;
import art.arcane.iris.engine.object.IrisDepositVariant;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisDimensionCarvingResolver;
import art.arcane.iris.engine.object.IrisObject;
import art.arcane.iris.engine.object.IrisProceduralBlocks;
import art.arcane.iris.engine.object.IrisRegion;
@@ -87,6 +88,7 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
}
public void generate(IrisDepositGenerator k, MantleChunk chunk, Hunk<PlatformBlockState> data, RNG rng, int cx, int cz, boolean safe, HeightMap he, ChunkContext context) {
IrisDimensionCarvingResolver.State carvingState = new IrisDimensionCarvingResolver.State();
if (k.getSpawnChance() < rng.d())
return;
@@ -123,6 +125,29 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
if (y > k.getMaxHeight() || y < k.getMinHeight() || y > height - 2)
continue;
if (k.isOre(getData())) {
IrisBiome depositBiome = getEngine().getCaveBiome(
(cx << 4) + x, y, (cz << 4) + z, carvingState);
if (depositBiome != null) {
double frequencyMultiplier = depositBiome.getOreDepositFrequencyMultiplier();
if (frequencyMultiplier < 1D
&& !passesOreFrequency(frequencyMultiplier, rng.d())) {
continue;
}
double sizeMultiplier = depositBiome.getOreDepositSizeMultiplier();
if (sizeMultiplier != 1D) {
IrisObject scaledClump = k.getClump(getEngine(), rng, getData(), sizeMultiplier);
int scaledDimension = scaledClump.getW();
x = clampDepositCenter(x, scaledDimension, 16);
y = clampDepositCenter(y, scaledDimension, getEngine().getHeight());
z = clampDepositCenter(z, scaledDimension, 16);
clump = scaledClump;
}
}
}
IrisDimension dimension = getDimension();
for (art.arcane.iris.util.common.math.IrisBlockVector j : clump.getBlocks().keys()) {
@@ -148,7 +173,8 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
if (chunk.get(nx, ny, nz, MatterCavern.class) == null) {
PlatformBlockState ore = clump.getBlocks().get(j);
PlatformBlockState remapped = resolveDepositVariant(cx, cz, nx, ny, nz, ore, dimension, context);
PlatformBlockState remapped = resolveDepositVariant(
cx, cz, nx, ny, nz, ore, dimension, context, carvingState);
PlatformBlockState finalBlock = remapped != null
? remapped
: B.toDeepSlateOre(current, ore);
@@ -177,12 +203,22 @@ public class IrisDepositModifier extends EngineAssignedModifier<PlatformBlockSta
return state != null && !state.isAir() && !state.isFluid();
}
private PlatformBlockState resolveDepositVariant(int cx, int cz, int nx, int localY, int nz, PlatformBlockState ore, IrisDimension dimension, ChunkContext context) {
static boolean passesOreFrequency(double multiplier, double sample) {
return multiplier >= 1D || sample < Math.max(0D, multiplier);
}
static int clampDepositCenter(int center, int dimension, int limit) {
int minimum = dimension / 2;
int maximum = (int) (limit - dimension / 2D);
return Math.max(minimum, Math.min(center, maximum));
}
private PlatformBlockState resolveDepositVariant(int cx, int cz, int nx, int localY, int nz, PlatformBlockState ore, IrisDimension dimension, ChunkContext context, IrisDimensionCarvingResolver.State carvingState) {
int worldX = (cx << 4) + nx;
int worldZ = (cz << 4) + nz;
int worldY = absoluteWorldY(getEngine().getMinHeight(), localY);
IrisBiome biome = getEngine().getBiome(worldX, localY, worldZ);
IrisBiome biome = getEngine().getCaveBiome(worldX, localY, worldZ, carvingState);
if (biome != null) {
PlatformBlockState match = matchDepositVariant(biome.getDepositVariants(), ore, worldY);
if (match != null) {
@@ -205,6 +205,14 @@ public class IrisBiome extends IrisRegistrant implements IRare {
@ArrayType(min = 1, type = IrisDepositGenerator.class)
@Desc("Define biome deposit generators that add onto the existing regional and global deposit generators")
private KList<IrisDepositGenerator> deposits = new KList<>();
@MinNumber(0)
@MaxNumber(1)
@Desc("Multiplier applied to the frequency of every ore deposit whose center is inside this biome. A value of 0.4 keeps 40% of ore veins while leaving non-ore deposits unchanged.")
private double oreDepositFrequencyMultiplier = 1D;
@MinNumber(0.01)
@MaxNumber(16)
@Desc("Multiplier applied to the block count of every ore deposit whose center is inside this biome. Non-ore deposits are unchanged.")
private double oreDepositSizeMultiplier = 1D;
@ArrayType(min = 1, type = IrisDepositVariant.class)
@Desc("Deposit ore remap rules scoped to this biome. Each entry declares a vertical band and a source->replacement block id map. Applied before regional and dimension rules; first matching biome rule wins.")
private KList<IrisDepositVariant> depositVariants = new KList<>();
@@ -27,16 +27,18 @@ import art.arcane.iris.engine.object.annotations.MaxNumber;
import art.arcane.iris.engine.object.annotations.MinNumber;
import art.arcane.iris.engine.object.annotations.Required;
import art.arcane.iris.engine.object.annotations.Snippet;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.common.math.IrisBlockVector;
import art.arcane.iris.util.common.math.Vector3i;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KSet;
import art.arcane.volmlib.util.math.BlockPosition;
import art.arcane.volmlib.util.math.RNG;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import art.arcane.iris.spi.PlatformBlockState;
import lombok.experimental.Accessors;
@Snippet("deposit")
@@ -46,8 +48,10 @@ import lombok.experimental.Accessors;
@Desc("Creates ore & other block deposits underground")
@Data
public class IrisDepositGenerator {
private final transient AtomicCache<KList<IrisObject>> objects = new AtomicCache<>();
private final transient ConcurrentMap<ClumpCacheKey, KList<IrisObject>> objects = new ConcurrentHashMap<>();
private final transient AtomicCache<KList<PlatformBlockState>> blockData = new AtomicCache<>();
private final transient AtomicCache<Boolean> ore = new AtomicCache<>();
private final transient ConcurrentMap<ClumpCacheKey, KList<IrisObject>> scaledObjects = new ConcurrentHashMap<>();
@Required
@MinNumber(0)
@MaxNumber(8192) // TODO: WARNING HEIGHT
@@ -98,13 +102,14 @@ public class IrisDepositGenerator {
private boolean replaceBedrock = false;
public IrisObject getClump(Engine engine, RNG rng, IrisData rdata) {
KList<IrisObject> objects = this.objects.aquire(() ->
{
RNG rngv = new RNG(engine.getSeedManager().getDeposit() + hashCode());
ClumpCacheKey cacheKey = new ClumpCacheKey(engine.getSeedManager().getDeposit(), minSize, maxSize);
KList<IrisObject> objects = this.objects.computeIfAbsent(cacheKey, key -> {
RNG rngv = new RNG(key.depositSeed() + hashCode());
KList<IrisObject> objectsf = new KList<>();
for (int i = 0; i < varience; i++) {
objectsf.add(generateClumpObject(rngv.nextParallelRNG(2349 * i + 3598), rdata));
objectsf.add(generateClumpObject(
rngv.nextParallelRNG(2349 * i + 3598), rdata, key.minSize(), key.maxSize()));
}
return objectsf;
@@ -112,12 +117,40 @@ public class IrisDepositGenerator {
return objects.get(rng.i(0, objects.size()));
}
public IrisObject getClump(Engine engine, RNG rng, IrisData rdata, double sizeMultiplier) {
if (sizeMultiplier == 1D) {
return getClump(engine, rng, rdata);
}
int scaledMinSize = scaledDepositSize(minSize, sizeMultiplier);
int scaledMaxSize = scaledDepositSize(maxSize, sizeMultiplier);
ClumpCacheKey cacheKey = new ClumpCacheKey(
engine.getSeedManager().getDeposit(), scaledMinSize, scaledMaxSize);
KList<IrisObject> objects = scaledObjects.computeIfAbsent(cacheKey, key -> {
long sizeSeed = ((long) key.minSize() << 32) ^ (key.maxSize() & 0xffffffffL);
RNG rngv = new RNG(key.depositSeed() + hashCode() + sizeSeed);
KList<IrisObject> generated = new KList<>();
for (int i = 0; i < varience; i++) {
generated.add(generateClumpObject(
rngv.nextParallelRNG(2349 * i + 3598), rdata, key.minSize(), key.maxSize()));
}
return generated;
});
return objects.get(rng.i(0, objects.size()));
}
public int getMaxDimension() {
return Math.min(11, (int) Math.ceil(Math.cbrt(maxSize)));
}
private IrisObject generateClumpObject(RNG rngv, IrisData rdata) {
int s = rngv.i(minSize, maxSize + 1);
static int scaledDepositSize(int size, double multiplier) {
return Math.max(0, Math.min(8192, (int) Math.round(size * multiplier)));
}
private IrisObject generateClumpObject(RNG rngv, IrisData rdata, int clumpMinSize, int clumpMaxSize) {
int s = rngv.i(clumpMinSize, clumpMaxSize + 1);
if (s == 1) {
IrisObject o = new IrisObject(1, 1, 1);
Vector3i center = o.getCenter();
@@ -184,4 +217,19 @@ public class IrisDepositGenerator {
return blockData;
});
}
public boolean isOre(IrisData rdata) {
return ore.aquire(() -> {
for (PlatformBlockState block : getBlockData(rdata)) {
if (block.isOre()) {
return true;
}
}
return false;
});
}
record ClumpCacheKey(long depositSeed, int minSize, int maxSize) {
}
}
@@ -69,6 +69,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
@@ -100,7 +101,7 @@ public class IrisObject extends IrisRegistrant {
protected transient volatile boolean smartBored = false;
@Setter
protected transient AtomicCache<AxisAlignedBB> aabb = new AtomicCache<>();
private transient final AtomicCache<Boolean> treeBlockPresence = new AtomicCache<>();
private transient final AtomicCache<KList<IrisBlockVector>> surfaceSupportOffsets = new AtomicCache<>();
@Getter
private VectorMap<PlatformBlockState> blocks;
@Getter
@@ -326,7 +327,7 @@ public class IrisObject extends IrisRegistrant {
}
public void readLegacy(InputStream in) throws IOException {
treeBlockPresence.reset();
surfaceSupportOffsets.reset();
DataInputStream din = new DataInputStream(in);
this.w = din.readInt();
this.h = din.readInt();
@@ -358,7 +359,7 @@ public class IrisObject extends IrisRegistrant {
}
public void read(InputStream in) throws Throwable {
treeBlockPresence.reset();
surfaceSupportOffsets.reset();
DataInputStream din = new DataInputStream(in);
this.w = din.readInt();
this.h = din.readInt();
@@ -602,6 +603,7 @@ public class IrisObject extends IrisRegistrant {
shrinkOffset = offset;
blocks = b;
states = s;
surfaceSupportOffsets.reset();
}
public void clean() {
@@ -613,6 +615,7 @@ public class IrisObject extends IrisRegistrant {
blocks = d;
states = dx;
surfaceSupportOffsets.reset();
}
public IrisBlockVector getSigned(int x, int y, int z) {
@@ -624,7 +627,7 @@ public class IrisObject extends IrisRegistrant {
}
public void setUnsigned(int x, int y, int z, PlatformBlockState block) {
treeBlockPresence.reset();
surfaceSupportOffsets.reset();
IrisBlockVector v = getSigned(x, y, z);
if (block == null) {
@@ -646,7 +649,7 @@ public class IrisObject extends IrisRegistrant {
}
public void setUnsigned(int x, int y, int z, Block block, boolean legacy) {
treeBlockPresence.reset();
surfaceSupportOffsets.reset();
IrisBlockVector v = getSigned(x, y, z);
if (block == null) {
@@ -932,9 +935,8 @@ public class IrisObject extends IrisRegistrant {
&& !config.isUnderwater()
&& !config.isOnwater()
&& config.getCarvingSupport().supportsSurface()
&& hasTreeBlocks()
&& IrisSurfaceOpening.isOpen(oplacer, getLoader(), x, z, config.getTranslate(),
config.getRotation(), spinx, spiny, spinz)) {
config.getRotation(), spinx, spiny, spinz, getSurfaceSupportOffsets())) {
return -1;
}
@@ -1511,16 +1513,32 @@ public class IrisObject extends IrisRegistrant {
};
}
boolean hasTreeBlocks() {
Boolean present = treeBlockPresence.aquire(() -> {
KList<IrisBlockVector> getSurfaceSupportOffsets() {
return surfaceSupportOffsets.aquire(() -> {
readLock.lock();
try {
return IrisSurfaceOpening.containsTreeBlocks(blocks.values());
int lowestY = Integer.MAX_VALUE;
KList<IrisBlockVector> offsets = new KList<>();
for (Map.Entry<IrisBlockVector, PlatformBlockState> entry : blocks) {
PlatformBlockState state = entry.getValue();
if (state == null || !state.isSolid() || state.isFoliage()) {
continue;
}
IrisBlockVector position = entry.getKey();
int blockY = position.getBlockY();
if (blockY < lowestY) {
lowestY = blockY;
offsets.clear();
}
if (blockY == lowestY) {
offsets.add(position.clone());
}
}
return offsets;
} finally {
readLock.unlock();
}
});
return Boolean.TRUE.equals(present);
}
private boolean isCarvedCaveAnchor(IObjectPlacer placer, int x, int y, int z) {
@@ -1584,6 +1602,7 @@ public class IrisObject extends IrisRegistrant {
blocks = d;
states = dx;
surfaceSupportOffsets.reset();
shrinkwrap();
writeLock.unlock();
}
@@ -1713,6 +1732,7 @@ public class IrisObject extends IrisRegistrant {
}
blocks = b;
surfaceSupportOffsets.reset();
writeLock.unlock();
}
@@ -1744,6 +1764,7 @@ public class IrisObject extends IrisRegistrant {
}
blocks = b;
surfaceSupportOffsets.reset();
writeLock.unlock();
}
@@ -1779,6 +1800,7 @@ public class IrisObject extends IrisRegistrant {
}
blocks = b;
surfaceSupportOffsets.reset();
writeLock.unlock();
}
@@ -21,4 +21,7 @@ public class IrisStructureStiltSettings {
@Desc("Block palette used for foundation columns.")
private IrisMaterialPalette palette = new IrisMaterialPalette().qclear().qadd("minecraft:cobblestone");
@Desc("For Iris-authored structures, whether solid partial blocks such as slabs, stairs, and walls may seed foundation columns instead of requiring a fully occluding base block.")
private boolean supportNonOccluding = false;
}
@@ -1,9 +1,10 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.common.math.IrisBlockVector;
import java.util.List;
final class IrisSurfaceOpening {
private static final int SUPPORT_RADIUS = 1;
@@ -11,15 +12,37 @@ final class IrisSurfaceOpening {
}
static boolean isOpen(IObjectPlacer placer, IrisData data, int x, int z, IrisObjectTranslate translate,
IrisObjectRotation rotation, int spinX, int spinY, int spinZ) {
IrisBlockVector offset = new IrisBlockVector(0, 0, 0);
if (translate != null) {
offset = rotation == null
? translate.translate(offset)
: translate.translate(offset, rotation, spinX, spinY, spinZ);
IrisObjectRotation rotation, int spinX, int spinY, int spinZ,
List<IrisBlockVector> supportOffsets) {
if (supportOffsets.isEmpty()) {
IrisBlockVector origin = transform(new IrisBlockVector(0, 0, 0), translate, rotation, spinX, spinY, spinZ);
return isOpenAround(placer, data, x + origin.getBlockX(), z + origin.getBlockZ());
}
int centerX = x + offset.getBlockX();
int centerZ = z + offset.getBlockZ();
for (IrisBlockVector supportOffset : supportOffsets) {
IrisBlockVector transformed = transform(supportOffset, translate, rotation, spinX, spinY, spinZ);
if (isOpenAround(placer, data, x + transformed.getBlockX(), z + transformed.getBlockZ())) {
return true;
}
}
return false;
}
private static IrisBlockVector transform(IrisBlockVector offset, IrisObjectTranslate translate,
IrisObjectRotation rotation, int spinX, int spinY, int spinZ) {
IrisBlockVector transformed = offset.clone();
if (rotation != null) {
transformed = rotation.rotate(transformed, spinX, spinY, spinZ);
}
if (translate == null) {
return transformed;
}
return rotation == null
? translate.translate(transformed)
: translate.translate(transformed, rotation, spinX, spinY, spinZ);
}
private static boolean isOpenAround(IObjectPlacer placer, IrisData data, int centerX, int centerZ) {
for (int dx = -SUPPORT_RADIUS; dx <= SUPPORT_RADIUS; dx++) {
for (int dz = -SUPPORT_RADIUS; dz <= SUPPORT_RADIUS; dz++) {
int sampleX = centerX + dx;
@@ -32,13 +55,4 @@ final class IrisSurfaceOpening {
}
return false;
}
static boolean containsTreeBlocks(Iterable<PlatformBlockState> states) {
for (PlatformBlockState state : states) {
if (state != null && state.isTreeBlock()) {
return true;
}
}
return false;
}
}
@@ -158,6 +158,7 @@ public class SchemaBuilderParityTest {
assertTrue(properties.has("distribution"));
assertEquals("object", stilt.getString("type"));
assertTrue(stiltProperties.has("palette"));
assertTrue(stiltProperties.has("supportNonOccluding"));
assertEquals(1, maxDepth.getInt("minimum"));
assertEquals(4064, maxDepth.getInt("maximum"));
assertFalse(properties.has("rotation"));
@@ -2,6 +2,7 @@ package art.arcane.iris.engine.decorator;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisBlockData;
import art.arcane.iris.engine.object.IrisDecorationPart;
import art.arcane.iris.engine.object.IrisDecorator;
import art.arcane.iris.spi.PlatformBlockState;
@@ -378,6 +379,31 @@ public class DecoratorCoreTest {
assertSame(upper, output.get(0, 2, 0));
}
@Test
public void forcedSurfaceBlockStillPlacesDecorantAboveIt() {
IrisDecorator decorator = mock(IrisDecorator.class);
IrisBlockData forcedBlock = mock(IrisBlockData.class);
IrisData data = mock(IrisData.class);
PlatformBlockState support = sturdyState();
PlatformBlockState farmland = sturdyState();
PlatformBlockState air = airState();
PlatformBlockState wheat = mock(PlatformBlockState.class);
when(wheat.key()).thenReturn("minecraft:wheat[age=7]");
when(decorator.getForceBlock()).thenReturn(forcedBlock);
when(forcedBlock.getBlockData(data)).thenReturn(farmland);
when(decorator.pickBlockData(any(RNG.class), eq(data), anyDouble(), anyDouble())).thenReturn(wheat);
Hunk<PlatformBlockState> output = Hunk.newArrayHunk(1, 3, 1);
output.set(0, 0, 0, support);
output.set(0, 1, 0, air);
DecoratorCore.placeSurfaceSingle(
decorator, 0, 0, 0, 0, 0, output, new RNG(1L), data, false, false, null);
assertSame(farmland, output.get(0, 0, 0));
assertSame(wheat, output.get(0, 1, 0));
}
private PlatformBlockState airState() {
PlatformBlockState air = mock(PlatformBlockState.class);
when(air.isAir()).thenReturn(true);
@@ -15,26 +15,52 @@ import static org.mockito.Mockito.when;
public class StructureFoundationPlannerTest {
@Test
public void recordsLowestPlacedOccludingCellAcrossTheAssembledFootprint() {
public void recordsLowestPlacedOccludingCellByDefault() {
PlatformBlockState solid = mock(PlatformBlockState.class);
when(solid.isOccluding()).thenReturn(true);
when(solid.isSolid()).thenReturn(true);
PlatformBlockState nonOccludingSolid = mock(PlatformBlockState.class);
when(nonOccludingSolid.isSolid()).thenReturn(true);
when(nonOccludingSolid.isOccluding()).thenReturn(false);
PlatformBlockState decoration = mock(PlatformBlockState.class);
when(decoration.isOccluding()).thenReturn(false);
Long2IntOpenHashMap columns = new Long2IntOpenHashMap();
StructureFoundationPlanner.recordBaseCell(columns, 100, 48, 200, decoration);
StructureFoundationPlanner.recordBaseCell(columns, 100, 49, 200, solid);
StructureFoundationPlanner.recordBaseCell(columns, 100, 50, 200, solid);
StructureFoundationPlanner.recordBaseCell(columns, 100, 45, 200, solid);
StructureFoundationPlanner.recordBaseCell(columns, -17, 60, -33, solid);
StructureFoundationPlanner.recordBaseCell(columns, 100, 48, 200, decoration, false);
StructureFoundationPlanner.recordBaseCell(columns, 100, 49, 200, solid, false);
StructureFoundationPlanner.recordBaseCell(columns, 100, 50, 200, solid, false);
StructureFoundationPlanner.recordBaseCell(columns, 100, 45, 200, solid, false);
StructureFoundationPlanner.recordBaseCell(columns, -17, 60, -33, solid, false);
StructureFoundationPlanner.recordBaseCell(columns, 12, 41, 44, solid, false);
StructureFoundationPlanner.recordBaseCell(columns, 12, 36, 44, nonOccludingSolid, false);
assertEquals(2, columns.size());
assertEquals(3, columns.size());
assertEquals(45, columns.get(StructureFoundationPlanner.pack(100, 200)));
assertEquals(60, columns.get(StructureFoundationPlanner.pack(-17, -33)));
assertEquals(41, columns.get(StructureFoundationPlanner.pack(12, 44)));
assertEquals(-17, StructureFoundationPlanner.unpackX(StructureFoundationPlanner.pack(-17, -33)));
assertEquals(-33, StructureFoundationPlanner.unpackZ(StructureFoundationPlanner.pack(-17, -33)));
}
@Test
public void recordsSolidNonOccludingBaseWhenEnabled() {
PlatformBlockState partialStructureBlock = mock(PlatformBlockState.class);
when(partialStructureBlock.isSolid()).thenReturn(true);
when(partialStructureBlock.isOccluding()).thenReturn(false);
PlatformBlockState fullStructureBlock = mock(PlatformBlockState.class);
when(fullStructureBlock.isSolid()).thenReturn(true);
when(fullStructureBlock.isOccluding()).thenReturn(true);
Long2IntOpenHashMap columns = new Long2IntOpenHashMap();
StructureFoundationPlanner.recordBaseCell(
columns, 12, 41, 44, fullStructureBlock, true);
StructureFoundationPlanner.recordBaseCell(
columns, 12, 36, 44, partialStructureBlock, true);
assertEquals(1, columns.size());
assertEquals(36, columns.get(StructureFoundationPlanner.pack(12, 44)));
}
@Test
public void scansThroughAirAndFluidUntilSolidGround() {
int groundY = StructureFoundationPlanner.findGroundY(10, 10, 0, y -> y == 3);
@@ -53,4 +53,21 @@ public class IrisDepositModifierContainmentTest {
assertFalse(IrisDepositModifier.canReplaceDepositTarget(fluid));
assertFalse(IrisDepositModifier.canReplaceDepositTarget(null));
}
@Test
public void oreFrequencyMultiplierKeepsConfiguredShareOfVeins() {
assertTrue(IrisDepositModifier.passesOreFrequency(0.4D, 0.399D));
assertFalse(IrisDepositModifier.passesOreFrequency(0.4D, 0.4D));
assertTrue(IrisDepositModifier.passesOreFrequency(1D, 0.999D));
assertFalse(IrisDepositModifier.passesOreFrequency(0D, 0D));
}
@Test
public void largerVeinsRemainCenteredAndInsideTheChunk() {
assertEquals(6, IrisDepositModifier.clampDepositCenter(6, 5, 16));
assertEquals(2, IrisDepositModifier.clampDepositCenter(1, 5, 16));
assertEquals(13, IrisDepositModifier.clampDepositCenter(14, 5, 16));
assertEquals(2, IrisDepositModifier.clampDepositCenter(1, 4, 16));
assertEquals(14, IrisDepositModifier.clampDepositCenter(15, 4, 16));
}
}
@@ -0,0 +1,64 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.spi.PlatformBlockState;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class IrisDepositTuningTest {
@Test
public void depositSizesScaleAndRemainWithinSchemaLimit() {
assertEquals(8, IrisDepositGenerator.scaledDepositSize(4, 2D));
assertEquals(16, IrisDepositGenerator.scaledDepositSize(8, 2D));
assertEquals(0, IrisDepositGenerator.scaledDepositSize(0, 2D));
assertEquals(8192, IrisDepositGenerator.scaledDepositSize(8192, 2D));
}
@Test
public void biomeOreTuningDefaultsPreserveExistingGeneration() {
IrisBiome biome = new IrisBiome();
assertEquals(1D, biome.getOreDepositFrequencyMultiplier(), 0D);
assertEquals(1D, biome.getOreDepositSizeMultiplier(), 0D);
}
@Test
public void clumpCachesAreScopedByWorldSeedAndSize() {
IrisDepositGenerator.ClumpCacheKey firstWorld =
new IrisDepositGenerator.ClumpCacheKey(41L, 4, 8);
IrisDepositGenerator.ClumpCacheKey secondWorld =
new IrisDepositGenerator.ClumpCacheKey(42L, 4, 8);
IrisDepositGenerator.ClumpCacheKey largerVein =
new IrisDepositGenerator.ClumpCacheKey(41L, 8, 16);
assertNotEquals(firstWorld, secondWorld);
assertNotEquals(firstWorld, largerVein);
assertEquals(firstWorld, new IrisDepositGenerator.ClumpCacheKey(41L, 4, 8));
}
@Test
public void onlyOreDepositPalettesReceiveBiomeTuning() {
IrisData data = mock(IrisData.class);
IrisDepositGenerator oreGenerator = generatorWithState(data, true);
IrisDepositGenerator stoneGenerator = generatorWithState(data, false);
assertTrue(oreGenerator.isOre(data));
assertFalse(stoneGenerator.isOre(data));
}
private IrisDepositGenerator generatorWithState(IrisData data, boolean ore) {
IrisBlockData block = mock(IrisBlockData.class);
PlatformBlockState state = mock(PlatformBlockState.class);
when(block.getBlockData(data)).thenReturn(state);
when(state.isOre()).thenReturn(ore);
IrisDepositGenerator generator = new IrisDepositGenerator();
generator.getPalette().add(block);
return generator;
}
}
@@ -7,6 +7,7 @@ import org.junit.Test;
import java.lang.reflect.Field;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
public class IrisStructureStiltSettingsTest {
@@ -15,6 +16,7 @@ public class IrisStructureStiltSettingsTest {
IrisStructureStiltSettings settings = new IrisStructureStiltSettings();
assertEquals(64, settings.getMaxDepth());
assertFalse(settings.isSupportNonOccluding());
assertNotNull(settings.getPalette());
assertEquals(1, settings.getPalette().getPalette().size());
assertEquals("minecraft:cobblestone", settings.getPalette().getPalette().get(0).getBlock());
@@ -3,6 +3,7 @@ package art.arcane.iris.engine.object;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.common.math.IrisBlockVector;
import org.junit.Test;
import java.util.HashMap;
@@ -23,7 +24,8 @@ public class IrisSurfaceOpeningTest {
SurfacePlacer placer = new SurfacePlacer(80);
placer.carve(1, 80, 1);
assertTrue(IrisSurfaceOpening.isOpen(placer, null, 0, 0, null, null, 0, 0, 0));
assertTrue(IrisSurfaceOpening.isOpen(placer, null, 0, 0, null, null, 0, 0, 0,
List.of(new IrisBlockVector(0, 0, 0))));
assertEquals(9, placer.heightQueries());
}
@@ -34,7 +36,8 @@ public class IrisSurfaceOpeningTest {
placer.carve(0, 78, 0);
placer.carve(0, 77, 0);
assertFalse(IrisSurfaceOpening.isOpen(placer, null, 0, 0, null, null, 0, 0, 0));
assertFalse(IrisSurfaceOpening.isOpen(placer, null, 0, 0, null, null, 0, 0, 0,
List.of(new IrisBlockVector(0, 0, 0))));
}
@Test
@@ -47,7 +50,8 @@ public class IrisSurfaceOpeningTest {
}
placer.carve(1, 82, 1);
assertTrue(IrisSurfaceOpening.isOpen(placer, null, 0, 0, null, null, 0, 0, 0));
assertTrue(IrisSurfaceOpening.isOpen(placer, null, 0, 0, null, null, 0, 0, 0,
List.of(new IrisBlockVector(0, 0, 0))));
}
@Test
@@ -55,36 +59,63 @@ public class IrisSurfaceOpeningTest {
SurfacePlacer placer = new SurfacePlacer(80);
placer.carve(2, 80, 0);
assertFalse(IrisSurfaceOpening.isOpen(placer, null, 0, 0, null, null, 0, 0, 0));
assertFalse(IrisSurfaceOpening.isOpen(placer, null, 0, 0, null, null, 0, 0, 0,
List.of(new IrisBlockVector(0, 0, 0))));
}
@Test
public void placementTranslationMovesSupportStencil() {
public void offsetSupportFootprintMovesSupportStencil() {
SurfacePlacer placer = new SurfacePlacer(80);
IrisObjectTranslate translate = new IrisObjectTranslate().setX(5).setZ(-3);
placer.carve(5, 80, -3);
assertTrue(IrisSurfaceOpening.isOpen(placer, null, 0, 0, translate, null, 0, 0, 0));
assertTrue(IrisSurfaceOpening.isOpen(placer, null, 0, 0, null, null, 0, 0, 0,
List.of(new IrisBlockVector(5, 0, -3))));
}
@Test
public void placementTranslationUsesTheSameRotationAsObjectBlocks() {
public void placementTranslationAndRotationMatchObjectBlocks() {
SurfacePlacer placer = new SurfacePlacer(80);
IrisObjectTranslate translate = new IrisObjectTranslate().setX(5).setZ(-3);
IrisObjectRotation rotation = IrisObjectRotation.of(0, 90, 0);
placer.carve(-3, 80, -5);
placer.carve(-3, 80, -7);
assertTrue(IrisSurfaceOpening.isOpen(placer, null, 0, 0, translate, rotation, 0, 0, 0));
assertTrue(IrisSurfaceOpening.isOpen(placer, null, 0, 0, translate, rotation, 0, 0, 0,
List.of(new IrisBlockVector(2, 0, 0))));
}
@Test
public void treeBlockClassificationDoesNotMatchUnrelatedObjects() {
PlatformBlockState stone = mock(PlatformBlockState.class);
PlatformBlockState log = mock(PlatformBlockState.class);
when(log.isTreeBlock()).thenReturn(true);
public void emptyFootprintFallsBackToPlacementOrigin() {
SurfacePlacer placer = new SurfacePlacer(80);
IrisObjectTranslate translate = new IrisObjectTranslate().setX(4).setZ(-2);
placer.carve(4, 80, -2);
assertFalse(IrisSurfaceOpening.containsTreeBlocks(List.of(stone)));
assertTrue(IrisSurfaceOpening.containsTreeBlocks(List.of(stone, log)));
assertTrue(IrisSurfaceOpening.isOpen(placer, null, 0, 0, translate, null, 0, 0, 0, List.of()));
}
@Test
public void objectCachesItsLowestNonFoliageSupportBlocks() {
PlatformBlockState solid = mock(PlatformBlockState.class);
PlatformBlockState foliage = mock(PlatformBlockState.class);
when(solid.isSolid()).thenReturn(true);
when(foliage.isSolid()).thenReturn(true);
when(foliage.isFoliage()).thenReturn(true);
IrisObject object = new IrisObject(9, 9, 9);
object.setUnsigned(7, 1, 4, solid);
object.setUnsigned(4, 0, 4, foliage);
object.setUnsigned(4, 5, 4, solid);
List<IrisBlockVector> initial = object.getSurfaceSupportOffsets();
assertEquals(1, initial.size());
assertEquals(3, initial.getFirst().getBlockX());
assertEquals(-3, initial.getFirst().getBlockY());
object.setUnsigned(2, 0, 4, solid);
List<IrisBlockVector> updated = object.getSurfaceSupportOffsets();
assertEquals(1, updated.size());
assertEquals(-2, updated.getFirst().getBlockX());
assertEquals(-4, updated.getFirst().getBlockY());
}
private static final class SurfacePlacer implements IObjectPlacer {
+1 -1
View File
@@ -28,4 +28,4 @@ minecraftVersion=26.2
fabricLoaderVersion=0.19.3
forgeVersion=26.2-65.0.4
neoForgeVersion=26.2.0.12-beta
volmLibCoordinate=com.github.VolmitSoftware:VolmLib:4c38988b344792a79f925f57f8a675bc85fc1bed
volmLibCoordinate=com.github.VolmitSoftware:VolmLib:d9026a7c8ebc391c8109f401ce79a0ce65df3969
+1 -1
View File
@@ -10,7 +10,7 @@ application {
}
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate')
.orElse('com.github.VolmitSoftware:VolmLib:4c38988b344792a79f925f57f8a675bc85fc1bed')
.orElse('com.github.VolmitSoftware:VolmLib:d9026a7c8ebc391c8109f401ce79a0ce65df3969')
.get()
dependencies {