diff --git a/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClient.java b/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClient.java index 658cb9c5c..eb7c3168e 100644 --- a/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClient.java +++ b/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClient.java @@ -97,11 +97,20 @@ public final class IrisClient { } public static void onWorldJoin() { + clearWorldState(); SESSION.sendHello(); } public static void onDisconnect() { SESSION.reset(); + clearWorldState(); + } + + public static void tick() { + SESSION.tick(); + } + + private static void clearWorldState() { PREGEN.clear(); DIMENSION.clear(); TILES.clear(); diff --git a/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClientCursor.java b/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClientCursor.java index 4170a8dcf..5cc00d789 100644 --- a/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClientCursor.java +++ b/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClientCursor.java @@ -7,6 +7,7 @@ import java.util.function.LongSupplier; public final class IrisClientCursor { private static final long MIN_REQUEST_INTERVAL_MILLIS = 500L; + private static final long SAME_POSITION_REFRESH_MILLIS = 2_000L; private final ClientPacketSink sink; private final LongSupplier clock; @@ -27,10 +28,11 @@ public final class IrisClientCursor { } public synchronized void requestFor(int blockX, int blockZ) { - if (requested && blockX == lastRequestedX && blockZ == lastRequestedZ) { + long now = clock.getAsLong(); + boolean samePosition = requested && blockX == lastRequestedX && blockZ == lastRequestedZ; + if (samePosition && now - lastRequestMillis < SAME_POSITION_REFRESH_MILLIS) { return; } - long now = clock.getAsLong(); if (now - lastRequestMillis < MIN_REQUEST_INTERVAL_MILLIS) { return; } diff --git a/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClientDimension.java b/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClientDimension.java index 42488151f..2ff5f3bcb 100644 --- a/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClientDimension.java +++ b/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClientDimension.java @@ -11,7 +11,7 @@ public final class IrisClientDimension { if (previous == null) { return true; } - return previous.irisWorld() != incoming.irisWorld() || !previous.dimensionKey().equals(incoming.dimensionKey()); + return !previous.equals(incoming); } public IrisMessage.DimensionStatus status() { diff --git a/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClientSession.java b/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClientSession.java index ad92cbfa7..6745b308f 100644 --- a/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClientSession.java +++ b/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClientSession.java @@ -4,21 +4,35 @@ import art.arcane.iris.spi.protocol.IrisMessage; import art.arcane.iris.spi.protocol.IrisMessageCodec; import art.arcane.iris.spi.protocol.IrisProtocol; +import java.util.function.LongSupplier; + public final class IrisClientSession { private static final long CLIENT_CAPABILITIES = IrisProtocol.CAPABILITY_PREGEN | IrisProtocol.CAPABILITY_VISION | IrisProtocol.CAPABILITY_CURSOR | IrisProtocol.CAPABILITY_STUDIO; + private static final long HELLO_RETRY_MILLIS = 2_000L; + private static final int MAX_HELLO_ATTEMPTS = 5; + private final LongSupplier clock; private volatile State state; private volatile long serverCapabilities; private volatile boolean irisActive; private volatile String serverBrand; private volatile ClientPacketSink sink; + private volatile long nextHelloAt; + private volatile int helloAttempts; public IrisClientSession() { + this(System::currentTimeMillis); + } + + IrisClientSession(LongSupplier clock) { + this.clock = clock; this.state = State.IDLE; this.serverCapabilities = 0L; this.irisActive = false; this.serverBrand = ""; this.sink = null; + this.nextHelloAt = Long.MAX_VALUE; + this.helloAttempts = 0; } public void bind(ClientPacketSink boundSink) { @@ -46,16 +60,39 @@ public final class IrisClientSession { } public void sendHello() { + helloAttempts = 0; + state = State.AWAITING_HELLO; + sendHelloAttempt(); + } + + public void tick() { + if (state != State.AWAITING_HELLO || clock.getAsLong() < nextHelloAt) { + return; + } + if (helloAttempts >= MAX_HELLO_ATTEMPTS) { + state = State.UNSUPPORTED; + return; + } + sendHelloAttempt(); + } + + private void sendHelloAttempt() { ClientPacketSink activeSink = sink; if (activeSink == null) { return; } byte[] frame = IrisMessageCodec.encode(new IrisMessage.ClientHello(IrisProtocol.PROTOCOL_VERSION, CLIENT_CAPABILITIES)); state = State.AWAITING_HELLO; + helloAttempts++; + nextHelloAt = clock.getAsLong() + HELLO_RETRY_MILLIS; activeSink.send(frame); } public void onServerHello(IrisMessage.ServerHello hello) { + if (hello.protocolVersion() != IrisProtocol.PROTOCOL_VERSION) { + this.state = State.INCOMPATIBLE; + return; + } this.serverCapabilities = hello.capabilities(); this.irisActive = hello.irisActive(); this.serverBrand = hello.serverBrand(); @@ -67,11 +104,15 @@ public final class IrisClientSession { this.serverCapabilities = 0L; this.irisActive = false; this.serverBrand = ""; + this.nextHelloAt = Long.MAX_VALUE; + this.helloAttempts = 0; } public enum State { IDLE, AWAITING_HELLO, - READY + READY, + UNSUPPORTED, + INCOMPATIBLE } } diff --git a/adapters/client-common/src/main/java/art/arcane/iris/client/IrisVisionScreen.java b/adapters/client-common/src/main/java/art/arcane/iris/client/IrisVisionScreen.java index ace741ccf..f252d937a 100644 --- a/adapters/client-common/src/main/java/art/arcane/iris/client/IrisVisionScreen.java +++ b/adapters/client-common/src/main/java/art/arcane/iris/client/IrisVisionScreen.java @@ -45,7 +45,7 @@ public final class IrisVisionScreen extends Screen { private double centerBlockZ; private int zoom; private boolean initialized; - private String renderedDimensionKey; + private IrisMessage.DimensionStatus renderedDimension; public IrisVisionScreen() { super(Component.literal(IrisLanguage.plain(ClientUiMessages.VISION_TITLE))); @@ -54,7 +54,7 @@ public final class IrisVisionScreen extends Screen { this.centerBlockZ = 0.0D; this.zoom = DEFAULT_ZOOM; this.initialized = false; - this.renderedDimensionKey = null; + this.renderedDimension = null; } @Override @@ -290,12 +290,12 @@ public final class IrisVisionScreen extends Screen { } private void syncWorld(IrisMessage.DimensionStatus status) { - if (renderedDimensionKey == null) { - renderedDimensionKey = status.dimensionKey(); + if (renderedDimension == null) { + renderedDimension = status; return; } - if (!renderedDimensionKey.equals(status.dimensionKey())) { - renderedDimensionKey = status.dimensionKey(); + if (!renderedDimension.equals(status)) { + renderedDimension = status; releaseTextures(); centerOnPlayer(); } diff --git a/adapters/client-common/src/main/java/art/arcane/iris/client/mixin/IrisWorldOpenFlowsMixin.java b/adapters/client-common/src/main/java/art/arcane/iris/client/mixin/IrisWorldOpenFlowsMixin.java new file mode 100644 index 000000000..7de17194f --- /dev/null +++ b/adapters/client-common/src/main/java/art/arcane/iris/client/mixin/IrisWorldOpenFlowsMixin.java @@ -0,0 +1,85 @@ +package art.arcane.iris.client.mixin; + +import art.arcane.iris.modded.IrisModdedChunkGenerator; +import com.mojang.serialization.Lifecycle; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen; +import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState; +import net.minecraft.client.gui.screens.worldselection.WorldOpenFlows; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.WorldStem; +import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.presets.WorldPreset; +import net.minecraft.world.level.storage.LevelStorageSource; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Optional; + +@Mixin(WorldOpenFlows.class) +public abstract class IrisWorldOpenFlowsMixin { + @Invoker("openWorldLoadBundledResourcePack") + protected abstract void iris$openWorldLoadBundledResourcePack( + LevelStorageSource.LevelStorageAccess worldAccess, + WorldStem worldStem, + PackRepository packRepository, + Runnable onCancel); + + @Inject(method = "confirmWorldCreation", at = @At("HEAD"), cancellable = true) + private static void iris$confirmWorldCreation( + Minecraft minecraft, + CreateWorldScreen parent, + Lifecycle lifecycle, + Runnable task, + boolean skipWarning, + CallbackInfo info) { + if (skipWarning || lifecycle == Lifecycle.stable() || !iris$selectedPresetIsIris(parent)) { + return; + } + task.run(); + info.cancel(); + } + + @Inject(method = "openWorldCheckWorldStemCompatibility", at = @At("HEAD"), cancellable = true) + private void iris$openWorldCheckWorldStemCompatibility( + LevelStorageSource.LevelStorageAccess worldAccess, + WorldStem worldStem, + PackRepository packRepository, + Runnable onCancel, + CallbackInfo info) { + if (!iris$containsIrisGenerator(worldStem)) { + return; + } + iris$openWorldLoadBundledResourcePack(worldAccess, worldStem, packRepository, onCancel); + info.cancel(); + } + + private static boolean iris$selectedPresetIsIris(CreateWorldScreen parent) { + WorldCreationUiState.WorldTypeEntry worldType = parent.getUiState().getWorldType(); + Holder preset = worldType.preset(); + if (preset == null) { + return false; + } + Optional> key = preset.unwrapKey(); + return key.isPresent() && "irisworldgen".equals(key.get().identifier().getNamespace()); + } + + private static boolean iris$containsIrisGenerator(WorldStem worldStem) { + Registry dimensions = worldStem.registries() + .compositeAccess() + .lookupOrThrow(Registries.LEVEL_STEM); + for (LevelStem dimension : dimensions) { + if (dimension.generator() instanceof IrisModdedChunkGenerator) { + return true; + } + } + return false; + } +} diff --git a/adapters/client-common/src/main/java/art/arcane/iris/client/mixin/IrisWorldTypeEntryMixin.java b/adapters/client-common/src/main/java/art/arcane/iris/client/mixin/IrisWorldTypeEntryMixin.java new file mode 100644 index 000000000..95cafac67 --- /dev/null +++ b/adapters/client-common/src/main/java/art/arcane/iris/client/mixin/IrisWorldTypeEntryMixin.java @@ -0,0 +1,37 @@ +package art.arcane.iris.client.mixin; + +import art.arcane.iris.modded.ModdedWorldgenIds; +import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState; +import net.minecraft.core.Holder; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.level.levelgen.presets.WorldPreset; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Optional; + +@Mixin(WorldCreationUiState.WorldTypeEntry.class) +public class IrisWorldTypeEntryMixin { + @Shadow + @Final + private Holder preset; + + @Inject(method = "describePreset", at = @At("HEAD"), cancellable = true) + private void iris$describePreset(CallbackInfoReturnable info) { + Optional> key = preset == null + ? Optional.empty() + : preset.unwrapKey(); + if (key.isEmpty() || !"irisworldgen".equals(key.get().identifier().getNamespace())) { + return; + } + String label = ModdedWorldgenIds.displayName(key.get().identifier().getPath()); + if (label != null) { + info.setReturnValue(Component.literal(label)); + } + } +} diff --git a/adapters/fabric/build.gradle b/adapters/fabric/build.gradle index 5c4b71332..ab92d6f8b 100644 --- a/adapters/fabric/build.gradle +++ b/adapters/fabric/build.gradle @@ -172,6 +172,10 @@ tasks.named('test').configure { loom { accessWidenerPath = file('src/main/resources/irisworldgen.accesswidener') runs { + client { + runDir(providers.gradleProperty('irisClientRunDir').getOrElse('run')) + vmArg('-Xmx8G') + } server { String parity = providers.gradleProperty('irisParity').getOrNull() if (parity != null) { diff --git a/adapters/fabric/logs/2026-07-26-1.log.gz b/adapters/fabric/logs/2026-07-26-1.log.gz new file mode 100644 index 000000000..e8607dc1c Binary files /dev/null and b/adapters/fabric/logs/2026-07-26-1.log.gz differ diff --git a/adapters/fabric/logs/2026-07-26-2.log.gz b/adapters/fabric/logs/2026-07-26-2.log.gz new file mode 100644 index 000000000..c97beb299 Binary files /dev/null and b/adapters/fabric/logs/2026-07-26-2.log.gz differ diff --git a/adapters/fabric/logs/2026-07-26-3.log.gz b/adapters/fabric/logs/2026-07-26-3.log.gz new file mode 100644 index 000000000..60f173448 Binary files /dev/null and b/adapters/fabric/logs/2026-07-26-3.log.gz differ diff --git a/adapters/fabric/logs/2026-07-26-4.log.gz b/adapters/fabric/logs/2026-07-26-4.log.gz new file mode 100644 index 000000000..bdeb94c12 Binary files /dev/null and b/adapters/fabric/logs/2026-07-26-4.log.gz differ diff --git a/adapters/fabric/logs/latest.log b/adapters/fabric/logs/latest.log index 2789a5b18..59efd9d14 100644 --- a/adapters/fabric/logs/latest.log +++ b/adapters/fabric/logs/latest.log @@ -1,371 +1,2 @@ -[19:42:28] [Test worker/INFO]: Iris registered custom content provider 'iris_deferred_test' -[19:42:28] [Test worker/ERROR]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService -java.lang.RuntimeException: second disable failed - at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:55) - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) - at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) - at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) - at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) - at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) - at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) - at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) - at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) - at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) - at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.ParentRunner.run(ParentRunner.java:413) - at org.junit.runner.JUnitCore.run(JUnitCore.java:137) - at org.junit.runner.JUnitCore.run(JUnitCore.java:115) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) - at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) - at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) - at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) - at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) - at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) - at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) - at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) - at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) - 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) -[19:42:28] [Test worker/ERROR]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService -java.lang.RuntimeException: first disable failed - at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:54) - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) - at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) - at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) - at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) - at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) - at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) - at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) - at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) - at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) - at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.ParentRunner.run(ParentRunner.java:413) - at org.junit.runner.JUnitCore.run(JUnitCore.java:137) - at org.junit.runner.JUnitCore.run(JUnitCore.java:115) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) - at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) - at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) - at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) - at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) - at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) - at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) - at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) - at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) - 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) -[19:42:28] [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) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) - at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) - at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) - at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) - at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) - at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) - at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) - at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) - at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) - at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.ParentRunner.run(ParentRunner.java:413) - at org.junit.runner.JUnitCore.run(JUnitCore.java:137) - at org.junit.runner.JUnitCore.run(JUnitCore.java:115) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) - at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) - at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) - at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) - at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) - at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) - at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) - at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) - at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) - 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) -[19:42:28] [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) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) - at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) - at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) - at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) - at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) - at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) - at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) - at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) - at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) - at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.ParentRunner.run(ParentRunner.java:413) - at org.junit.runner.JUnitCore.run(JUnitCore.java:137) - at org.junit.runner.JUnitCore.run(JUnitCore.java:115) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) - at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) - at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) - at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) - at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) - at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) - at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) - at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) - at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) - 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) - Suppressed: java.lang.RuntimeException: cleanup failed - at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) - ... 42 more -[19:42:28] [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: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) - at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) - at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) - at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) - at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) - at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) - at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) - at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) - at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) - at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) - at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.ParentRunner.run(ParentRunner.java:413) - at org.junit.runner.JUnitCore.run(JUnitCore.java:137) - at org.junit.runner.JUnitCore.run(JUnitCore.java:115) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) - at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) - at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) - at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) - at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) - at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) - at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) - at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) - at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) - 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) -[19:42:28] [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: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) - at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) - at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) - at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) - at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) - at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) - at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) - at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) - at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) - at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) - at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.ParentRunner.run(ParentRunner.java:413) - at org.junit.runner.JUnitCore.run(JUnitCore.java:137) - at org.junit.runner.JUnitCore.run(JUnitCore.java:115) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) - at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) - at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) - at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) - at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) - at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) - at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) - at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) - at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) - 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) -[19:42:28] [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: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) - at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) - at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) - at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) - at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) - at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) - at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) - at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) - at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) - at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) - at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.ParentRunner.run(ParentRunner.java:413) - at org.junit.runner.JUnitCore.run(JUnitCore.java:137) - at org.junit.runner.JUnitCore.run(JUnitCore.java:115) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) - at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) - at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) - at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) - at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) - at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) - at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) - at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) - at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) - 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) -[19:42:28] [Test worker/INFO]: Iris registered custom content provider 'iris_discovery_success' -[19:42:28] [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) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) - at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) - at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) - at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) - at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) - at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) - at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) - at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) - at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) - at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) - at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) - at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) - at org.junit.runners.ParentRunner.run(ParentRunner.java:413) - at org.junit.runner.JUnitCore.run(JUnitCore.java:137) - at org.junit.runner.JUnitCore.run(JUnitCore.java:115) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) - at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) - at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) - at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) - at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) - at java.base/java.lang.reflect.Method.invoke(Method.java:565) - at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) - at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) - at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) - at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) - at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) - at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) - at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) - at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) - at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) - 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) +[15:08:50] [Test worker/INFO]: Iris object undo service ready (bounded to 32 paste(s) per player) +[15:08:50] [Test worker/INFO]: [STDOUT]: [Iris/INFO] Iris /iris command tree registered diff --git a/adapters/fabric/src/main/java/art/arcane/iris/fabric/IrisFabricClient.java b/adapters/fabric/src/main/java/art/arcane/iris/fabric/IrisFabricClient.java index 94acff154..f2b05df4c 100644 --- a/adapters/fabric/src/main/java/art/arcane/iris/fabric/IrisFabricClient.java +++ b/adapters/fabric/src/main/java/art/arcane/iris/fabric/IrisFabricClient.java @@ -45,6 +45,9 @@ public final class IrisFabricClient implements ClientModInitializer { KeyMappingHelper.registerKeyMapping(IrisClientKeybinds.OPEN_MAP); KeyMappingHelper.registerKeyMapping(IrisClientKeybinds.TOGGLE_WHAT); HudElementRegistry.addLast(IrisClient.HUD_ELEMENT_ID, (graphics, delta) -> IrisClientHud.render(graphics)); - ClientTickEvents.END_CLIENT_TICK.register(client -> IrisClientKeybinds.pollToggle()); + ClientTickEvents.END_CLIENT_TICK.register(client -> { + IrisClient.tick(); + IrisClientKeybinds.pollToggle(); + }); } } diff --git a/adapters/fabric/src/main/java/art/arcane/iris/fabric/mixin/ServerPacksSourceMixin.java b/adapters/fabric/src/main/java/art/arcane/iris/fabric/mixin/PackRepositoryMixin.java similarity index 68% rename from adapters/fabric/src/main/java/art/arcane/iris/fabric/mixin/ServerPacksSourceMixin.java rename to adapters/fabric/src/main/java/art/arcane/iris/fabric/mixin/PackRepositoryMixin.java index 2cf85eb8d..ee5e19a60 100644 --- a/adapters/fabric/src/main/java/art/arcane/iris/fabric/mixin/ServerPacksSourceMixin.java +++ b/adapters/fabric/src/main/java/art/arcane/iris/fabric/mixin/PackRepositoryMixin.java @@ -20,17 +20,22 @@ package art.arcane.iris.fabric.mixin; import art.arcane.iris.fabric.FabricForcedDatapackSources; import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.server.packs.repository.RepositorySource; import net.minecraft.server.packs.repository.ServerPacksSource; -import net.minecraft.world.level.storage.LevelStorageSource; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -@Mixin(ServerPacksSource.class) -public class ServerPacksSourceMixin { - @Inject(method = "createPackRepository(Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;)Lnet/minecraft/server/packs/repository/PackRepository;", at = @At("RETURN")) - private static void iris$addForcedDatapackSource(LevelStorageSource.LevelStorageAccess storage, CallbackInfoReturnable info) { - FabricForcedDatapackSources.attach(info.getReturnValue()); +@Mixin(PackRepository.class) +public class PackRepositoryMixin { + @Inject(method = "", at = @At("RETURN")) + private void iris$addForcedDatapackSource(RepositorySource[] sources, CallbackInfo info) { + for (RepositorySource source : sources) { + if (source instanceof ServerPacksSource) { + FabricForcedDatapackSources.attach((PackRepository) (Object) this); + return; + } + } } } diff --git a/adapters/fabric/src/main/resources/fabric.mod.json b/adapters/fabric/src/main/resources/fabric.mod.json index 2752c6c5a..d47ac6abd 100644 --- a/adapters/fabric/src/main/resources/fabric.mod.json +++ b/adapters/fabric/src/main/resources/fabric.mod.json @@ -11,7 +11,14 @@ "license": "GPL-3.0", "environment": "*", "accessWidener": "irisworldgen.accesswidener", - "mixins": ["irisworldgen.mixins.json", "irisworldgen.entity.mixins.json"], + "mixins": [ + "irisworldgen.mixins.json", + "irisworldgen.entity.mixins.json", + { + "config": "irisworldgen.client.mixins.json", + "environment": "client" + } + ], "entrypoints": { "main": ["art.arcane.iris.fabric.IrisFabricBootstrap"], "client": ["art.arcane.iris.fabric.IrisFabricClient"] diff --git a/adapters/fabric/src/main/resources/irisworldgen.accesswidener b/adapters/fabric/src/main/resources/irisworldgen.accesswidener index 4ccffaffa..573ad0649 100644 --- a/adapters/fabric/src/main/resources/irisworldgen.accesswidener +++ b/adapters/fabric/src/main/resources/irisworldgen.accesswidener @@ -4,4 +4,3 @@ accessible field net/minecraft/server/MinecraftServer executor Ljava/util/concur accessible field net/minecraft/server/MinecraftServer storageSource Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess; accessible field net/minecraft/server/packs/repository/PackRepository sources Ljava/util/Set; mutable field net/minecraft/server/packs/repository/PackRepository sources Ljava/util/Set; -accessible field net/minecraft/core/MappedRegistry frozen Z diff --git a/adapters/fabric/src/main/resources/irisworldgen.mixins.json b/adapters/fabric/src/main/resources/irisworldgen.mixins.json index 5cd269f68..2e2f8b083 100644 --- a/adapters/fabric/src/main/resources/irisworldgen.mixins.json +++ b/adapters/fabric/src/main/resources/irisworldgen.mixins.json @@ -6,7 +6,7 @@ "mixins": [ "BlockItemMixin", "BlockMixin", - "ServerPacksSourceMixin" + "PackRepositoryMixin" ], "injectors": { "defaultRequire": 1 diff --git a/adapters/forge/build.gradle b/adapters/forge/build.gradle index 8271219e1..3e4e01f6e 100644 --- a/adapters/forge/build.gradle +++ b/adapters/forge/build.gradle @@ -174,6 +174,12 @@ dependencies { minecraft { accessTransformer.from(file('src/main/resources/META-INF/accesstransformer.cfg')) runs { + register('client') { + workingDir = layout.projectDirectory.dir('run') + jvmArgs('-Xmx8G') + args('--mixin.config', 'irisworldgen.entity.mixins.json', + '--mixin.config', 'irisworldgen.client.mixins.json') + } register('server') { workingDir = layout.projectDirectory.dir('run') String parity = providers.gradleProperty('irisParity').getOrNull() @@ -223,7 +229,7 @@ tasks.named('shadowJar', ShadowJar).configure { delete(layout.buildDirectory.file("libs/Iris-${project.version}+mc${minecraftVersion}-forge.jar")) } manifest { - attributes('MixinConfigs': 'irisworldgen.entity.mixins.json') + attributes('MixinConfigs': 'irisworldgen.entity.mixins.json,irisworldgen.client.mixins.json') } archiveFileName.set(irisArtifactName('Forge', "${minecraftVersion}+${loaderDisplayVersion(forgeVersion)}")) configurations = [project.configurations.named('bundle').get()] diff --git a/adapters/forge/logs/2026-07-26-1.log.gz b/adapters/forge/logs/2026-07-26-1.log.gz new file mode 100644 index 000000000..4b8196e18 Binary files /dev/null and b/adapters/forge/logs/2026-07-26-1.log.gz differ diff --git a/adapters/forge/logs/debug-1.log.gz b/adapters/forge/logs/debug-1.log.gz index 3c86ece52..f5b4355b8 100644 Binary files a/adapters/forge/logs/debug-1.log.gz and b/adapters/forge/logs/debug-1.log.gz differ diff --git a/adapters/forge/logs/debug-2.log.gz b/adapters/forge/logs/debug-2.log.gz index 311ea1b5d..3c86ece52 100644 Binary files a/adapters/forge/logs/debug-2.log.gz and b/adapters/forge/logs/debug-2.log.gz differ diff --git a/adapters/forge/logs/debug-3.log.gz b/adapters/forge/logs/debug-3.log.gz index 9bc216e76..311ea1b5d 100644 Binary files a/adapters/forge/logs/debug-3.log.gz and b/adapters/forge/logs/debug-3.log.gz differ diff --git a/adapters/forge/logs/debug-4.log.gz b/adapters/forge/logs/debug-4.log.gz index b8467bbe0..9bc216e76 100644 Binary files a/adapters/forge/logs/debug-4.log.gz and b/adapters/forge/logs/debug-4.log.gz differ diff --git a/adapters/forge/logs/debug-5.log.gz b/adapters/forge/logs/debug-5.log.gz index d4536c195..b8467bbe0 100644 Binary files a/adapters/forge/logs/debug-5.log.gz and b/adapters/forge/logs/debug-5.log.gz differ diff --git a/adapters/forge/logs/debug.log b/adapters/forge/logs/debug.log index e33106aec..b2ada07a4 100644 --- a/adapters/forge/logs/debug.log +++ b/adapters/forge/logs/debug.log @@ -1,8 +1,55 @@ -[20Jul2026 19:42:36.918] [Test worker/DEBUG] [io.netty.util.internal.logging.InternalLoggerFactory/]: Using SLF4J as the default logging framework -[20Jul2026 19:42:36.919] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.level: simple -[20Jul2026 19:42:36.919] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.targetRecords: 4 -[20Jul2026 19:42:38.772] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test' -[20Jul2026 19:42:38.831] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService +[26Jul2026 15:03:10.625] [Test worker/DEBUG] [io.netty.util.internal.logging.InternalLoggerFactory/]: Using SLF4J as the default logging framework +[26Jul2026 15:03:10.627] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.level: simple +[26Jul2026 15:03:10.627] [Test worker/DEBUG] [io.netty.util.ResourceLeakDetector/]: -Dio.netty.leakDetection.targetRecords: 4 +[26Jul2026 15:03:12.446] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test' +[26Jul2026 15:03:12.461] [Test worker/ERROR] [Iris/]: Iris persistent dimension registry entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial9764216152837868358/iris-dimensions.json is invalid; skipping only that entry +java.lang.IllegalArgumentException: entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial9764216152837868358/iris-dimensions.json has no dimension + at art.arcane.iris.modded.ModdedDimensionRegistryStore.required(ModdedDimensionRegistryStore.java:117) ~[main/:?] + at art.arcane.iris.modded.ModdedDimensionRegistryStore.load(ModdedDimensionRegistryStore.java:68) ~[main/:?] + at art.arcane.iris.modded.ModdedDimensionRegistryStoreTest.malformedEntryDoesNotDiscardHealthyEntries(ModdedDimensionRegistryStoreTest.java:51) ~[test/:?] + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] + at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[?:?] + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) ~[junit-4.13.2.jar:4.13.2] + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) ~[junit-4.13.2.jar:4.13.2] + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner.run(ParentRunner.java:413) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runner.JUnitCore.run(JUnitCore.java:137) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runner.JUnitCore.run(JUnitCore.java:115) ~[junit-4.13.2.jar:4.13.2] + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) ~[?:?] + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) ~[?:?] + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) ~[?:?] + at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) ~[?:?] + at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) ~[?:?] + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] + at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[?:?] + at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) ~[?:?] + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) ~[?:?] + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) ~[?:?] + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) ~[?:?] + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) ~[?:?] + at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) ~[?:?] + at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) ~[?:?] + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) ~[?:?] + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) ~[?:?] + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) ~[?:?] + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) ~[?:?] + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) ~[?:?] + 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:?] +[26Jul2026 15:03:12.522] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService java.lang.RuntimeException: second disable failed at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:55) ~[test/:?] at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] @@ -47,7 +94,7 @@ java.lang.RuntimeException: second disable 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:?] -[20Jul2026 19:42:38.837] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService +[26Jul2026 15:03:12.524] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService java.lang.RuntimeException: first disable failed at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:54) ~[test/:?] at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] @@ -92,7 +139,7 @@ java.lang.RuntimeException: first disable 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:?] -[20Jul2026 19:42:38.840] [Test worker/ERROR] [Iris/]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService +[26Jul2026 15:03:12.528] [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) ~[?:?] @@ -137,7 +184,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:?] -[20Jul2026 19:42:38.843] [Test worker/ERROR] [Iris/]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService +[26Jul2026 15:03:12.531] [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) ~[?:?] @@ -185,7 +232,7 @@ java.lang.RuntimeException: enable failed Suppressed: java.lang.RuntimeException: cleanup failed at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?] ... 42 more -[20Jul2026 19:42:38.875] [Test worker/ERROR] [Iris/]: [worldcheck] server stop request failed +[26Jul2026 15:03:12.561] [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:179) ~[main/:?] @@ -232,7 +279,7 @@ 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:?] -[20Jul2026 19:42:38.878] [Test worker/ERROR] [Iris/]: [worldcheck] waiting for server shutdown failed +[26Jul2026 15:03:12.564] [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:194) ~[main/:?] @@ -279,7 +326,7 @@ 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:?] -[20Jul2026 19:42:38.883] [Test worker/ERROR] [Iris/]: [worldcheck] check failed +[26Jul2026 15:03:12.571] [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:174) ~[main/:?] @@ -326,8 +373,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:?] -[20Jul2026 19:42:38.889] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success' -[20Jul2026 19:42:38.889] [Test worker/ERROR] [Iris/]: Iris custom content provider discovery failed +[26Jul2026 15:03:12.577] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success' +[26Jul2026 15:03:12.578] [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) ~[?:?] @@ -372,3 +419,4 @@ java.lang.RuntimeException: provider init 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:?] +[26Jul2026 15:03:12.591] [Test worker/INFO] [Iris/]: Iris object undo service ready (bounded to 32 paste(s) per player) diff --git a/adapters/forge/logs/latest.log b/adapters/forge/logs/latest.log index 8cb7e60aa..11b500b2f 100644 --- a/adapters/forge/logs/latest.log +++ b/adapters/forge/logs/latest.log @@ -1,5 +1,52 @@ -[20Jul2026 19:42:38.772] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test' -[20Jul2026 19:42:38.831] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService +[26Jul2026 15:03:12.446] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test' +[26Jul2026 15:03:12.461] [Test worker/ERROR] [Iris/]: Iris persistent dimension registry entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial9764216152837868358/iris-dimensions.json is invalid; skipping only that entry +java.lang.IllegalArgumentException: entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial9764216152837868358/iris-dimensions.json has no dimension + at art.arcane.iris.modded.ModdedDimensionRegistryStore.required(ModdedDimensionRegistryStore.java:117) ~[main/:?] + at art.arcane.iris.modded.ModdedDimensionRegistryStore.load(ModdedDimensionRegistryStore.java:68) ~[main/:?] + at art.arcane.iris.modded.ModdedDimensionRegistryStoreTest.malformedEntryDoesNotDiscardHealthyEntries(ModdedDimensionRegistryStoreTest.java:51) ~[test/:?] + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] + at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[?:?] + at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59) ~[junit-4.13.2.jar:4.13.2] + at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56) ~[junit-4.13.2.jar:4.13.2] + at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runners.ParentRunner.run(ParentRunner.java:413) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runner.JUnitCore.run(JUnitCore.java:137) ~[junit-4.13.2.jar:4.13.2] + at org.junit.runner.JUnitCore.run(JUnitCore.java:115) ~[junit-4.13.2.jar:4.13.2] + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175) ~[?:?] + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84) ~[?:?] + at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47) ~[?:?] + at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65) ~[?:?] + at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53) ~[?:?] + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] + at java.base/java.lang.reflect.Method.invoke(Method.java:565) ~[?:?] + at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77) ~[?:?] + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28) ~[?:?] + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19) ~[?:?] + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) ~[?:?] + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88) ~[?:?] + at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source) ~[?:?] + at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178) ~[?:?] + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126) ~[?:?] + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103) ~[?:?] + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63) ~[?:?] + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) ~[?:?] + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122) ~[?:?] + 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:?] +[26Jul2026 15:03:12.522] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService java.lang.RuntimeException: second disable failed at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:55) ~[test/:?] at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] @@ -44,7 +91,7 @@ java.lang.RuntimeException: second disable 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:?] -[20Jul2026 19:42:38.837] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService +[26Jul2026 15:03:12.524] [Test worker/ERROR] [Iris/]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService java.lang.RuntimeException: first disable failed at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:54) ~[test/:?] at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] @@ -89,7 +136,7 @@ java.lang.RuntimeException: first disable 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:?] -[20Jul2026 19:42:38.840] [Test worker/ERROR] [Iris/]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService +[26Jul2026 15:03:12.528] [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) ~[?:?] @@ -134,7 +181,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:?] -[20Jul2026 19:42:38.843] [Test worker/ERROR] [Iris/]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService +[26Jul2026 15:03:12.531] [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) ~[?:?] @@ -182,7 +229,7 @@ java.lang.RuntimeException: enable failed Suppressed: java.lang.RuntimeException: cleanup failed at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?] ... 42 more -[20Jul2026 19:42:38.875] [Test worker/ERROR] [Iris/]: [worldcheck] server stop request failed +[26Jul2026 15:03:12.561] [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:179) ~[main/:?] @@ -229,7 +276,7 @@ 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:?] -[20Jul2026 19:42:38.878] [Test worker/ERROR] [Iris/]: [worldcheck] waiting for server shutdown failed +[26Jul2026 15:03:12.564] [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:194) ~[main/:?] @@ -276,7 +323,7 @@ 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:?] -[20Jul2026 19:42:38.883] [Test worker/ERROR] [Iris/]: [worldcheck] check failed +[26Jul2026 15:03:12.571] [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:174) ~[main/:?] @@ -323,8 +370,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:?] -[20Jul2026 19:42:38.889] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success' -[20Jul2026 19:42:38.889] [Test worker/ERROR] [Iris/]: Iris custom content provider discovery failed +[26Jul2026 15:03:12.577] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_discovery_success' +[26Jul2026 15:03:12.578] [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) ~[?:?] @@ -369,3 +416,4 @@ java.lang.RuntimeException: provider init 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:?] +[26Jul2026 15:03:12.591] [Test worker/INFO] [Iris/]: Iris object undo service ready (bounded to 32 paste(s) per player) diff --git a/adapters/forge/src/main/java/art/arcane/iris/forge/IrisForgeClient.java b/adapters/forge/src/main/java/art/arcane/iris/forge/IrisForgeClient.java index 3d20e7b1e..fc3cf213b 100644 --- a/adapters/forge/src/main/java/art/arcane/iris/forge/IrisForgeClient.java +++ b/adapters/forge/src/main/java/art/arcane/iris/forge/IrisForgeClient.java @@ -27,6 +27,7 @@ import net.minecraftforge.client.event.AddGuiOverlayLayersEvent; import net.minecraftforge.client.event.ClientPlayerNetworkEvent; import net.minecraftforge.client.event.InputEvent; import net.minecraftforge.client.event.RegisterKeyMappingsEvent; +import net.minecraftforge.event.TickEvent; import net.minecraftforge.network.Channel; import net.minecraftforge.network.PacketDistributor; @@ -46,6 +47,8 @@ public final class IrisForgeClient { ClientPlayerNetworkEvent.LoggingIn.BUS.addListener((ClientPlayerNetworkEvent.LoggingIn event) -> IrisClient.onWorldJoin()); ClientPlayerNetworkEvent.LoggingOut.BUS.addListener((ClientPlayerNetworkEvent.LoggingOut event) -> IrisClient.onDisconnect()); InputEvent.Key.BUS.addListener((InputEvent.Key event) -> IrisClientKeybinds.pollToggle()); + TickEvent.ClientTickEvent.Post.BUS.addListener( + (TickEvent.ClientTickEvent.Post event) -> IrisClient.tick()); } private static void sendToServer(byte[] frame) { diff --git a/adapters/forge/src/main/resources/META-INF/accesstransformer.cfg b/adapters/forge/src/main/resources/META-INF/accesstransformer.cfg index 42e421a53..5a2faeeb3 100644 --- a/adapters/forge/src/main/resources/META-INF/accesstransformer.cfg +++ b/adapters/forge/src/main/resources/META-INF/accesstransformer.cfg @@ -1,4 +1,3 @@ public net.minecraft.server.MinecraftServer levels public net.minecraft.server.MinecraftServer executor public net.minecraft.server.MinecraftServer storageSource -public net.minecraft.core.MappedRegistry frozen diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedBiomeSource.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedBiomeSource.java index 77f82d002..134954a7a 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedBiomeSource.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedBiomeSource.java @@ -334,8 +334,7 @@ final class IrisModdedBiomeSource extends BiomeSource { if (customBiome == null) { return fallbackBiome(registry, quartX, quartY, quartZ, sampler); } - biomeKey = engine.getDimension().getLoadKey().toLowerCase(Locale.ROOT) - + ":" + customBiome.getId().toLowerCase(Locale.ROOT); + biomeKey = ModdedWorldgenIds.biomeRef(engine, customBiome.getId()); } else if (resolution.underground()) { biomeKey = resolution.irisBiome().getGroundBiomeKey( resolution.rng(), engine, resolution.blockX(), resolution.blockY(), resolution.blockZ()); @@ -514,7 +513,6 @@ final class IrisModdedBiomeSource extends BiomeSource { throw new IllegalStateException("Iris structure biome key lookup has no active engine runtime"); } LinkedHashSet possible = new LinkedHashSet<>(); - String namespace = engine.getDimension().getLoadKey().toLowerCase(Locale.ROOT); for (IrisBiome irisBiome : engine.getAllBiomes()) { String derivative = normalizeKey(irisBiome.getStructureDerivativeKey()); if (derivative != null) { @@ -524,7 +522,7 @@ final class IrisModdedBiomeSource extends BiomeSource { continue; } for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) { - possible.add(namespace + ":" + customBiome.getId().toLowerCase(Locale.ROOT)); + possible.add(ModdedWorldgenIds.biomeRef(engine, customBiome.getId())); } } return Set.copyOf(possible); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/MainWorldService.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/MainWorldService.java index 2906f6750..fc91f41ad 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/MainWorldService.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/MainWorldService.java @@ -26,13 +26,11 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; -import java.util.stream.Stream; +import java.util.UUID; public final class MainWorldService { private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); - private static final String PRESET_NAMESPACE = "irisworldgen"; private static final String MARKER_NAME = "mainworld.pending"; private static final String[] VANILLA_DIMENSION_FOLDERS = { "region", @@ -54,8 +52,7 @@ public final class MainWorldService { int colon = value.indexOf(':'); String pack = colon >= 0 ? value.substring(0, colon) : value; String dimension = colon >= 0 ? value.substring(colon + 1) : value; - String presetKey = dimension.equals(pack) ? pack : pack + "_" + dimension; - return PRESET_NAMESPACE + ":" + presetKey; + return ModdedWorldgenIds.presetRef(pack, dimension); } public static void reconcileEarly() { @@ -78,15 +75,22 @@ public final class MainWorldService { return; } String levelName = firstNonBlank(readProperty(properties, "level-name"), "world"); - wipeVanillaDimensions(instanceRoot().resolve(levelName)); + Path worldRoot = resolveWorldRoot(levelName); + Path recovery = quarantineVanillaDimensions(worldRoot); clearPending(); - LOGGER.warn("Iris main world '{}' generated fresh: cleared the previous overworld/nether/end so this boot regenerates them as {} (player data kept).", pack, target); + LOGGER.warn("Iris main world '{}' generated fresh: moved the prior overworld/nether/end data to {} so this boot regenerates them as {} (player data kept).", pack, recovery, target); } catch (Throwable e) { LOGGER.error("Iris main world reconciliation failed", e); + throw new IllegalStateException( + "Iris refused startup after main-world reconciliation failed", e); } } public static boolean stage(String packRef, long seed) { + if (ModdedEngineBootstrap.loader().clientEnvironment()) { + LOGGER.error("Iris main-world replacement is only available on dedicated servers; use the Create World generator selector in singleplayer"); + return false; + } try { Path properties = instanceRoot().resolve("server.properties"); writeLevelProperties(properties, presetIdFor(packRef), seed); @@ -164,24 +168,53 @@ public final class MainWorldService { lines.add(prefix + value); } - private static void wipeVanillaDimensions(Path worldRoot) throws IOException { - Files.deleteIfExists(worldRoot.resolve("level.dat")); - Files.deleteIfExists(worldRoot.resolve("level.dat_old")); - for (String folder : VANILLA_DIMENSION_FOLDERS) { - deleteRecursively(worldRoot.resolve(folder)); + private static Path resolveWorldRoot(String levelName) throws IOException { + Path root = instanceRoot().toAbsolutePath().normalize(); + Path worldRoot = root.resolve(levelName).toAbsolutePath().normalize(); + if (worldRoot.equals(root) || !worldRoot.startsWith(root)) { + throw new IOException("Unsafe level-name path outside the server instance: " + levelName); } + return worldRoot; } - private static void deleteRecursively(Path path) throws IOException { - if (!Files.exists(path)) { + private static Path quarantineVanillaDimensions(Path worldRoot) throws IOException { + Path recovery = markerFile().getParent().resolve("mainworld-recovery-" + UUID.randomUUID()); + List moved = new ArrayList<>(); + moveToRecovery(worldRoot, worldRoot.resolve("level.dat"), recovery, moved); + moveToRecovery(worldRoot, worldRoot.resolve("level.dat_old"), recovery, moved); + for (String folder : VANILLA_DIMENSION_FOLDERS) { + moveToRecovery(worldRoot, worldRoot.resolve(folder), recovery, moved); + } + if (moved.isEmpty()) { + Files.deleteIfExists(recovery); + } + return recovery; + } + + private static void moveToRecovery(Path worldRoot, Path source, Path recovery, + List moved) throws IOException { + if (!Files.exists(source)) { return; } - List entries = new ArrayList<>(); - try (Stream walk = Files.walk(path)) { - walk.sorted(Comparator.comparingInt(Path::getNameCount).reversed()).forEach(entries::add); - } - for (Path entry : entries) { - Files.deleteIfExists(entry); + Path relative = worldRoot.relativize(source); + Path target = recovery.resolve(relative); + Files.createDirectories(target.getParent()); + try { + Files.move(source, target); + moved.add(relative); + } catch (IOException failure) { + for (int index = moved.size() - 1; index >= 0; index--) { + Path rollbackRelative = moved.get(index); + Path rollbackSource = recovery.resolve(rollbackRelative); + Path rollbackTarget = worldRoot.resolve(rollbackRelative); + try { + Files.createDirectories(rollbackTarget.getParent()); + Files.move(rollbackSource, rollbackTarget); + } catch (IOException rollbackFailure) { + failure.addSuppressed(rollbackFailure); + } + } + throw failure; } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBiomeWriter.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBiomeWriter.java index 2be9308b2..ba7af1033 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBiomeWriter.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBiomeWriter.java @@ -23,6 +23,7 @@ import art.arcane.iris.engine.object.IrisBiome; import art.arcane.iris.engine.object.IrisBiomeCustom; import art.arcane.iris.spi.PlatformBiome; import art.arcane.iris.spi.PlatformBiomeWriter; +import art.arcane.iris.util.project.context.IrisContext; import net.minecraft.core.Registry; import net.minecraft.core.registries.Registries; import net.minecraft.resources.Identifier; @@ -51,7 +52,7 @@ public final class ModdedBiomeWriter implements PlatformBiomeWriter { if (registry == null) { return 0; } - int direct = idForKey(registry, key); + int direct = idForKey(registry, scopedBiomeKey(key)); if (direct >= 0) { return direct; } @@ -62,6 +63,19 @@ public final class ModdedBiomeWriter implements PlatformBiomeWriter { return fallbackId(registry); } + private String scopedBiomeKey(String key) { + IrisContext context = IrisContext.get(); + if (context == null || key == null) { + return key; + } + Engine engine = context.getEngine(); + String prefix = engine.getDimension().getLoadKey() + ":"; + if (!key.regionMatches(true, 0, prefix, 0, prefix.length())) { + return key; + } + return ModdedWorldgenIds.biomeRef(engine, key.substring(prefix.length())); + } + @Override public List allBiomes() { Registry registry = biomeRegistry(); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionManager.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionManager.java index 99d0e12eb..f06b1f63f 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionManager.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionManager.java @@ -152,11 +152,21 @@ public final class ModdedDimensionManager { } public static Handle createPersistent(MinecraftServer server, String dimensionId, String pack, String packDimensionKey, long seed) { + ModdedDimensionRegistryStore.PersistentDimension previous = + ModdedDimensionRegistryStore.get(server, dimensionId); ModdedDimensionRegistryStore.put(server, new ModdedDimensionRegistryStore.PersistentDimension(dimensionId, pack, packDimensionKey, seed)); try { return create(server, dimensionId, pack, packDimensionKey, seed); } catch (Throwable e) { - ModdedDimensionRegistryStore.remove(server, dimensionId); + try { + if (previous == null) { + ModdedDimensionRegistryStore.remove(server, dimensionId); + } else { + ModdedDimensionRegistryStore.put(server, previous); + } + } catch (Throwable rollbackFailure) { + e.addSuppressed(rollbackFailure); + } if (e instanceof RuntimeException runtimeException) { throw runtimeException; } @@ -168,9 +178,21 @@ public final class ModdedDimensionManager { } public static boolean removePersistent(MinecraftServer server, String dimensionId, boolean wipeStorage) { - boolean removed = remove(server, dimensionId, wipeStorage); + ModdedDimensionRegistryStore.PersistentDimension previous = + ModdedDimensionRegistryStore.get(server, dimensionId); ModdedDimensionRegistryStore.remove(server, dimensionId); - return removed; + try { + return remove(server, dimensionId, wipeStorage); + } catch (Throwable e) { + if (previous != null) { + try { + ModdedDimensionRegistryStore.put(server, previous); + } catch (Throwable rollbackFailure) { + e.addSuppressed(rollbackFailure); + } + } + throw e; + } } public static boolean remove(MinecraftServer server, String dimensionId, boolean wipeStorage) { @@ -273,10 +295,10 @@ public final class ModdedDimensionManager { private static Holder resolveDimensionType(RegistryAccess registryAccess, String pack, String packDimensionKey) { Registry registry = registryAccess.lookupOrThrow(Registries.DIMENSION_TYPE); IrisDimension dimension = loadPackDimension(pack, packDimensionKey); - String typeRef = ModdedForcedDatapack.dimensionTypeRef(dimension); + String typeRef = ModdedWorldgenIds.dimensionTypeRef(pack, packDimensionKey); ResourceKey typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, Identifier.parse(typeRef)); ModdedRuntimeRegistry.ensureCustomBiomes(registryAccess, dimension, pack); - ModdedRuntimeRegistry.ensureDimensionType(registryAccess, registry, typeKey, typeRef, dimension); + ModdedRuntimeRegistry.ensureDimensionType(registry, typeKey, typeRef); return ModdedForcedDatapack.requireRegisteredDimensionType( typeRef, registry.get(typeKey), pack, packDimensionKey); } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionRegistryStore.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionRegistryStore.java index 439e4840f..bdf7cbc99 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionRegistryStore.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionRegistryStore.java @@ -26,10 +26,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; +import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -43,7 +46,10 @@ public final class ModdedDimensionRegistryStore { } public static List load(MinecraftServer server) { - Path file = storeFile(server); + return load(storeFile(server)); + } + + static List load(Path file) { if (!Files.isRegularFile(file)) { return new ArrayList<>(); } @@ -51,34 +57,39 @@ public final class ModdedDimensionRegistryStore { JSONObject root = new JSONObject(Files.readString(file, StandardCharsets.UTF_8)); JSONArray entries = root.optJSONArray("dimensions"); if (entries == null) { - return new ArrayList<>(); + throw new IllegalArgumentException("registry root has no dimensions array"); } Map deduplicated = new LinkedHashMap<>(); for (int index = 0; index < entries.length(); index++) { - JSONObject entry = entries.getJSONObject(index); - String id = entry.optString("id", null); - if (id == null) { - continue; + try { + JSONObject entry = entries.getJSONObject(index); + String id = required(entry, "id", index, file); + String pack = required(entry, "pack", index, file); + String dimension = required(entry, "dimension", index, file); + if (!entry.has("seed")) { + throw new IllegalArgumentException("missing seed"); + } + PersistentDimension previous = deduplicated.putIfAbsent( + id, new PersistentDimension(id, pack, dimension, entry.getLong("seed"))); + if (previous != null) { + throw new IllegalArgumentException("duplicate id '" + id + "'"); + } + } catch (RuntimeException invalidEntry) { + LOGGER.error("Iris persistent dimension registry entry {} in {} is invalid; skipping only that entry", + index, file, invalidEntry); } - String pack = entry.optString("pack", null); - String dimension = entry.optString("dimension", null); - if (pack == null || dimension == null) { - LOGGER.error("Iris registry entry '{}' in {} has no pack/dimension fields; skipping it. Re-create the world with /iris world enable", id, file); - continue; - } - if (!entry.has("seed")) { - LOGGER.warn("Iris registry entry '{}' in {} has no seed; skipping it. Re-create the world with /iris world enable", id, file); - continue; - } - deduplicated.put(id, new PersistentDimension(id, pack, dimension, entry.getLong("seed"))); } return new ArrayList<>(deduplicated.values()); } catch (RuntimeException | IOException e) { - LOGGER.error("Iris persistent dimension registry at {} is invalid; ignoring it", file, e); - return new ArrayList<>(); + throw new IllegalStateException("Iris persistent dimension registry at " + file + + " could not be read; refusing to discard persistent worlds", e); } } + public static PersistentDimension get(MinecraftServer server, String id) { + return index(load(server)).get(id); + } + public static synchronized void put(MinecraftServer server, PersistentDimension dimension) { Map current = index(load(server)); current.put(dimension.id(), dimension); @@ -100,8 +111,19 @@ public final class ModdedDimensionRegistryStore { return map; } + private static String required(JSONObject entry, String key, int index, Path file) { + String value = entry.optString(key, null); + if (value == null || value.isBlank()) { + throw new IllegalArgumentException("entry " + index + " in " + file + " has no " + key); + } + return value; + } + private static void write(MinecraftServer server, List dimensions) { - Path file = storeFile(server); + write(storeFile(server), dimensions); + } + + static void write(Path file, List dimensions) { JSONArray entries = new JSONArray(); for (PersistentDimension dimension : dimensions) { JSONObject entry = new JSONObject(); @@ -113,13 +135,29 @@ public final class ModdedDimensionRegistryStore { } JSONObject root = new JSONObject(); root.put("dimensions", entries); + Path temp = file.resolveSibling(FILE_NAME + ".tmp"); try { Files.createDirectories(file.getParent()); - Path temp = file.resolveSibling(FILE_NAME + ".tmp"); Files.writeString(temp, root.toString(2), StandardCharsets.UTF_8); - Files.move(temp, file, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + try (FileChannel channel = FileChannel.open(temp, StandardOpenOption.WRITE)) { + channel.force(true); + } + moveReplacing(temp, file); } catch (IOException e) { - LOGGER.error("Iris failed to write persistent dimension registry at {}", file, e); + try { + Files.deleteIfExists(temp); + } catch (IOException cleanupFailure) { + e.addSuppressed(cleanupFailure); + } + throw new IllegalStateException("Iris failed to write persistent dimension registry at " + file, e); + } + } + + private static void moveReplacing(Path source, Path target) throws IOException { + try { + Files.move(source, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException unsupported) { + Files.move(source, target, StandardCopyOption.REPLACE_EXISTING); } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionStorage.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionStorage.java index ebfd75f57..58f4d28bc 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionStorage.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionStorage.java @@ -47,13 +47,19 @@ public final class ModdedDimensionStorage { public static void wipe(MinecraftServer server, ResourceKey dimension) { File storageFolder = storageFolder(server, dimension); - for (String folder : CHUNK_DATA_FOLDERS) { - deleteRecursively(new File(storageFolder, folder).toPath()); + try { + for (String folder : CHUNK_DATA_FOLDERS) { + deleteRecursively(new File(storageFolder, folder).toPath()); + } + } catch (IOException e) { + throw new IllegalStateException( + "Iris failed to completely wipe dimension storage at " + + storageFolder.getAbsolutePath(), e); } LOGGER.info("Iris wiped dimension storage at {}", storageFolder.getAbsolutePath()); } - private static void deleteRecursively(Path root) { + private static void deleteRecursively(Path root) throws IOException { if (!Files.exists(root)) { return; } @@ -61,8 +67,6 @@ public final class ModdedDimensionStorage { for (Path path : walk.sorted(Comparator.reverseOrder()).toList()) { Files.deleteIfExists(path); } - } catch (IOException e) { - LOGGER.error("Iris failed to wipe dimension storage at {}", root, e); } } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java index a35fd90c2..e81b38771 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java @@ -262,7 +262,10 @@ public final class ModdedEngineBootstrap { selfTest(moddedLoader.getClass().getClassLoader()); bind(); IrisLanguage.initialize(); - MainWorldService.reconcileEarly(); + ModdedStartup.prefetchDefaultPack(); + if (!moddedLoader.clientEnvironment()) { + MainWorldService.reconcileEarly(); + } chunkGeneratorRegistration.run(); ModdedIrisLog.info("Iris chunk generator registered as irisworldgen:iris"); armParityProbe(); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java index fb9d28302..a72ea2264 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java @@ -23,6 +23,9 @@ import art.arcane.iris.core.localization.IrisLanguage; import art.arcane.iris.core.localization.RuntimeUiMessages; import art.arcane.iris.core.nms.datapack.DataVersion; import art.arcane.iris.core.nms.datapack.IDataFixer; +import art.arcane.iris.core.pack.PackValidationRegistry; +import art.arcane.iris.core.pack.PackValidationResult; +import art.arcane.iris.core.pack.PackValidator; import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisDimensionType; import art.arcane.volmlib.util.collection.KList; @@ -80,8 +83,17 @@ public final class ModdedForcedDatapack { } private static Pack buildPack() { - Path directory = regenerate(); - return requireReadablePack(directory); + try { + return requireReadablePack(regenerate()); + } catch (RuntimeException | Error generationFailure) { + Path published = packDirectory(); + if (Files.isRegularFile(published.resolve("pack.mcmeta"))) { + LOGGER.error("Iris kept the last known-good generated datapack after regeneration failed", + generationFailure); + return requireReadablePack(published); + } + throw generationFailure; + } } private static Pack requireReadablePack(Path directory) { @@ -138,9 +150,6 @@ public final class ModdedForcedDatapack { } private static void writeStagedPack(Path stagingDirectory) throws IOException { - File packFolder = stagingDirectory.toFile(); - KList folders = new KList<>(); - folders.add(packFolder); Map> seenBiomes = new LinkedHashMap<>(); IDataFixer fixer = DataVersion.getLatest().get(); @@ -154,7 +163,7 @@ public final class ModdedForcedDatapack { if (packs != null) { Arrays.sort(packs, Comparator.comparing(File::getName)); for (File pack : packs) { - if (installPack(pack, fixer, folders, seenBiomes, presetIds)) { + if (stagePack(pack, fixer, stagingDirectory, seenBiomes, presetIds)) { packCount++; } } @@ -173,6 +182,88 @@ public final class ModdedForcedDatapack { } } + private static boolean stagePack(File sourcePack, IDataFixer fixer, Path stagingDirectory, + Map> seenBiomes, + KList presetIds) throws IOException { + PackValidationResult validation; + try { + validation = PackValidator.validate(sourcePack); + PackValidationRegistry.publish(validation); + } catch (Throwable validationFailure) { + LOGGER.error("Iris excluded pack '{}' from Create World because validation failed", + sourcePack.getName(), validationFailure); + if (validationFailure instanceof Error fatalError) { + throw fatalError; + } + return false; + } + if (!validation.isLoadable()) { + LOGGER.error("Iris excluded pack '{}' from Create World: {} blocking validation error(s); first error: {}", + sourcePack.getName(), validation.getBlockingErrors().size(), + validation.getBlockingErrors().getFirst()); + return false; + } + + Path packStagingDirectory = Files.createTempDirectory( + stagingDirectory.getParent(), PACK_FOLDER + ".pack-" + sourcePack.getName() + "-"); + Map> packBiomes = new LinkedHashMap<>(); + KList packPresetIds = new KList<>(); + KList packFolders = new KList<>(); + packFolders.add(packStagingDirectory.toFile()); + boolean installed; + try { + installed = installPack(sourcePack, fixer, packFolders, packBiomes, packPresetIds); + } catch (Throwable installationFailure) { + LOGGER.error("Iris excluded pack '{}' from Create World because datapack serialization failed", + sourcePack.getName(), installationFailure); + if (installationFailure instanceof Error fatalError) { + throw fatalError; + } + installed = false; + } + + try { + if (!installed) { + return false; + } + mergeDirectory(packStagingDirectory, stagingDirectory); + mergeBiomes(seenBiomes, packBiomes); + presetIds.addAll(packPresetIds); + return true; + } finally { + try { + clean(packStagingDirectory); + } catch (Throwable cleanupFailure) { + LOGGER.warn("Iris could not remove temporary datapack staging for pack '{}'", + sourcePack.getName(), cleanupFailure); + } + } + } + + private static void mergeDirectory(Path sourceDirectory, Path destinationDirectory) throws IOException { + List entries = new ArrayList<>(); + try (Stream walk = Files.walk(sourceDirectory)) { + walk.sorted(Comparator.comparingInt(Path::getNameCount)).forEach(entries::add); + } + for (Path source : entries) { + Path relative = sourceDirectory.relativize(source); + Path destination = destinationDirectory.resolve(relative); + if (Files.isDirectory(source)) { + Files.createDirectories(destination); + } else if (!Files.exists(destination)) { + Files.copy(source, destination); + } + } + } + + private static void mergeBiomes(Map> destination, + Map> source) { + for (Map.Entry> entry : source.entrySet()) { + destination.computeIfAbsent(entry.getKey(), ignored -> new KSet<>()) + .addAll(entry.getValue()); + } + } + private static boolean installPack(File packFolder, IDataFixer fixer, KList folders, Map> seenBiomes, KList presetIds) throws IOException { @@ -198,12 +289,15 @@ public final class ModdedForcedDatapack { throw new IllegalStateException("Iris pack '" + packName + "' dimension '" + dimensionKey + "' did not load while building the forced datapack"); } + String biomePathPrefix = ModdedWorldgenIds.biomePathPrefix(packName, dimensionKey); + dimension.installBiomes(fixer, () -> data, folders, "irisworldgen", biomePathPrefix, + biomesForNamespace(seenBiomes, biomePathPrefix)); dimension.installBiomes(fixer, () -> data, folders, biomesForNamespace(seenBiomes, dimension.getLoadKey())); - writeDimensionType(folders, fixer, dimension); - String presetKey = dimensionKey.equals(packName) ? packName : packName + "_" + dimensionKey; - writeWorldPreset(folders, dimension, packName, dimensionKey, presetKey); - presetIds.add("irisworldgen:" + presetKey); + writeDimensionType(folders, fixer, dimension, packName, dimensionKey); + String presetRef = ModdedWorldgenIds.presetRef(packName, dimensionKey); + writeWorldPreset(folders, packName, dimensionKey, presetRef); + presetIds.add(presetRef); } return true; } @@ -212,10 +306,6 @@ public final class ModdedForcedDatapack { return biomes.computeIfAbsent(namespace, ignored -> new KSet<>()); } - public static String dimensionTypeRef(IrisDimension dimension) { - return "irisworldgen:" + dimension.getDimensionTypeKey(); - } - static T requireRegisteredDimensionType(String typeRef, Optional registeredType, String pack, String packDimensionKey) { return registeredType.orElseThrow(() -> new IllegalStateException( @@ -223,13 +313,26 @@ public final class ModdedForcedDatapack { + packDimensionKey + "' is not loaded. Restart the server so the forced Iris datapack registers it before creating the world.")); } - private static void writeWorldPreset(KList folders, IrisDimension dimension, String packName, String dimensionKey, String presetKey) throws IOException { + private static void writeWorldPreset(KList folders, String packName, String dimensionKey, + String presetRef) throws IOException { String dimensionRef = dimensionKey.equals(packName) ? packName : packName + ":" + dimensionKey; - String json = worldPresetJson(dimensionRef, dimensionTypeRef(dimension)); + String json = worldPresetJson(dimensionRef, + ModdedWorldgenIds.dimensionTypeRef(packName, dimensionKey)); + String presetPath = presetRef.substring(presetRef.indexOf(':') + 1); for (File datapackRoot : folders) { - Path output = datapackRoot.toPath().resolve("data").resolve("irisworldgen").resolve("worldgen").resolve("world_preset").resolve(presetKey + ".json"); + Path output = datapackRoot.toPath().resolve("data").resolve("irisworldgen") + .resolve("worldgen").resolve("world_preset").resolve(presetPath + ".json"); Files.createDirectories(output.getParent()); Files.writeString(output, json, StandardCharsets.UTF_8); + String legacyPresetKey = dimensionKey.equals(packName) + ? packName + : packName + "_" + dimensionKey; + Path legacyOutput = datapackRoot.toPath().resolve("data").resolve("irisworldgen") + .resolve("worldgen").resolve("world_preset").resolve(legacyPresetKey + ".json"); + if (!Files.exists(legacyOutput)) { + Files.createDirectories(legacyOutput.getParent()); + Files.writeString(legacyOutput, json, StandardCharsets.UTF_8); + } } } @@ -291,14 +394,23 @@ public final class ModdedForcedDatapack { Files.writeString(output, json, StandardCharsets.UTF_8); } - static void writeDimensionType(KList folders, IDataFixer fixer, IrisDimension dimension) throws IOException { + static void writeDimensionType(KList folders, IDataFixer fixer, IrisDimension dimension, + String pack, String packDimensionKey) throws IOException { IrisDimensionType type = dimension.getDimensionType(); String json = type.toJson(fixer); - String typeKey = dimension.getDimensionTypeKey(); + String typeRef = ModdedWorldgenIds.dimensionTypeRef(pack, packDimensionKey); + String typePath = typeRef.substring(typeRef.indexOf(':') + 1); for (File datapackRoot : folders) { - Path output = datapackRoot.toPath().resolve("data").resolve("irisworldgen").resolve("dimension_type").resolve(typeKey + ".json"); + Path output = datapackRoot.toPath().resolve("data").resolve("irisworldgen") + .resolve("dimension_type").resolve(typePath + ".json"); Files.createDirectories(output.getParent()); Files.writeString(output, json, StandardCharsets.UTF_8); + Path legacyOutput = datapackRoot.toPath().resolve("data").resolve("irisworldgen") + .resolve("dimension_type").resolve(dimension.getDimensionTypeKey() + ".json"); + if (!Files.exists(legacyOutput)) { + Files.createDirectories(legacyOutput.getParent()); + Files.writeString(legacyOutput, json, StandardCharsets.UTF_8); + } } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPackInstaller.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPackInstaller.java index 90160587c..9be37a66e 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPackInstaller.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPackInstaller.java @@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.nio.file.Path; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.regex.Pattern; @@ -35,11 +36,13 @@ public final class ModdedPackInstaller { private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final Pattern PACK_NAME = Pattern.compile("[a-z0-9_-]+"); private static final Pattern BRANCH_NAME = Pattern.compile("[A-Za-z0-9._-]+"); + private static final ConcurrentHashMap INSTALL_LOCKS = new ConcurrentHashMap<>(); private ModdedPackInstaller() { } - public static boolean install(Path configDir, String pack, String branch, Consumer feedback) { + public static boolean install(Path configDir, String pack, String branch, + boolean forceOverwrite, Consumer feedback) { if (pack == null || !PACK_NAME.matcher(pack).matches()) { feedback.accept(IrisLanguage.plain(PackDownloadMessages.INVALID_PACK_NAME, MessageArgument.untrusted("pack", String.valueOf(pack)))); return false; @@ -49,20 +52,31 @@ public final class ModdedPackInstaller { return false; } - File packs = configDir.resolve("irisworldgen").resolve("packs").toFile(); - try { - if (PackDownloader.isDefaultOverworld(pack)) { - return PackDownloader.downloadDefaultOverworld(packs, true, feedback) != null; + Object installLock = INSTALL_LOCKS.computeIfAbsent(pack, key -> new Object()); + synchronized (installLock) { + File packs = configDir.resolve("irisworldgen").resolve("packs").toFile(); + try { + if (PackDownloader.isDefaultOverworld(pack)) { + return PackDownloader.downloadDefaultOverworld( + packs, forceOverwrite, feedback) != null; + } + return PackDownloader.download( + packs, + "IrisDimensions/" + pack, + branch, + forceOverwrite, + false, + feedback + ) != null; + } catch (IOException error) { + LOGGER.error("Iris pack download failed for IrisDimensions/{} ({})", pack, branch, error); + feedback.accept(IrisLanguage.plain( + PackDownloadMessages.DOWNLOAD_FAILED, + MessageArgument.untrusted("type", error.getClass().getSimpleName()), + MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(error)) + )); + return false; } - return PackDownloader.download(packs, "IrisDimensions/" + pack, branch, true, false, feedback) != null; - } catch (IOException error) { - LOGGER.error("Iris pack download failed for IrisDimensions/{} ({})", pack, branch, error); - feedback.accept(IrisLanguage.plain( - PackDownloadMessages.DOWNLOAD_FAILED, - MessageArgument.untrusted("type", error.getClass().getSimpleName()), - MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(error)) - )); - return false; } } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPlatform.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPlatform.java index 433d32d54..3ccece584 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPlatform.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPlatform.java @@ -48,10 +48,10 @@ public final class ModdedPlatform implements IrisPlatform { public ModdedPlatform(ModdedLoader loader) { this.loader = loader; - this.registries = new ModdedRegistries(loader::currentServer); + this.registries = new ModdedRegistries(ModdedEngineBootstrap::currentServer); this.scheduler = new ModdedScheduler(); - this.structureHooks = new ModdedStructureHooks(loader::currentServer); - this.biomeWriter = new ModdedBiomeWriter(loader::currentServer); + this.structureHooks = new ModdedStructureHooks(ModdedEngineBootstrap::currentServer); + this.biomeWriter = new ModdedBiomeWriter(ModdedEngineBootstrap::currentServer); } public static void errorSink(Consumer sink) { @@ -63,7 +63,7 @@ public final class ModdedPlatform implements IrisPlatform { } public MinecraftServer server() { - return loader.currentServer(); + return ModdedEngineBootstrap.currentServer(); } public ModdedScheduler moddedScheduler() { @@ -137,7 +137,7 @@ public final class ModdedPlatform implements IrisPlatform { @Override public void dispatchConsoleCommand(String command) { - ModdedServerCommands.dispatch(loader.currentServer(), command); + ModdedServerCommands.dispatch(ModdedEngineBootstrap.currentServer(), command); } @Override diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedProtocolHandler.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedProtocolHandler.java index 3a3254a43..7a1a4a489 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedProtocolHandler.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedProtocolHandler.java @@ -64,6 +64,7 @@ public final class ModdedProtocolHandler { } SESSION_ENGINES.clear(); SESSION_LEVELS.clear(); + dimensionSyncTicks = 0; IrisSessionRegistry sessionRegistry = new IrisSessionRegistry(); ModdedProtocolTransport serverTransport = new ModdedProtocolTransport(server, boundChannel); IrisProtocolServer protocol = new IrisProtocolServer(sessionRegistry, SERVER_CAPABILITIES, brand(), true); @@ -95,6 +96,7 @@ public final class ModdedProtocolHandler { } SESSION_ENGINES.clear(); SESSION_LEVELS.clear(); + dimensionSyncTicks = 0; registry = null; protocolServer = null; transport = null; diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedRuntimeRegistry.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedRuntimeRegistry.java index 7bd771779..de43d065f 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedRuntimeRegistry.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedRuntimeRegistry.java @@ -19,53 +19,35 @@ package art.arcane.iris.modded; import art.arcane.iris.core.loader.IrisData; -import art.arcane.iris.core.nms.datapack.DataVersion; -import art.arcane.iris.core.nms.datapack.IDataFixer; import art.arcane.iris.engine.object.IrisBiome; import art.arcane.iris.engine.object.IrisBiomeCustom; import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.util.common.data.DataProvider; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import com.mojang.serialization.Codec; -import com.mojang.serialization.JsonOps; -import net.minecraft.core.Holder; -import net.minecraft.core.MappedRegistry; -import net.minecraft.core.RegistrationInfo; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; import net.minecraft.resources.Identifier; -import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.dimension.DimensionType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; +import java.util.ArrayList; import java.util.HashSet; -import java.util.Locale; -import java.util.Optional; +import java.util.List; import java.util.Set; public final class ModdedRuntimeRegistry { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); - private static final Object LOCK = new Object(); - private ModdedRuntimeRegistry() { } - static void ensureDimensionType(RegistryAccess registryAccess, Registry registry, - ResourceKey typeKey, String typeRef, IrisDimension dimension) { + static void ensureDimensionType(Registry registry, + ResourceKey typeKey, String typeRef) { if (registry.get(typeKey).isPresent()) { return; } - IDataFixer fixer = DataVersion.getLatest().get(); - String json = dimension.getDimensionType().toJson(fixer); - DimensionType type = decode(registryAccess, DimensionType.DIRECT_CODEC, json, typeRef); - registerIntoFrozen(registry, typeKey, type, typeRef); - LOGGER.info("Iris registered runtime dimension type '{}'", typeRef); + throw new IllegalStateException("Iris dimension type '" + typeRef + + "' is not synchronized. Restart after installing the pack before creating its world."); } static void ensureCustomBiomes(RegistryAccess registryAccess, IrisDimension dimension, String pack) { @@ -76,10 +58,8 @@ public final class ModdedRuntimeRegistry { Registry registry = registryAccess.lookupOrThrow(Registries.BIOME); IrisData data = IrisData.get(packFolder); DataProvider provider = () -> data; - IDataFixer fixer = DataVersion.getLatest().get(); - String namespace = dimension.getLoadKey().toLowerCase(Locale.ROOT); Set seen = new HashSet<>(); - int registered = 0; + List missing = new ArrayList<>(); for (IrisBiome irisBiome : dimension.getAllBiomes(provider)) { if (!irisBiome.isCustom()) { continue; @@ -89,48 +69,17 @@ public final class ModdedRuntimeRegistry { if (!seen.add(biomeId)) { continue; } - String biomeRef = namespace + ":" + biomeId; + String biomeRef = ModdedWorldgenIds.biomeRef(pack, dimension.getLoadKey(), biomeId); ResourceKey biomeKey = ResourceKey.create(Registries.BIOME, Identifier.parse(biomeRef)); - if (registry.get(biomeKey).isPresent()) { - continue; - } - String json = customBiome.generateJson(fixer); - Biome biome = decode(registryAccess, Biome.DIRECT_CODEC, json, biomeRef); - registerIntoFrozen(registry, biomeKey, biome, biomeRef); - registered++; - } - } - if (registered > 0) { - LOGGER.info("Iris registered {} runtime biome(s) for pack '{}'", registered, pack); - } - } - - private static T decode(RegistryAccess registryAccess, Codec codec, String json, String ref) { - JsonElement element = JsonParser.parseString(json); - RegistryOps ops = RegistryOps.create(JsonOps.INSTANCE, registryAccess); - return codec.parse(ops, element).getOrThrow((String message) -> - new IllegalStateException("Iris could not decode runtime registry entry '" + ref + "': " + message)); - } - - private static Holder.Reference registerIntoFrozen(Registry registry, ResourceKey key, T value, String ref) { - if (!(registry instanceof MappedRegistry mapped)) { - throw new IllegalStateException("Iris cannot register '" + ref + "' at runtime: " - + registry.getClass().getName() + " is not a MappedRegistry"); - } - synchronized (LOCK) { - Optional> raced = registry.get(key); - if (raced.isPresent()) { - return raced.get(); - } - boolean wasFrozen = mapped.frozen; - mapped.frozen = false; - try { - return mapped.register(key, value, RegistrationInfo.BUILT_IN); - } finally { - if (wasFrozen) { - mapped.freeze(); + if (registry.get(biomeKey).isEmpty()) { + missing.add(biomeRef); } } } + if (!missing.isEmpty()) { + throw new IllegalStateException("Iris pack '" + pack + "' has " + missing.size() + + " unsynchronized custom biome(s). Restart before creating its world. First missing entry: " + + missing.getFirst()); + } } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedServiceManager.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedServiceManager.java index c50e70516..ce8e7bd2f 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedServiceManager.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedServiceManager.java @@ -90,7 +90,6 @@ public final class ModdedServiceManager { if (!enabled) { return; } - enabled = false; Throwable failure = null; ModdedService[] ordered = services.values().toArray(new ModdedService[0]); for (int i = ordered.length - 1; i >= 0; i--) { @@ -109,6 +108,7 @@ public final class ModdedServiceManager { if (failure != null) { throw new IllegalStateException("One or more Iris services failed to disable", failure); } + enabled = false; } synchronized void rollback(Throwable failure) { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedStartup.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedStartup.java index b4592cad5..780f6a282 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedStartup.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedStartup.java @@ -95,10 +95,9 @@ public final class ModdedStartup { PackValidationResult result = PackValidator.validate(packDir); PackValidationRegistry.publish(result); if (!result.isLoadable()) { - LOGGER.error("Iris pack '{}' FAILED validation - world/studio creation will be refused. Reasons:", result.getPackName()); - for (String reason : result.getBlockingErrors()) { - LOGGER.error(" - {}", reason); - } + LOGGER.error("Iris pack '{}' FAILED validation with {} blocking error(s); world/studio creation will be refused. First error: {}", + result.getPackName(), result.getBlockingErrors().size(), + result.getBlockingErrors().getFirst()); } else if (!result.getWarnings().isEmpty()) { LOGGER.info("Iris pack '{}' validated ({} warning(s)).", result.getPackName(), result.getWarnings().size()); for (String warning : result.getWarnings()) { @@ -168,8 +167,6 @@ public final class ModdedStartup { if (e instanceof Error fatalError) { throw fatalError; } - throw new IllegalStateException("Iris failed to re-inject persistent dimension '" - + dimension.id() + "' from pack '" + dimension.pack() + "'", e); } } LOGGER.info("Iris re-injected {} persistent dimension(s) at startup", injected); @@ -187,9 +184,11 @@ public final class ModdedStartup { if (new File(packFolder, "dimensions/" + pack + ".json").isFile()) { return; } - String source = PackDownloader.isDefaultOverworld(pack) ? "beta release" : "master branch"; + String source = "master branch"; LOGGER.info("Iris default pack '{}' missing; downloading IrisDimensions/{} ({})", pack, pack, source); - boolean installed = ModdedPackInstaller.install(configDir, pack, "master", (String line) -> LOGGER.info("Iris: {}", line)); + boolean installed = ModdedPackInstaller.install( + configDir, pack, "master", false, + (String line) -> LOGGER.info("Iris: {}", line)); if (!installed) { LOGGER.warn("Iris default pack '{}' could not be downloaded; install it with /iris download {}", pack, pack); } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldEngines.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldEngines.java index d8e1b23c3..f59cb7da2 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldEngines.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldEngines.java @@ -111,7 +111,7 @@ public final class ModdedWorldEngines { } long seed = seedOverride == Long.MIN_VALUE ? level.getSeed() : seedOverride; - validateDimensionContract(dimension, level); + validateDimensionContract(pack, dimensionKey, dimension, level); File worldFolder = DimensionType.getStorageFolder(level.dimension(), level.getServer().getWorldPath(LevelResource.ROOT)).toFile(); IrisWorld world = IrisWorld.builder() .platformIdentity(level.dimension().identifier().toString()) @@ -139,12 +139,21 @@ public final class ModdedWorldEngines { return engine; } - private static void validateDimensionContract(IrisDimension dimension, ServerLevel level) { + private static void validateDimensionContract(String pack, String dimensionKey, + IrisDimension dimension, ServerLevel level) { DimensionType actualType = level.dimensionType(); String actualTypeKey = level.dimensionTypeRegistration().unwrapKey() .map(key -> key.identifier().toString()) .orElse(""); - IrisDimensionRuntimeContract expected = IrisDimensionRuntimeContract.expected(dimension, "irisworldgen"); + String legacyTypeKey = "irisworldgen:" + dimension.getDimensionTypeKey(); + String expectedTypeKey = legacyTypeKey.equals(actualTypeKey) + ? legacyTypeKey + : ModdedWorldgenIds.dimensionTypeRef(pack, dimensionKey); + IrisDimensionRuntimeContract expected = new IrisDimensionRuntimeContract( + expectedTypeKey, + dimension.getMinHeight(), + dimension.getMaxHeight() - dimension.getMinHeight(), + dimension.getLogicalHeight()); IrisDimensionRuntimeContract actual = new IrisDimensionRuntimeContract( actualTypeKey, actualType.minY(), diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldgenIds.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldgenIds.java new file mode 100644 index 000000000..64f99715c --- /dev/null +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldgenIds.java @@ -0,0 +1,103 @@ +package art.arcane.iris.modded; + +import art.arcane.iris.engine.framework.Engine; + +import java.nio.charset.StandardCharsets; +import java.util.Locale; + +public final class ModdedWorldgenIds { + private static final String NAMESPACE = "irisworldgen"; + + private ModdedWorldgenIds() { + } + + public static String presetRef(String pack, String dimension) { + return NAMESPACE + ":" + scopedPath(pack, dimension) + "/preset"; + } + + public static String dimensionTypeRef(String pack, String dimension) { + return NAMESPACE + ":" + scopedPath(pack, dimension) + "/dimension_type"; + } + + public static String biomePathPrefix(String pack, String dimension) { + return scopedPath(pack, dimension) + "/biomes"; + } + + public static String biomeRef(String pack, String dimension, String biome) { + return NAMESPACE + ":" + biomePathPrefix(pack, dimension) + "/" + + biome.toLowerCase(Locale.ROOT); + } + + public static String biomeRef(Engine engine, String biome) { + return biomeRef(engine.getData().getDataFolder().getName(), + engine.getDimension().getLoadKey(), biome); + } + + public static String displayName(String presetPath) { + String[] parts = presetPath.split("/"); + if (parts.length != 5 || !"packs".equals(parts[0]) + || !"dimensions".equals(parts[2]) || !"preset".equals(parts[4])) { + return null; + } + String pack = decode(parts[1]); + String dimension = decode(parts[3]); + if (pack == null || dimension == null) { + return null; + } + String packLabel = title(pack); + return pack.equalsIgnoreCase(dimension) + ? "IRIS:" + packLabel + : "IRIS:" + packLabel + " / " + title(dimension); + } + + public static String generatorIdentity(String packDimension) { + int separator = packDimension.indexOf(':'); + String pack = separator >= 0 ? packDimension.substring(0, separator) : packDimension; + String dimension = separator >= 0 ? packDimension.substring(separator + 1) : packDimension; + return "iris:" + (pack.equalsIgnoreCase(dimension) + ? pack.toLowerCase(Locale.ROOT) + : pack.toLowerCase(Locale.ROOT) + "/" + dimension.toLowerCase(Locale.ROOT)); + } + + private static String scopedPath(String pack, String dimension) { + return "packs/" + encode(pack) + "/dimensions/" + encode(dimension); + } + + private static String encode(String value) { + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); + StringBuilder encoded = new StringBuilder(bytes.length * 2); + for (byte current : bytes) { + encoded.append(Character.forDigit((current >>> 4) & 0xF, 16)); + encoded.append(Character.forDigit(current & 0xF, 16)); + } + return encoded.toString(); + } + + private static String decode(String value) { + if ((value.length() & 1) != 0) { + return null; + } + byte[] bytes = new byte[value.length() / 2]; + try { + for (int index = 0; index < bytes.length; index++) { + int high = Character.digit(value.charAt(index * 2), 16); + int low = Character.digit(value.charAt(index * 2 + 1), 16); + if (high < 0 || low < 0) { + return null; + } + bytes[index] = (byte) ((high << 4) | low); + } + return new String(bytes, StandardCharsets.UTF_8); + } catch (RuntimeException invalid) { + return null; + } + } + + private static String title(String value) { + String normalized = value.replace('_', ' ').replace('-', ' ').trim(); + if (normalized.isEmpty()) { + return value; + } + return Character.toUpperCase(normalized.charAt(0)) + normalized.substring(1); + } +} diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java index 8a226093e..be4743429 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java @@ -33,22 +33,21 @@ import art.arcane.iris.engine.framework.WrongEngineBroException; import art.arcane.iris.engine.object.IrisBiome; import art.arcane.iris.engine.object.IrisNativeStructureDecision; import art.arcane.iris.engine.object.NativeStructureGenerationStatus; -import art.arcane.iris.engine.object.IrisPosition; import art.arcane.iris.engine.object.IrisRegion; import art.arcane.iris.modded.IrisModdedChunkGenerator; -import art.arcane.iris.modded.ModdedBlockState; import art.arcane.iris.modded.ModdedDimensionManager; import art.arcane.iris.modded.ModdedEngineBootstrap; import art.arcane.iris.modded.ModdedLoader; import art.arcane.iris.modded.ModdedPackInstaller; +import art.arcane.iris.modded.ModdedScheduler; +import art.arcane.iris.modded.ModdedWorldgenIds; import art.arcane.iris.spi.IrisLogging; -import art.arcane.iris.spi.PlatformBlockState; import art.arcane.iris.util.project.context.IrisContext; import art.arcane.volmlib.util.collection.KMap; import art.arcane.volmlib.util.math.Position2; -import art.arcane.volmlib.util.matter.MatterMarker; import com.mojang.datafixers.util.Pair; import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.BoolArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.LongArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; @@ -70,7 +69,6 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; -import net.minecraft.core.particles.DustParticleOptions; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; @@ -79,13 +77,9 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Relative; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.structure.Structure; -import net.minecraft.world.phys.BlockHitResult; -import net.minecraft.world.phys.HitResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -123,8 +117,6 @@ public final class IrisModdedCommands { private static final SuggestionProvider OBJECT_KEYS = (CommandContext context, SuggestionsBuilder builder) -> suggestObjectKeys(context, builder); private static final SuggestionProvider STRUCTURE_KEYS = (CommandContext context, SuggestionsBuilder builder) -> suggestStructureKeys(context, builder); private static final SuggestionProvider POI_TYPES = (CommandContext context, SuggestionsBuilder builder) -> SharedSuggestionProvider.suggest(List.of("buried_treasure"), builder); - private static final SuggestionProvider MARKER_TYPES = (CommandContext context, SuggestionsBuilder builder) -> SharedSuggestionProvider.suggest(List.of("cave_floor", "cave_ceiling", "object"), builder); - private static final DustParticleOptions MARKER_DUST = new DustParticleOptions(0x5A8CFF, 1.2F); static final SuggestionProvider PACK_NAMES = (CommandContext context, SuggestionsBuilder builder) -> suggestPackNames(context, builder); private static final SuggestionProvider DIMENSION_NAMES = (CommandContext context, SuggestionsBuilder builder) -> suggestDimensionNames(context, builder); @@ -152,21 +144,10 @@ public final class IrisModdedCommands { .then(Commands.argument("dimension", StringArgumentType.greedyString()).suggests(DIMENSION_NAMES) .executes((CommandContext context) -> info(context.getSource(), StringArgumentType.getString(context, "dimension"))))); - root.then(Commands.literal("what").requires(GATE) - .executes((CommandContext context) -> what(context.getSource())) - .then(Commands.literal("block") - .executes((CommandContext context) -> whatBlock(context.getSource()))) - .then(Commands.literal("hand") - .executes((CommandContext context) -> whatHand(context.getSource()))) - .then(Commands.literal("markers") - .then(Commands.argument("marker", StringArgumentType.greedyString()).suggests(MARKER_TYPES) - .executes((CommandContext context) -> whatMarkers(context.getSource(), StringArgumentType.getString(context, "marker")))))); + root.then(ModdedWhatCommands.tree()); - root.then(Commands.literal("tp").requires(GATE) - .then(Commands.argument("dimension", DimensionArgument.dimension()).suggests(DIMENSION_NAMES) - .executes((CommandContext context) -> tp(context.getSource(), DimensionArgument.getDimension(context, "dimension"), null)) - .then(Commands.argument("player", EntityArgument.player()) - .executes((CommandContext context) -> tp(context.getSource(), DimensionArgument.getDimension(context, "dimension"), EntityArgument.getPlayer(context, "player")))))); + root.then(teleportTree("teleport")); + root.then(teleportTree("tp")); root.then(Commands.literal("evacuate").requires(GATE) .executes((CommandContext context) -> evacuate(context.getSource(), null)) @@ -178,6 +159,12 @@ public final class IrisModdedCommands { root.then(Commands.literal("reload").requires(GATE) .executes((CommandContext context) -> reload(context.getSource()))); + root.then(Commands.literal("height").requires(GATE) + .executes((CommandContext context) -> height(context.getSource()))); + root.then(Commands.literal("worlds").requires(GATE) + .executes((CommandContext context) -> info(context.getSource(), null))); + root.then(Commands.literal("accesslist").requires(GATE) + .executes((CommandContext context) -> info(context.getSource(), null))); root.then(gotoTree("goto")); root.then(gotoTree("find")); @@ -202,11 +189,16 @@ public final class IrisModdedCommands { root.then(Commands.literal("wand").requires(GATE) .executes((CommandContext context) -> ModdedObjectCommands.giveWand(context.getSource()))); + root.then(Commands.literal("dust").requires(GATE) + .executes((CommandContext context) -> ModdedObjectCommands.giveDust(context.getSource()))); + root.then(Commands.literal("d").requires(GATE) + .executes((CommandContext context) -> ModdedObjectCommands.giveDust(context.getSource()))); root.then(ModdedObjectCommands.tree("object")); root.then(ModdedObjectCommands.tree("o")); root.then(editTree()); - root.then(createTree()); + root.then(createTree("create")); + root.then(createTree("c")); root.then(ModdedStudioCommands.tree("studio")); root.then(ModdedStudioCommands.tree("std")); @@ -227,9 +219,15 @@ public final class IrisModdedCommands { return root; } - private static LiteralArgumentBuilder createTree() { - return Commands.literal("create").requires(GATE) + private static LiteralArgumentBuilder createTree(String name) { + return Commands.literal(name).requires(GATE) .then(Commands.argument("name", StringArgumentType.word()) + .executes((CommandContext context) -> + ModdedWorldCommands.createWorld( + context.getSource(), + StringArgumentType.getString(context, "name"), + "overworld", + 1337L)) .then(Commands.argument("pack", StringArgumentType.string()).suggests(PACK_NAMES) .executes((CommandContext context) -> ModdedWorldCommands.createWorld(context.getSource(), StringArgumentType.getString(context, "name"), @@ -242,6 +240,17 @@ public final class IrisModdedCommands { LongArgumentType.getLong(context, "seed")))))); } + private static LiteralArgumentBuilder teleportTree(String name) { + return Commands.literal(name).requires(GATE) + .then(Commands.argument("dimension", DimensionArgument.dimension()).suggests(DIMENSION_NAMES) + .executes((CommandContext context) -> + tp(context.getSource(), DimensionArgument.getDimension(context, "dimension"), null)) + .then(Commands.argument("player", EntityArgument.player()) + .executes((CommandContext context) -> + tp(context.getSource(), DimensionArgument.getDimension(context, "dimension"), + EntityArgument.getPlayer(context, "player"))))); + } + private static LiteralArgumentBuilder helpTree() { return Commands.literal("help") .executes((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), "")) @@ -252,9 +261,34 @@ public final class IrisModdedCommands { private static LiteralArgumentBuilder downloadTree(String name) { return Commands.literal(name).requires(GATE) .then(Commands.argument("pack", StringArgumentType.word()).suggests(PACK_NAMES) - .executes((CommandContext context) -> download(context.getSource(), StringArgumentType.getString(context, "pack"), "stable")) + .executes((CommandContext context) -> + download(context.getSource(), + StringArgumentType.getString(context, "pack"), "stable", false)) + .then(Commands.literal("force") + .executes((CommandContext context) -> + download(context.getSource(), + StringArgumentType.getString(context, "pack"), "stable", true))) + .then(Commands.argument("overwrite", BoolArgumentType.bool()) + .executes((CommandContext context) -> + download(context.getSource(), + StringArgumentType.getString(context, "pack"), "stable", + BoolArgumentType.getBool(context, "overwrite")))) .then(Commands.argument("branch", StringArgumentType.word()) - .executes((CommandContext context) -> download(context.getSource(), StringArgumentType.getString(context, "pack"), StringArgumentType.getString(context, "branch"))))); + .executes((CommandContext context) -> + download(context.getSource(), + StringArgumentType.getString(context, "pack"), + StringArgumentType.getString(context, "branch"), false)) + .then(Commands.literal("force") + .executes((CommandContext context) -> + download(context.getSource(), + StringArgumentType.getString(context, "pack"), + StringArgumentType.getString(context, "branch"), true))) + .then(Commands.argument("overwrite", BoolArgumentType.bool()) + .executes((CommandContext context) -> + download(context.getSource(), + StringArgumentType.getString(context, "pack"), + StringArgumentType.getString(context, "branch"), + BoolArgumentType.getBool(context, "overwrite")))))); } private static LiteralArgumentBuilder metricsTree(String name) { @@ -380,11 +414,21 @@ public final class IrisModdedCommands { .executes((CommandContext context) -> editBiome(context.getSource(), null)) .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(BIOME_KEYS) .executes((CommandContext context) -> editBiome(context.getSource(), StringArgumentType.getString(context, "key"))))) + .then(Commands.literal("b") + .executes((CommandContext context) -> editBiome(context.getSource(), null)) + .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(BIOME_KEYS) + .executes((CommandContext context) -> editBiome(context.getSource(), StringArgumentType.getString(context, "key"))))) .then(Commands.literal("region") .executes((CommandContext context) -> editRegion(context.getSource(), null)) .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(REGION_KEYS) .executes((CommandContext context) -> editRegion(context.getSource(), StringArgumentType.getString(context, "key"))))) + .then(Commands.literal("r") + .executes((CommandContext context) -> editRegion(context.getSource(), null)) + .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(REGION_KEYS) + .executes((CommandContext context) -> editRegion(context.getSource(), StringArgumentType.getString(context, "key"))))) .then(Commands.literal("dimension") + .executes((CommandContext context) -> editDimension(context.getSource()))) + .then(Commands.literal("d") .executes((CommandContext context) -> editDimension(context.getSource()))); } @@ -540,21 +584,18 @@ public final class IrisModdedCommands { MessageArgument.untrusted("locale", IrisSettings.get().getGeneral().getLanguage()), MessageArgument.trusted("activeLocale", IrisLanguage.activeLocale()) )); - return 1; + return 0; } - private static int whatHand(CommandSourceStack source) { - ServerPlayer player = source.getPlayer(); - if (player == null) { - fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_IT_INSPECTS)); - return 0; - } - ItemStack stack = player.getMainHandItem(); - if (stack.isEmpty()) { - fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_YOUR_MAIN_HAND_IS_EMPTY)); - return 0; - } - ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_HAND_X, MessageArgument.untrusted("value", BuiltInRegistries.ITEM.getKey(stack.getItem())), MessageArgument.untrusted("value2", stack.getCount()))); + private static int height(CommandSourceStack source) { + ServerLevel level = source.getLevel(); + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WORLD_HEIGHT_RANGE, + MessageArgument.trusted("minY", level.getMinY()), + MessageArgument.trusted("maxY", level.getMaxY()))); + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WORLD_HEIGHT_TOTAL, + MessageArgument.trusted("height", level.getHeight()))); return 1; } @@ -662,15 +703,20 @@ public final class IrisModdedCommands { } iris++; String dimensionId = level.dimension().identifier().toString(); - if (filter != null && !dimensionId.contains(filter) && !irisGenerator.dimensionKey().contains(filter)) { + String irisIdentity = ModdedWorldgenIds.generatorIdentity(irisGenerator.dimensionKey()); + if (filter != null && !dimensionId.contains(filter) + && !irisIdentity.contains(filter) + && !irisGenerator.dimensionKey().contains(filter)) { continue; } Engine engine = irisGenerator.engineIfBound(); if (engine == null) { - lines.add(dimensionId + ": pack=" + irisGenerator.dimensionKey() + " (engine not started yet)"); + lines.add(irisIdentity + ": pack=" + irisGenerator.dimensionKey() + + " world=" + dimensionId + " (engine not started yet)"); continue; } - lines.add(dimensionId + ": pack=" + engine.getDimension().getLoadKey() + lines.add(irisIdentity + ": pack=" + engine.getDimension().getLoadKey() + + " world=" + dimensionId + " seed=" + level.getSeed() + " height=" + engine.getMinHeight() + ".." + engine.getMaxHeight() + " generated=" + engine.getGenerated() @@ -691,154 +737,6 @@ public final class IrisModdedCommands { return 1; } - private static int what(CommandSourceStack source) { - ServerPlayer player = source.getPlayer(); - if (player == null) { - fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_2)); - return 0; - } - ServerLevel level = source.getLevel(); - Engine engine = engineFor(level); - if (engine == null) { - fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_6)); - return 0; - } - BlockPos pos = player.blockPosition(); - int relativeY = pos.getY() - engine.getMinHeight(); - try { - IrisBiome biome = engine.getBiome(pos.getX(), relativeY, pos.getZ()); - ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_BIOME, MessageArgument.untrusted("value", biome.getLoadKey()), MessageArgument.untrusted("value2", biome.getName()))); - } catch (Throwable e) { - fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_BIOME_LOOKUP_FAILED_2, MessageArgument.untrusted("value", e.getClass().getSimpleName()))); - } - try { - IrisRegion region = engine.getRegion(pos.getX(), pos.getZ()); - ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_REGION, MessageArgument.untrusted("value", region.getLoadKey()), MessageArgument.untrusted("value2", region.getName()))); - } catch (Throwable e) { - fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_REGION_LOOKUP_FAILED_2, MessageArgument.untrusted("value", e.getClass().getSimpleName()))); - } - try { - IrisBiome cave = engine.getCaveBiome(pos.getX(), relativeY, pos.getZ()); - ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_CAVE_BIOME, MessageArgument.untrusted("value", cave == null ? IrisLanguage.plain(RuntimeUiMessages.STATUS_NONE) : cave.getLoadKey()))); - } catch (Throwable e) { - fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_CAVE_BIOME_LOOKUP_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()))); - } - int surfaceY = level.getHeight(Heightmap.Types.WORLD_SURFACE, pos.getX(), pos.getZ()); - BlockState surface = level.getBlockState(new BlockPos(pos.getX(), surfaceY - 1, pos.getZ())); - ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SURFACE_BLOCK_Y, MessageArgument.untrusted("value", BuiltInRegistries.BLOCK.getKey(surface.getBlock())), MessageArgument.untrusted("value2", (surfaceY - 1)))); - ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_POSITION_CHUNK, MessageArgument.untrusted("value", pos.getX()), MessageArgument.untrusted("value2", pos.getY()), MessageArgument.untrusted("value3", pos.getZ()), MessageArgument.untrusted("value4", (pos.getX() >> 4)), MessageArgument.untrusted("value5", (pos.getZ() >> 4)))); - return 1; - } - - private static int whatBlock(CommandSourceStack source) { - ServerPlayer player = source.getPlayer(); - if (player == null) { - fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_IT_INSPECTS_2)); - return 0; - } - HitResult hit = player.pick(128.0D, 1.0F, false); - if (hit.getType() != HitResult.Type.BLOCK || !(hit instanceof BlockHitResult blockHit)) { - fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_LOOK_AT_BLOCK_NOT_SKY)); - return 0; - } - ServerLevel level = source.getLevel(); - BlockPos pos = blockHit.getBlockPos(); - BlockState state = level.getBlockState(pos); - PlatformBlockState platform = ModdedBlockState.of(state, null); - ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_BLOCK_Y, MessageArgument.untrusted("value", platform.key()), MessageArgument.untrusted("value2", pos.getY()))); - List flags = new ArrayList<>(); - if (platform.isSolid()) { - flags.add("solid"); - } - if (platform.isFluid()) { - flags.add("fluid"); - } - if (platform.isWater()) { - flags.add("water"); - } - if (platform.isWaterLogged()) { - flags.add("waterlogged"); - } - if (platform.isStorage()) { - flags.add("storage (loot capable)"); - } - if (platform.isLit()) { - flags.add("lit"); - } - if (platform.isFoliage()) { - flags.add("foliage"); - } - if (platform.isFoliagePlantable()) { - flags.add("plantable foliage"); - } - if (platform.isDecorant()) { - flags.add("decorant"); - } - if (platform.isOre()) { - flags.add("ore"); - } - if (platform.hasTileEntity()) { - flags.add("tile entity"); - } - if (flags.isEmpty()) { - ok(source, IrisLanguage.plain(IrisMessages.MODDED_PROPERTIES_NONE)); - return 1; - } - ok(source, IrisLanguage.plain( - IrisMessages.MODDED_PROPERTIES, - MessageArgument.untrusted("properties", String.join(", ", flags)) - )); - return 1; - } - - private static int whatMarkers(CommandSourceStack source, String markerRaw) { - ServerPlayer player = source.getPlayer(); - if (player == null) { - fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_MARKERS_RENDER)); - return 0; - } - ServerLevel level = source.getLevel(); - Engine engine = engineFor(level); - if (engine == null) { - fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_7)); - return 0; - } - String marker = markerRaw.trim(); - BlockPos origin = player.blockPosition(); - int chunkX = origin.getX() >> 4; - int chunkZ = origin.getZ() >> 4; - MinecraftServer server = source.getServer(); - ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SCANNING_MARKERS_AROUND_YOU, MessageArgument.untrusted("marker", marker))); - Thread thread = new Thread(() -> { - List hits = new ArrayList<>(); - MatterMarker matterMarker = new MatterMarker(marker); - try { - for (int cx = chunkX - 4; cx <= chunkX + 4; cx++) { - for (int cz = chunkZ - 4; cz <= chunkZ + 4; cz++) { - for (IrisPosition position : engine.getMantle().findMarkers(cx, cz, matterMarker)) { - hits.add(new int[]{position.getX(), position.getY(), position.getZ()}); - } - } - } - } catch (Throwable e) { - LOGGER.error("Iris marker scan failed for {}", marker, e); - server.execute(() -> fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_MARKER_SCAN_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName())))); - return; - } - server.execute(() -> { - for (int[] hit : hits) { - level.sendParticles(player, MARKER_DUST, true, true, - hit[0] + 0.5D, hit[1] + 1.0D, hit[2] + 0.5D, - 3, 0.2D, 0.2D, 0.2D, 0.0D); - } - ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_FOUND_NEARBY_MARKER_S, MessageArgument.untrusted("value", hits.size()), MessageArgument.untrusted("marker", marker))); - }); - }, "Iris Marker Scan"); - thread.setDaemon(true); - thread.start(); - return 1; - } - private static int gotoBiome(CommandSourceStack source, String key) { ServerPlayer player = source.getPlayer(); if (player == null) { @@ -1253,9 +1151,26 @@ public final class IrisModdedCommands { int blockX = (at.getX() << 4) + 8; int blockZ = (at.getZ() << 4) + 8; try (GenerationSessionLease lease = engine.acquireGenerationLease("modded_locator_teleport"); - IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) { + IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) { int blockY = engine.getMinHeight() + engine.getHeight(blockX, blockZ, false) + 2; - player.teleportTo(level, blockX + 0.5D, blockY, blockZ + 0.5D, Set.of(), player.getYRot(), player.getXRot(), false); + boolean teleported = player.teleportTo( + level, + blockX + 0.5D, + blockY, + blockZ + 0.5D, + Set.of(), + player.getYRot(), + player.getXRot(), + false); + if (!teleported) { + fail(source, IrisLanguage.plain( + ModdedCommandMessages.IRIS_MODDED_COMMANDS_FOUND_AT_BUT_TELEPORTATION_FAILED, + MessageArgument.untrusted("label", label), + MessageArgument.trusted("targetX", blockX), + MessageArgument.trusted("clampedY", blockY), + MessageArgument.trusted("targetZ", blockZ))); + return; + } ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_TELEPORTED_AT_2, MessageArgument.untrusted("label", label), MessageArgument.untrusted("blockX", blockX), MessageArgument.untrusted("blockY", blockY), MessageArgument.untrusted("blockZ", blockZ))); } catch (GenerationSessionException e) { fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_ENGINE_CHANGED_WHILE_LOCATING_TRY_AGAIN, MessageArgument.untrusted("label", label))); @@ -1294,22 +1209,32 @@ public final class IrisModdedCommands { return 1; } - private static int download(CommandSourceStack source, String pack, String branch) { - MinecraftServer server = source.getServer(); + private static int download(CommandSourceStack source, String pack, + String branch, boolean forceOverwrite) { boolean defaultOverworld = PackDownloader.isDefaultOverworld(pack); - String downloadSource = defaultOverworld ? "beta release" : "branch " + branch; + String baseDownloadSource = defaultOverworld ? "beta release" : "branch " + branch; + String downloadSource = forceOverwrite + ? baseDownloadSource + IrisLanguage.plain(RuntimeUiMessages.DOWNLOAD_OVERWRITE_SUFFIX) + : baseDownloadSource; ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_DOWNLOADING_IRISDIMENSIONS, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("downloadSource", downloadSource))); - Thread thread = new Thread(() -> { - boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), pack, branch, - (String message) -> server.execute(() -> ok(source, message))); + ModdedScheduler scheduler = ModdedEngineBootstrap.schedulerOrNull(); + if (scheduler == null) { + fail(source, IrisLanguage.plain( + ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE, + MessageArgument.untrusted("pack", pack), + MessageArgument.untrusted("downloadSource", downloadSource))); + return 0; + } + scheduler.async(() -> { + boolean installed = ModdedPackInstaller.install( + ModdedEngineBootstrap.loader().configDir(), pack, branch, forceOverwrite, + (String message) -> scheduler.global(() -> ok(source, message))); if (installed) { - server.execute(() -> ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_INSTALLED_ITS_EXACT_DIMENSION_TYPES_CUSTOM_BIOMES_JOIN_FORCED, MessageArgument.untrusted("pack", pack)))); + scheduler.global(() -> ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_INSTALLED_ITS_EXACT_DIMENSION_TYPES_CUSTOM_BIOMES_JOIN_FORCED, MessageArgument.untrusted("pack", pack)))); } else { - server.execute(() -> fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("downloadSource", downloadSource)))); + scheduler.global(() -> fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("downloadSource", downloadSource)))); } - }, "Iris Pack Download"); - thread.setDaemon(true); - thread.start(); + }); return 1; } @@ -1416,15 +1341,27 @@ public final class IrisModdedCommands { private static CompletableFuture suggestPackNames(CommandContext context, SuggestionsBuilder builder) { ModdedCommandFeedback.tab(context.getSource()); - List names = new ArrayList<>(); + Set names = new TreeSet<>(); names.add("overworld"); try { File packs = ModdedEngineBootstrap.loader().configDir().resolve("irisworldgen").resolve("packs").toFile(); File[] children = packs.listFiles(); if (children != null) { for (File child : children) { - if (child.isDirectory() && !names.contains(child.getName())) { - names.add(child.getName()); + if (!child.isDirectory()) { + continue; + } + String packName = child.getName(); + names.add(packName); + File dimensions = new File(child, "dimensions"); + File[] dimensionFiles = dimensions.listFiles( + (File directory, String name) -> name.endsWith(".json")); + if (dimensionFiles == null) { + continue; + } + for (File dimensionFile : dimensionFiles) { + String fileName = dimensionFile.getName(); + names.add(packName + ":" + fileName.substring(0, fileName.length() - 5)); } } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandHelp.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandHelp.java index 64e7fc6ff..2d08701c3 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandHelp.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandHelp.java @@ -18,18 +18,18 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.core.localization.IrisLanguage; +import art.arcane.iris.core.localization.IrisMessages; +import art.arcane.iris.core.localization.ModdedHelpMessages; +import art.arcane.volmlib.util.director.help.DirectorHelpMessages; +import art.arcane.volmlib.util.localization.MessageArgument; +import art.arcane.volmlib.util.localization.TextKey; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.HoverEvent; import net.minecraft.network.chat.MutableComponent; -import art.arcane.iris.core.localization.IrisMessages; -import art.arcane.iris.core.localization.IrisLanguage; -import art.arcane.iris.core.localization.ModdedHelpMessages; -import art.arcane.volmlib.util.director.help.DirectorHelpMessages; -import art.arcane.volmlib.util.localization.MessageArgument; -import art.arcane.volmlib.util.localization.TextKey; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -60,21 +60,24 @@ final class ModdedCommandHelp { SECTIONS.put("", List.of( Entry.command("version", "", ModdedHelpMessages.COMMAND_VERSION_PRINT_VERSION_INFORMATION), Entry.command("info", "[dimension]", ModdedHelpMessages.COMMAND_INFO_LIST_LOADED_IRIS_DIMENSIONS_AND_PACK_DETAILS), - Entry.command("what", "[block|hand|markers]", ModdedHelpMessages.COMMAND_WHAT_INSPECT_THE_IRIS_BIOME_REGION_CAVE_BIOME_SURFACE_AND_CHUNK_AT_YOUR), + Entry.command("what", "[here|biome|region|block|hand|markers]", ModdedHelpMessages.COMMAND_WHAT_INSPECT_THE_IRIS_BIOME_REGION_CAVE_BIOME_SURFACE_AND_CHUNK_AT_YOUR), Entry.group("find", ModdedHelpMessages.GROUP_FIND_FIND_AND_TELEPORT_TO_IRIS_BIOMES_REGIONS_OBJECTS_IRIS_STRUCTURES_NATIVE_STRUCTURES, "goto"), - Entry.command("tp", " [player]", ModdedHelpMessages.COMMAND_TP_TELEPORT_YOURSELF_OR_A_NAMED_PLAYER_INTO_A_LOADED_IRIS_DIMENSION), + Entry.command("teleport", " [player]", ModdedHelpMessages.COMMAND_TP_TELEPORT_YOURSELF_OR_A_NAMED_PLAYER_INTO_A_LOADED_IRIS_DIMENSION, "tp"), Entry.command("evacuate", "[dimension]", ModdedHelpMessages.COMMAND_EVACUATE_TELEPORT_EVERY_PLAYER_OUT_OF_AN_IRIS_DIMENSION_TO_THE_PRIMARY_WORLD), Entry.command("seed", "", ModdedHelpMessages.COMMAND_SEED_PRINT_WORLD_AND_ENGINE_SEED_INFORMATION), Entry.command("debug", "", ModdedHelpMessages.COMMAND_DEBUG_TOGGLE_IRIS_DEBUG_LOGGING_AND_SAVE_SETTINGS_JSON), Entry.command("reload", "", ModdedHelpMessages.COMMAND_RELOAD_RELOAD_SETTINGS_JSON_ALSO_HOTLOADED_AUTOMATICALLY_EVERY_3S), - Entry.command("download", " [branch]", ModdedHelpMessages.COMMAND_DOWNLOAD_DOWNLOAD_A_PACK_PROJECT, "dl"), + Entry.command("download", " [branch] [overwrite]", ModdedHelpMessages.COMMAND_DOWNLOAD_DOWNLOAD_A_PACK_PROJECT, "dl"), Entry.command("metrics", "", ModdedHelpMessages.COMMAND_METRICS_PRINT_GENERATION_METRICS_FOR_YOUR_CURRENT_IRIS_DIMENSION, "measure"), Entry.command("regen", "[radius]", ModdedHelpMessages.COMMAND_REGEN_DELETE_AND_REGENERATE_NEARBY_CHUNKS_IN_PLACE, "rg"), Entry.group("pregen", ModdedHelpMessages.GROUP_PREGEN_PREGENERATE_AN_IRIS_DIMENSION, "pregenerate"), Entry.command("wand", "", ModdedHelpMessages.COMMAND_WAND_GET_AN_IRIS_OBJECT_WAND), + Entry.command("dust", "", ModdedHelpMessages.COMMAND_DUST_GET_DUST_THAT_REVEALS_OBJECT_PLACEMENTS, "d"), Entry.group("object", ModdedHelpMessages.GROUP_OBJECT_OBJECT_WAND_SAVE_PASTE_ANALYZE_AND_UNDO_TOOLS, "o"), Entry.group("edit", ModdedHelpMessages.GROUP_EDIT_OPEN_PACK_BIOME_REGION_AND_DIMENSION_JSON_FILES_IN_YOUR_DESKTOP_EDITOR), - Entry.command("create", " [seed]", ModdedHelpMessages.COMMAND_CREATE_CREATE_AND_INJECT_A_PERSISTENT_IRIS_DIMENSION_QUOTE_PACK_DIMENSIONKEY_TO_PICK), + Entry.command("create", " [pack|pack:dimensionKey] [seed]", ModdedHelpMessages.COMMAND_CREATE_CREATE_AND_INJECT_A_PERSISTENT_IRIS_DIMENSION_QUOTE_PACK_DIMENSIONKEY_TO_PICK, "c"), + Entry.command("height", "", ModdedHelpMessages.COMMAND_HEIGHT_PRINT_WORLD_HEIGHT), + Entry.command("worlds", "", ModdedHelpMessages.COMMAND_WORLDS_LIST_WORLD_ACCESS, "accesslist"), Entry.group("studio", ModdedHelpMessages.GROUP_STUDIO_PACK_PROJECT_CREATION_PACKAGING_AND_REPORTS, "std", "s"), Entry.group("pack", ModdedHelpMessages.GROUP_PACK_PACK_VALIDATION_AND_MAINTENANCE, "pk"), Entry.group("world", ModdedHelpMessages.GROUP_WORLD_RUNTIME_IRIS_DIMENSION_CREATION_REMOVAL_AND_STATUS, "w"), @@ -83,6 +86,14 @@ final class ModdedCommandHelp { Entry.command("goldenhash", "[radius] [threads] [capture|verify]", ModdedHelpMessages.COMMAND_GOLDENHASH_GENERATE_DETERMINISTIC_BLOCK_HASHES_FOR_PARITY_TESTING, "gold"), Entry.group("developer", ModdedHelpMessages.GROUP_DEVELOPER_DEVELOPER_DIAGNOSTICS_SENTRY_TEST_NETWORK_INTERFACES_REGION_FILE_SCAN, "dev") )); + SECTIONS.put("what", List.of( + Entry.command("here", "", ModdedHelpMessages.COMMAND_WHAT_HERE_INSPECT_CURRENT_IRIS_CONTEXT), + Entry.command("biome", "", ModdedHelpMessages.COMMAND_WHAT_BIOME_INSPECT_CURRENT_BIOME), + Entry.command("region", "", ModdedHelpMessages.COMMAND_WHAT_REGION_INSPECT_CURRENT_REGION), + Entry.command("block", "", ModdedHelpMessages.COMMAND_WHAT_BLOCK_INSPECT_TARGET_BLOCK), + Entry.command("hand", "", ModdedHelpMessages.COMMAND_WHAT_HAND_INSPECT_HELD_ITEM), + Entry.command("markers", "", ModdedHelpMessages.COMMAND_WHAT_MARKERS_REVEAL_NEARBY_MARKERS) + )); SECTIONS.put("find", List.of( Entry.command("biome", "", ModdedHelpMessages.COMMAND_BIOME_FIND_AN_IRIS_BIOME), Entry.command("region", "", ModdedHelpMessages.COMMAND_REGION_FIND_AN_IRIS_REGION), @@ -92,9 +103,9 @@ final class ModdedCommandHelp { )); SECTIONS.put("goto", SECTIONS.get("find")); SECTIONS.put("edit", List.of( - Entry.command("biome", "[key]", ModdedHelpMessages.COMMAND_BIOME_OPEN_A_BIOME_JSON_IN_YOUR_DESKTOP_EDITOR_NO_KEY_OPENS_THE), - Entry.command("region", "[key]", ModdedHelpMessages.COMMAND_REGION_OPEN_A_REGION_JSON_IN_YOUR_DESKTOP_EDITOR_NO_KEY_OPENS_THE), - Entry.command("dimension", "", ModdedHelpMessages.COMMAND_DIMENSION_OPEN_THE_CURRENT_PACK_S_DIMENSION_JSON_IN_YOUR_DESKTOP_EDITOR) + Entry.command("biome", "[key]", ModdedHelpMessages.COMMAND_BIOME_OPEN_A_BIOME_JSON_IN_YOUR_DESKTOP_EDITOR_NO_KEY_OPENS_THE, "b"), + Entry.command("region", "[key]", ModdedHelpMessages.COMMAND_REGION_OPEN_A_REGION_JSON_IN_YOUR_DESKTOP_EDITOR_NO_KEY_OPENS_THE, "r"), + Entry.command("dimension", "", ModdedHelpMessages.COMMAND_DIMENSION_OPEN_THE_CURRENT_PACK_S_DIMENSION_JSON_IN_YOUR_DESKTOP_EDITOR, "d") )); SECTIONS.put("pregen", List.of( Entry.command("start", " [dimension] [at] [x] [z] [gui] [sync] [nocache]", ModdedHelpMessages.COMMAND_START_START_PREGENERATION_RADIUS_IN_BLOCKS_RESUMABLE_CHECKPOINT_CACHE_ON_BY_DEFAULT_CENTER), @@ -118,12 +129,15 @@ final class ModdedCommandHelp { Entry.command("analyze", "", ModdedHelpMessages.COMMAND_ANALYZE_SHOW_OBJECT_COMPOSITION), Entry.command("shrink", "", ModdedHelpMessages.COMMAND_SHRINK_SHRINK_AN_OBJECT_TO_ITS_MINIMUM_SIZE), Entry.command("plausibilize", " [dryrun=true] [reach=N]", ModdedHelpMessages.COMMAND_PLAUSIBILIZE_GROW_BRANCHES_SO_TREE_LEAVES_SURVIVE_VANILLA_DECAY), - Entry.command("undo", "[amount]", ModdedHelpMessages.COMMAND_UNDO_UNDO_PASTED_OBJECTS, "u") + Entry.command("undo", "[amount]", ModdedHelpMessages.COMMAND_UNDO_UNDO_PASTED_OBJECTS, "u"), + Entry.command("we", "", ModdedHelpMessages.COMMAND_OBJECT_WE_BUKKIT_ONLY), + Entry.command("studio", "", ModdedHelpMessages.COMMAND_OBJECT_STUDIO_BUKKIT_ONLY), + Entry.command("convert", "", ModdedHelpMessages.COMMAND_OBJECT_CONVERT_BUKKIT_ONLY) )); SECTIONS.put("o", SECTIONS.get("object")); SECTIONS.put("studio", List.of( - Entry.command("create", " [template]", ModdedHelpMessages.COMMAND_CREATE_CREATE_A_NEW_PACK_PROJECT, "+"), - Entry.command("package", "[pack]", ModdedHelpMessages.COMMAND_PACKAGE_PACKAGE_A_DIMENSION_INTO_A_COMPRESSED_FORMAT), + Entry.command("create", "[name] [template]", ModdedHelpMessages.COMMAND_CREATE_CREATE_A_NEW_PACK_PROJECT, "+"), + Entry.command("package", "[pack]", ModdedHelpMessages.COMMAND_PACKAGE_PACKAGE_A_DIMENSION_INTO_A_COMPRESSED_FORMAT, "pkg"), Entry.command("version", "[pack]", ModdedHelpMessages.COMMAND_VERSION_PRINT_A_PACK_VERSION), Entry.command("regions", "[radius]", ModdedHelpMessages.COMMAND_REGIONS_CALCULATE_NEARBY_REGION_DISTRIBUTION), Entry.command("open", " [seed]", ModdedHelpMessages.COMMAND_OPEN_OPEN_A_TEMPORARY_STUDIO_DIMENSION_FOR_A_PACK, "o"), @@ -134,7 +148,11 @@ final class ModdedCommandHelp { Entry.command("map", "", ModdedHelpMessages.COMMAND_MAP_OPEN_THE_VISION_MAP_GUI_ON_THE_SERVER_DISPLAY, "render"), Entry.command("vscode", "[pack]", ModdedHelpMessages.COMMAND_VSCODE_REGENERATE_THE_CODE_WORKSPACE_FOR_A_PACK_AND_OPEN_IT_IN_YOUR, "vsc"), Entry.command("update", "[pack]", ModdedHelpMessages.COMMAND_UPDATE_REGENERATE_THE_CODE_WORKSPACE_FOR_A_PACK), - Entry.command("importvanilla", "", ModdedHelpMessages.COMMAND_IMPORTVANILLA_EXPLAIN_VANILLA_IMPORT_WORKFLOW, "importv", "iv") + Entry.command("importvanilla", "", ModdedHelpMessages.COMMAND_IMPORTVANILLA_EXPLAIN_VANILLA_IMPORT_WORKFLOW, "importv", "iv"), + Entry.command("loot", "", ModdedHelpMessages.COMMAND_STUDIO_LOOT_BUKKIT_ONLY), + Entry.command("profile", "", ModdedHelpMessages.COMMAND_STUDIO_PROFILE_BUKKIT_ONLY), + Entry.command("spawn", "", ModdedHelpMessages.COMMAND_STUDIO_SPAWN_BUKKIT_ONLY, "summon"), + Entry.command("objects", "", ModdedHelpMessages.COMMAND_STUDIO_OBJECTS_BUKKIT_ONLY, "find-objects") )); SECTIONS.put("std", SECTIONS.get("studio")); SECTIONS.put("s", SECTIONS.get("studio")); @@ -148,6 +166,7 @@ final class ModdedCommandHelp { SECTIONS.put("world", List.of( Entry.command("enable", " [seed|random]", ModdedHelpMessages.COMMAND_ENABLE_CREATE_AND_INJECT_A_PERSISTENT_IRIS_DIMENSION_AT_RUNTIME_DOWNLOADS_THE_PACK, "create"), Entry.command("replace-overworld", " [seed|random]", ModdedHelpMessages.COMMAND_REPLACE_OVERWORLD_INJECT_AN_IRIS_PRIMARY_WORLD_AND_ROUTE_PLAYERS_THERE_INSTEAD_OF_THE), + Entry.command("mainworld", " [seed|random]", ModdedHelpMessages.COMMAND_MAINWORLD_CONFIGURE_PRIMARY_WORLD_PRESET), Entry.command("disable", "", ModdedHelpMessages.COMMAND_DISABLE_EVACUATE_AND_UNLOAD_AN_IRIS_DIMENSION_WORLD_DATA_ON_DISK_IS_KEPT), Entry.command("delete", "", ModdedHelpMessages.COMMAND_DELETE_DISABLE_AN_IRIS_DIMENSION_AND_WIPE_ITS_CHUNK_AND_MANTLE_DATA_FROM, "remove", "rm"), Entry.command("list", "", ModdedHelpMessages.COMMAND_LIST_LIST_LOADED_IRIS_DIMENSIONS, "ls"), @@ -183,6 +202,24 @@ final class ModdedCommandHelp { private ModdedCommandHelp() { } + static boolean documents(String section, String command) { + List entries = SECTIONS.get(section); + if (entries == null) { + return false; + } + for (Entry entry : entries) { + if (entry.name().equals(command)) { + return true; + } + for (String alias : entry.aliases()) { + if (alias.equals(command)) { + return true; + } + } + } + return false; + } + static int send(CommandSourceStack source, String path) { Request request = parse(path); List entries = SECTIONS.get(request.section()); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDustRevealer.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDustRevealer.java index 8b7ec3de6..b0897ccbd 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDustRevealer.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDustRevealer.java @@ -21,16 +21,31 @@ package art.arcane.iris.modded.command; import art.arcane.iris.core.localization.IrisLanguage; import art.arcane.iris.core.localization.RuntimeUiMessages; import art.arcane.iris.engine.framework.Engine; +import art.arcane.iris.engine.framework.GenerationSessionException; +import art.arcane.iris.engine.framework.GenerationSessionLease; import art.arcane.iris.engine.object.IrisBiome; import art.arcane.iris.engine.object.IrisRegion; import art.arcane.iris.modded.ModdedBlockState; +import art.arcane.iris.modded.ModdedEngineBootstrap; +import art.arcane.iris.modded.ModdedScheduler; +import art.arcane.iris.util.project.context.IrisContext; import art.arcane.volmlib.util.localization.MessageArgument; +import net.minecraft.ChatFormatting; import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; import net.minecraft.core.particles.DustParticleOptions; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.Component; -import net.minecraft.server.MinecraftServer; +import net.minecraft.network.chat.HoverEvent; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.level.biome.Biome; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,12 +55,18 @@ import java.util.Deque; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; public final class ModdedDustRevealer { private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); - private static final int MAX_HITS = 2048; + private static final int MAX_HITS = 2_048; + private static final int PARTICLE_BATCH_SIZE = 64; private static final DustParticleOptions REVEAL_DUST = new DustParticleOptions(0xFFD24A, 1.2F); + private static final ConcurrentHashMap ACTIVE_RUNS = new ConcurrentHashMap<>(); private ModdedDustRevealer() { } @@ -53,76 +74,167 @@ public final class ModdedDustRevealer { public static void reveal(ServerPlayer player, ServerLevel level, BlockPos pos) { Engine engine = IrisModdedCommands.engineFor(level); if (engine == null) { - player.sendSystemMessage(Component.literal(IrisLanguage.plain(RuntimeUiMessages.DUST_IRIS_WORLD_REQUIRED))); + player.sendSystemMessage(Component.literal( + IrisLanguage.plain(RuntimeUiMessages.DUST_IRIS_WORLD_REQUIRED))); return; } describe(player, level, engine, pos); int relativeY = pos.getY() - engine.getMinHeight(); - String key = safe(() -> engine.getObjectPlacementKey(pos.getX(), relativeY, pos.getZ())); + String key = safe( + "object lookup at " + coordinates(pos), + () -> engine.getObjectPlacementKey(pos.getX(), relativeY, pos.getZ())); if (key == null) { return; } + ModdedScheduler scheduler = ModdedEngineBootstrap.schedulerOrNull(); + if (scheduler == null) { + player.sendSystemMessage(Component.literal( + IrisLanguage.plain(RuntimeUiMessages.DUST_REVEAL_FAILED))); + return; + } + level.playSound(null, pos, SoundEvents.LODESTONE_COMPASS_LOCK, + SoundSource.PLAYERS, 1.0F, 0.1F); player.sendSystemMessage(Component.literal(IrisLanguage.plain( RuntimeUiMessages.DUST_FOUND_OBJECT, MessageArgument.untrusted("object", key) ))); - MinecraftServer server = level.getServer(); - BlockPos origin = pos.immutable(); - Thread thread = new Thread(() -> { - List hits = collect(engine, level, origin, key); - server.execute(() -> { - for (BlockPos hit : hits) { - level.sendParticles(player, REVEAL_DUST, true, true, - hit.getX() + 0.5D, hit.getY() + 0.5D, hit.getZ() + 0.5D, - 3, 0.25D, 0.25D, 0.25D, 0.0D); - } - player.sendSystemMessage(Component.literal(IrisLanguage.plain( - hits.size() >= MAX_HITS ? RuntimeUiMessages.DUST_REVEALED_CAPPED : RuntimeUiMessages.DUST_REVEALED, - MessageArgument.trusted("count", hits.size()), - MessageArgument.untrusted("object", key) - ))); - }); - }, "Iris Dust Revealer"); - thread.setDaemon(true); - thread.start(); + + RevealRun run = new RevealRun( + player.getUUID(), + player, + level, + engine, + pos.immutable(), + key, + level.getMinY(), + level.getMaxY(), + new AtomicBoolean()); + RevealRun previous = ACTIVE_RUNS.put(player.getUUID(), run); + if (previous != null) { + previous.cancelled().set(true); + } + scheduler.async(() -> discover(scheduler, run)); } - private static List collect(Engine engine, ServerLevel level, BlockPos origin, String key) { + static void clear() { + for (RevealRun run : ACTIVE_RUNS.values()) { + run.cancelled().set(true); + } + ACTIVE_RUNS.clear(); + } + + private static void discover(ModdedScheduler scheduler, RevealRun run) { + try (GenerationSessionLease lease = run.engine().acquireGenerationLease("modded_dust_reveal"); + IrisContext.Scope ignored = IrisContext.open(run.engine(), lease.sessionId(), null)) { + List hits = collect(run); + if (!run.cancelled().get()) { + scheduler.global(() -> revealBatch(scheduler, run, hits, 0)); + } + } catch (GenerationSessionException error) { + revealFailure(scheduler, run, error); + } catch (Throwable error) { + revealFailure(scheduler, run, error); + } + } + + static List collect(RevealRun run) { + return collect( + run.origin(), + run.key(), + run.engine().getMinHeight(), + run.minY(), + run.maxY(), + run.cancelled(), + (int x, int relativeY, int z) -> + run.engine().getObjectPlacementKey(x, relativeY, z)); + } + + static List collect(BlockPos origin, String key, int engineMinY, + int minY, int maxY, AtomicBoolean cancelled, + ObjectPlacementLookup lookup) { List hits = new ArrayList<>(); Set visited = new HashSet<>(); Deque frontier = new ArrayDeque<>(); frontier.add(origin); visited.add(origin); - int minY = level.getMinY(); - int maxY = minY + level.getHeight(); - try { - while (!frontier.isEmpty() && hits.size() < MAX_HITS) { - BlockPos current = frontier.poll(); - hits.add(current); - for (int dx = -1; dx <= 1; dx++) { - for (int dy = -1; dy <= 1; dy++) { - for (int dz = -1; dz <= 1; dz++) { - if (dx == 0 && dy == 0 && dz == 0) { - continue; - } - BlockPos next = current.offset(dx, dy, dz); - if (next.getY() < minY || next.getY() >= maxY || visited.contains(next)) { - continue; - } - visited.add(next); - String nextKey = engine.getObjectPlacementKey(next.getX(), next.getY() - engine.getMinHeight(), next.getZ()); - if (key.equals(nextKey)) { - frontier.add(next); - } + while (!frontier.isEmpty() && hits.size() < MAX_HITS && !cancelled.get()) { + BlockPos current = frontier.poll(); + hits.add(current); + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + for (int dz = -1; dz <= 1; dz++) { + if (dx == 0 && dy == 0 && dz == 0) { + continue; + } + BlockPos next = current.offset(dx, dy, dz); + if (next.getY() < minY + || next.getY() >= maxY + || !visited.add(next)) { + continue; + } + String nextKey = lookup.at( + next.getX(), next.getY() - engineMinY, next.getZ()); + if (key.equals(nextKey)) { + frontier.add(next); } } } } - } catch (Throwable e) { - LOGGER.error("Iris dust reveal BFS failed for {}", key, e); } - return hits; + return List.copyOf(hits); + } + + private static void revealBatch(ModdedScheduler scheduler, RevealRun run, + List hits, int from) { + if (!active(run)) { + return; + } + int to = Math.min(hits.size(), from + PARTICLE_BATCH_SIZE); + for (int index = from; index < to; index++) { + BlockPos hit = hits.get(index); + run.level().sendParticles(run.player(), REVEAL_DUST, true, true, + hit.getX() + 0.5D, hit.getY() + 0.5D, hit.getZ() + 0.5D, + 3, 0.25D, 0.25D, 0.25D, 0.0D); + } + if (to > from) { + BlockPos soundAt = hits.get(from); + run.level().playSound(null, soundAt, SoundEvents.AMETHYST_BLOCK_CHIME, + SoundSource.PLAYERS, 0.5F, + ThreadLocalRandom.current().nextFloat(0.2F, 2.0F)); + } + if (to < hits.size()) { + scheduler.laterGlobal(() -> revealBatch(scheduler, run, hits, to), 1); + return; + } + ACTIVE_RUNS.remove(run.playerId(), run); + run.player().sendSystemMessage(Component.literal(IrisLanguage.plain( + hits.size() >= MAX_HITS + ? RuntimeUiMessages.DUST_REVEALED_CAPPED + : RuntimeUiMessages.DUST_REVEALED, + MessageArgument.trusted("count", hits.size()), + MessageArgument.untrusted("object", run.key()) + ))); + } + + private static boolean active(RevealRun run) { + return !run.cancelled().get() + && ACTIVE_RUNS.get(run.playerId()) == run + && !run.player().hasDisconnected() + && !run.player().isRemoved() + && run.player().level() == run.level() + && !run.engine().isClosing() + && !run.engine().isClosed(); + } + + private static void revealFailure(ModdedScheduler scheduler, RevealRun run, Throwable error) { + LOGGER.error("Iris dust reveal failed for {} at {}", run.key(), coordinates(run.origin()), error); + scheduler.global(() -> { + if (ACTIVE_RUNS.remove(run.playerId(), run)) { + run.player().sendSystemMessage(Component.literal( + IrisLanguage.plain(RuntimeUiMessages.DUST_REVEAL_FAILED))); + } + }); } private static void describe(ServerPlayer player, ServerLevel level, Engine engine, BlockPos pos) { @@ -131,103 +243,248 @@ public final class ModdedDustRevealer { int z = pos.getZ(); int minHeight = engine.getMinHeight(); int relativeY = y - minHeight; - int surfaceRelative = safeInt(() -> engine.getHeight(x, z, true)); - int surfaceY = surfaceRelative + minHeight; - int offset = y - surfaceY; + Integer surfaceRelative = safe( + "surface height lookup at " + coordinates(pos), + () -> engine.getHeight(x, z, true)); + Integer surfaceY = surfaceRelative == null ? null : surfaceRelative + minHeight; + Integer offset = surfaceY == null ? null : y - surfaceY; - String objectKey = safe(() -> engine.getObjectPlacementKey(x, relativeY, z)); - IrisBiome surfaceBiome = safe(() -> engine.getSurfaceBiome(x, z)); - IrisBiome biomeHere = safe(() -> engine.getBiome(x, relativeY, z)); - IrisBiome caveBiome = safe(() -> engine.getCaveOrMantleBiome(x, relativeY, z)); - IrisRegion region = safe(() -> engine.getRegion(x, z)); + String objectKey = safe( + "object lookup at " + coordinates(pos), + () -> engine.getObjectPlacementKey(x, relativeY, z)); + IrisBiome surfaceBiome = safe( + "surface biome lookup at " + x + ", " + z, + () -> engine.getSurfaceBiome(x, z)); + IrisBiome biomeHere = safe( + "biome lookup at " + coordinates(pos), + () -> engine.getBiome(x, relativeY, z)); + IrisBiome caveBiome = safe( + "cave biome lookup at " + coordinates(pos), + () -> engine.getCaveOrMantleBiome(x, relativeY, z)); + IrisRegion region = safe( + "region lookup at " + x + ", " + z, + () -> engine.getRegion(x, z)); - List lines = new ArrayList<>(); - lines.add(IrisLanguage.plain( + List lines = new ArrayList<>(); + lines.add(new DustLine(IrisLanguage.plain( RuntimeUiMessages.DUST_HEADER, MessageArgument.trusted("x", x), MessageArgument.trusted("y", y), MessageArgument.trusted("z", z) - )); - lines.add(IrisLanguage.plain( + ), false)); + lines.add(new DustLine(IrisLanguage.plain( RuntimeUiMessages.DUST_BLOCK, MessageArgument.untrusted("block", ModdedBlockState.serialize(level.getBlockState(pos))) - )); - if (offset > 0) { - lines.add(IrisLanguage.plain( - RuntimeUiMessages.DUST_POSITION_ABOVE, - MessageArgument.trusted("offset", offset), - MessageArgument.trusted("surfaceY", surfaceY) - )); - } else if (offset < 0) { - lines.add(IrisLanguage.plain( - RuntimeUiMessages.DUST_POSITION_BELOW, - MessageArgument.trusted("offset", -offset), - MessageArgument.trusted("surfaceY", surfaceY) - )); - } else { - lines.add(IrisLanguage.plain( - RuntimeUiMessages.DUST_POSITION_AT, - MessageArgument.trusted("surfaceY", surfaceY) - )); + ), false)); + if (offset != null && surfaceY != null) { + lines.add(new DustLine(positionLine(offset, surfaceY), true)); + lines.add(new DustLine( + placementLine(offset, surfaceRelative, relativeY, objectKey), true)); } - lines.add(IrisLanguage.plain( + lines.add(new DustLine(IrisLanguage.plain( RuntimeUiMessages.DUST_OBJECT_AT_BLOCK, MessageArgument.untrusted( "object", - objectKey == null ? IrisLanguage.plain(RuntimeUiMessages.DUST_NONE) : objectKey - ) - )); - if (surfaceBiome != null) { - lines.add(IrisLanguage.plain( - RuntimeUiMessages.DUST_SURFACE_BIOME, - MessageArgument.untrusted("biome", surfaceBiome.getLoadKey()) - )); + objectKey == null + ? IrisLanguage.plain(RuntimeUiMessages.DUST_NONE) + : objectKey) + ), false)); + if (objectKey == null) { + String columnObject = findColumnObject(engine, x, relativeY, z, minHeight); + lines.add(new DustLine(IrisLanguage.plain( + columnObject == null + ? RuntimeUiMessages.DUST_COLUMN_OBJECT_NONE + : RuntimeUiMessages.DUST_COLUMN_OBJECT, + MessageArgument.trusted( + columnObject == null ? "detail" : "object", + columnObject == null + ? IrisLanguage.plain(RuntimeUiMessages.DUST_COLUMN_NONE) + : columnObject) + ), false)); } - if (biomeHere != null && (surfaceBiome == null || !biomeHere.getLoadKey().equals(surfaceBiome.getLoadKey()))) { - lines.add(IrisLanguage.plain( + if (surfaceBiome != null) { + lines.add(new DustLine(IrisLanguage.plain( + RuntimeUiMessages.DUST_SURFACE_BIOME_DETAIL, + MessageArgument.untrusted("biome", surfaceBiome.getLoadKey()), + MessageArgument.untrusted("derivative", surfaceBiome.getDerivativeKey()) + ), false)); + } + if (biomeHere != null + && (surfaceBiome == null + || !biomeHere.getLoadKey().equals(surfaceBiome.getLoadKey()))) { + lines.add(new DustLine(IrisLanguage.plain( RuntimeUiMessages.DUST_BIOME_AT_Y, MessageArgument.untrusted("biome", biomeHere.getLoadKey()) - )); + ), false)); } - if (caveBiome != null && (surfaceBiome == null || !caveBiome.getLoadKey().equals(surfaceBiome.getLoadKey()))) { - lines.add(IrisLanguage.plain( + if (caveBiome != null + && (surfaceBiome == null + || !caveBiome.getLoadKey().equals(surfaceBiome.getLoadKey()))) { + lines.add(new DustLine(IrisLanguage.plain( RuntimeUiMessages.DUST_CAVE_BIOME, MessageArgument.untrusted("biome", caveBiome.getLoadKey()) - )); + ), false)); } + NativeBiome nativeBiome = nativeBiome(level, pos); + lines.add(new DustLine(IrisLanguage.plain( + RuntimeUiMessages.DUST_SERVER_BIOME, + MessageArgument.untrusted("biome", nativeBiome.key()), + MessageArgument.trusted("id", nativeBiome.id()) + ), false)); if (region != null) { - lines.add(IrisLanguage.plain( + lines.add(new DustLine(IrisLanguage.plain( RuntimeUiMessages.DUST_REGION, MessageArgument.untrusted("region", region.getLoadKey()), MessageArgument.untrusted("name", region.getName()) - )); + ), false)); } - Set objects = safe(() -> engine.getObjectsAt(x >> 4, z >> 4)); + Set objects = safe( + "chunk object lookup at " + (x >> 4) + ", " + (z >> 4), + () -> engine.getObjectsAt(x >> 4, z >> 4)); if (objects != null && !objects.isEmpty()) { - lines.add(IrisLanguage.plain( + lines.add(new DustLine(IrisLanguage.plain( RuntimeUiMessages.DUST_OBJECTS_IN_CHUNK, - MessageArgument.untrusted("objects", objects) - )); - } - for (String line : lines) { - player.sendSystemMessage(Component.literal(line)); + MessageArgument.untrusted("objects", objects.stream().sorted().toList()) + ), false)); } + sendReport(player, lines); } - private static T safe(Supplier supplier) { + static String positionLine(int offset, int surfaceY) { + if (offset > 0) { + return IrisLanguage.plain( + RuntimeUiMessages.DUST_POSITION_ABOVE, + MessageArgument.trusted("offset", offset), + MessageArgument.trusted("surfaceY", surfaceY)); + } + if (offset < 0) { + return IrisLanguage.plain( + RuntimeUiMessages.DUST_POSITION_BELOW, + MessageArgument.trusted("offset", -offset), + MessageArgument.trusted("surfaceY", surfaceY)); + } + return IrisLanguage.plain( + RuntimeUiMessages.DUST_POSITION_AT, + MessageArgument.trusted("surfaceY", surfaceY)); + } + + static String placementLine(int offset, int surfaceRelative, + int relativeY, String objectKey) { + if (offset > 0) { + return objectKey == null + ? IrisLanguage.plain(RuntimeUiMessages.DUST_PLACED_BY_DECORATION_ABOVE) + : IrisLanguage.plain( + RuntimeUiMessages.DUST_PLACED_BY_OBJECT_ABOVE, + MessageArgument.untrusted("object", objectKey)); + } + if (objectKey != null) { + return IrisLanguage.plain( + RuntimeUiMessages.DUST_PLACED_BY_BURIED_OBJECT, + MessageArgument.untrusted("object", objectKey)); + } + return IrisLanguage.plain( + RuntimeUiMessages.DUST_PLACED_BY_TERRAIN, + MessageArgument.trusted("depth", Math.max(0, surfaceRelative - relativeY))); + } + + private static String findColumnObject(Engine engine, int x, int relativeY, + int z, int minHeight) { + int maxRelativeY = engine.getMaxHeight() - minHeight - 1; + try { + for (int dy = 1; dy <= 64; dy++) { + if (relativeY + dy <= maxRelativeY) { + String up = engine.getObjectPlacementKey(x, relativeY + dy, z); + if (up != null) { + return IrisLanguage.plain( + RuntimeUiMessages.DUST_COLUMN_ABOVE, + MessageArgument.untrusted("object", up), + MessageArgument.trusted("y", relativeY + dy + minHeight)); + } + } + if (relativeY - dy >= 0) { + String down = engine.getObjectPlacementKey(x, relativeY - dy, z); + if (down != null) { + return IrisLanguage.plain( + RuntimeUiMessages.DUST_COLUMN_BELOW, + MessageArgument.untrusted("object", down), + MessageArgument.trusted("y", relativeY - dy + minHeight)); + } + } + } + } catch (Throwable error) { + LOGGER.error("Iris dust column-object lookup failed at {}, {}, {}", + x, relativeY + minHeight, z, error); + } + return null; + } + + private static NativeBiome nativeBiome(ServerLevel level, BlockPos pos) { + Holder holder = level.getBiome(pos); + String key = holder.unwrapKey() + .map((ResourceKey resourceKey) -> resourceKey.identifier().toString()) + .orElse(IrisLanguage.plain(RuntimeUiMessages.STATUS_UNREGISTERED)); + Registry registry = level.registryAccess().lookupOrThrow(Registries.BIOME); + return new NativeBiome(key, registry.getId(holder.value())); + } + + private static void sendReport(ServerPlayer player, List lines) { + StringBuilder payload = new StringBuilder(); + for (int index = 0; index < lines.size(); index++) { + DustLine line = lines.get(index); + ChatFormatting color = index == 0 + ? ChatFormatting.GOLD + : line.emphasis() ? ChatFormatting.YELLOW : ChatFormatting.WHITE; + player.sendSystemMessage(Component.literal(line.text()).withStyle(color)); + if (index > 0) { + payload.append('\n'); + } + payload.append(line.text()); + } + MutableComponent hover = Component.literal( + IrisLanguage.plain(RuntimeUiMessages.DUST_COPY_HOVER)); + MutableComponent button = Component.literal( + IrisLanguage.plain(RuntimeUiMessages.DUST_COPY_BUTTON)) + .withStyle(ChatFormatting.GREEN) + .withStyle(style -> style + .withClickEvent(new ClickEvent.CopyToClipboard(payload.toString())) + .withHoverEvent(new HoverEvent.ShowText(hover))); + player.sendSystemMessage(button); + } + + private static T safe(String operation, Supplier supplier) { try { return supplier.get(); - } catch (Throwable e) { + } catch (Throwable error) { + LOGGER.error("Iris dust {} failed", operation, error); return null; } } - private static int safeInt(Supplier supplier) { - try { - Integer value = supplier.get(); - return value == null ? 0 : value; - } catch (Throwable e) { - return 0; - } + private static String coordinates(BlockPos pos) { + return pos.getX() + ", " + pos.getY() + ", " + pos.getZ(); + } + + private record DustLine(String text, boolean emphasis) { + } + + private record NativeBiome(String key, int id) { + } + + @FunctionalInterface + interface ObjectPlacementLookup { + String at(int x, int relativeY, int z); + } + + static record RevealRun( + UUID playerId, + ServerPlayer player, + ServerLevel level, + Engine engine, + BlockPos origin, + String key, + int minY, + int maxY, + AtomicBoolean cancelled + ) { } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java index ae1583e13..1f4f86541 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java @@ -233,7 +233,7 @@ public final class ModdedObjectCommands { return 1; } - private static int giveDust(CommandSourceStack source) { + static int giveDust(CommandSourceStack source) { ServerPlayer player = source.getPlayer(); if (player == null) { IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_DUST_IS)); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStudioCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStudioCommands.java index ad5bea398..1e2332f67 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStudioCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStudioCommands.java @@ -115,21 +115,11 @@ public final class ModdedStudioCommands { root.executes((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name)); - root.then(Commands.literal("create") - .then(Commands.argument("name", StringArgumentType.word()) - .executes((CommandContext context) -> create(context.getSource(), StringArgumentType.getString(context, "name"), DEFAULT_TEMPLATE)) - .then(Commands.argument("template", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> create(context.getSource(), StringArgumentType.getString(context, "name"), StringArgumentType.getString(context, "template")))))); - root.then(Commands.literal("+") - .then(Commands.argument("name", StringArgumentType.word()) - .executes((CommandContext context) -> create(context.getSource(), StringArgumentType.getString(context, "name"), DEFAULT_TEMPLATE)) - .then(Commands.argument("template", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> create(context.getSource(), StringArgumentType.getString(context, "name"), StringArgumentType.getString(context, "template")))))); + root.then(createTree("create")); + root.then(createTree("+")); - root.then(Commands.literal("package") - .executes((CommandContext context) -> pkg(context.getSource(), null)) - .then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> pkg(context.getSource(), StringArgumentType.getString(context, "pack"))))); + root.then(packageTree("package")); + root.then(packageTree("pkg")); root.then(Commands.literal("version") .executes((CommandContext context) -> version(context.getSource(), null)) @@ -173,6 +163,30 @@ public final class ModdedStudioCommands { return root; } + private static LiteralArgumentBuilder createTree(String name) { + return Commands.literal(name) + .executes((CommandContext context) -> + create(context.getSource(), "studio", DEFAULT_TEMPLATE)) + .then(Commands.argument("name", StringArgumentType.word()) + .executes((CommandContext context) -> + create(context.getSource(), StringArgumentType.getString(context, "name"), DEFAULT_TEMPLATE)) + .then(Commands.argument("template", StringArgumentType.word()) + .suggests(IrisModdedCommands.PACK_NAMES) + .executes((CommandContext context) -> + create(context.getSource(), + StringArgumentType.getString(context, "name"), + StringArgumentType.getString(context, "template"))))); + } + + private static LiteralArgumentBuilder packageTree(String name) { + return Commands.literal(name) + .executes((CommandContext context) -> pkg(context.getSource(), null)) + .then(Commands.argument("pack", StringArgumentType.word()) + .suggests(IrisModdedCommands.PACK_NAMES) + .executes((CommandContext context) -> + pkg(context.getSource(), StringArgumentType.getString(context, "pack")))); + } + public static void clear() { STUDIOS.clear(); } @@ -368,7 +382,7 @@ public final class ModdedStudioCommands { File packFolder = new File(ModdedPackCommands.packsRoot(), pack); if (!new File(packFolder, "dimensions/" + pack + ".json").isFile()) { server.execute(() -> IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACK_MISSING_DOWNLOADING_IRISDIMENSIONS, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("pack2", pack)))); - boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), pack, "master", + boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), pack, "master", false, (String line) -> server.execute(() -> IrisModdedCommands.ok(source, line))); if (!installed || !new File(packFolder, "dimensions/" + pack + ".json").isFile()) { server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACK_COULD_NOT_BE_DOWNLOADED_CHECK_NAME_TRY_IRIS_DOWNLOAD, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("pack2", pack)))); @@ -573,7 +587,7 @@ public final class ModdedStudioCommands { File templateFolder = new File(packsRoot, template); if (!new File(templateFolder, "dimensions/" + template + ".json").isFile()) { server.execute(() -> IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_TEMPLATE_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS, MessageArgument.untrusted("template", template), MessageArgument.untrusted("template2", template)))); - boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), template, "master", + boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), template, "master", false, (String line) -> server.execute(() -> IrisModdedCommands.ok(source, line))); if (!installed || !new File(templateFolder, "dimensions/" + template + ".json").isFile()) { server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_TEMPLATE_COULD_NOT_BE_DOWNLOADED_INSTALL_PACK_WITH_DIMENSIONS_JSON, MessageArgument.untrusted("template", template), MessageArgument.untrusted("template2", template)))); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWandService.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWandService.java index 746154e6e..a430491f1 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWandService.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWandService.java @@ -40,6 +40,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.item.component.CustomData; import net.minecraft.world.item.component.ItemLore; +import net.minecraft.world.item.component.TooltipDisplay; import net.minecraft.world.level.Level; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -88,6 +89,8 @@ public final class ModdedWandService { stack.set(DataComponents.UNBREAKABLE, Unit.INSTANCE); stack.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, Boolean.TRUE); stack.set(DataComponents.CUSTOM_DATA, CustomData.of(flagTag(WAND_TAG))); + stack.set(DataComponents.TOOLTIP_DISPLAY, + TooltipDisplay.DEFAULT.withHidden(DataComponents.UNBREAKABLE, true)); return stack; } @@ -99,6 +102,8 @@ public final class ModdedWandService { stack.set(DataComponents.UNBREAKABLE, Unit.INSTANCE); stack.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, Boolean.TRUE); stack.set(DataComponents.CUSTOM_DATA, CustomData.of(flagTag(DUST_TAG))); + stack.set(DataComponents.TOOLTIP_DISPLAY, + TooltipDisplay.DEFAULT.withHidden(DataComponents.UNBREAKABLE, true)); return stack; } @@ -187,6 +192,8 @@ public final class ModdedWandService { public static void clearAll() { SELECTIONS.clear(); + ModdedDustRevealer.clear(); + ModdedWhatCommands.clear(); } public static void serverTick(MinecraftServer server) { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWhatCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWhatCommands.java new file mode 100644 index 000000000..baaa8332c --- /dev/null +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWhatCommands.java @@ -0,0 +1,535 @@ +/* + * Iris is a World Generator for Minecraft Servers + * Copyright (c) 2026 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package art.arcane.iris.modded.command; + +import art.arcane.iris.core.localization.IrisLanguage; +import art.arcane.iris.core.localization.IrisMessages; +import art.arcane.iris.core.localization.ModdedCommandMessages; +import art.arcane.iris.core.localization.RuntimeUiMessages; +import art.arcane.iris.engine.framework.Engine; +import art.arcane.iris.engine.framework.GenerationSessionException; +import art.arcane.iris.engine.framework.GenerationSessionLease; +import art.arcane.iris.engine.object.IrisBiome; +import art.arcane.iris.engine.object.IrisPosition; +import art.arcane.iris.engine.object.IrisRegion; +import art.arcane.iris.modded.ModdedBlockState; +import art.arcane.iris.modded.ModdedEngineBootstrap; +import art.arcane.iris.modded.ModdedScheduler; +import art.arcane.iris.spi.PlatformBlockState; +import art.arcane.iris.util.project.context.IrisContext; +import art.arcane.volmlib.util.localization.MessageArgument; +import art.arcane.volmlib.util.localization.TextKey; +import art.arcane.volmlib.util.matter.MatterMarker; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.Registry; +import net.minecraft.core.particles.DustParticleOptions; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.RandomizableContainer; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.SpawnerBlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; + +public final class ModdedWhatCommands { + private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); + private static final Predicate GATE = + Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); + private static final SuggestionProvider MARKER_TYPES = + (CommandContext context, SuggestionsBuilder builder) -> + SharedSuggestionProvider.suggest( + List.of("cave_floor", "cave_ceiling", "object"), builder); + private static final DustParticleOptions MARKER_DUST = new DustParticleOptions(0x5A8CFF, 1.2F); + private static final int MAX_MARKERS = 8_192; + private static final int MARKER_BATCH_SIZE = 128; + private static final ConcurrentHashMap ACTIVE_MARKER_RUNS = new ConcurrentHashMap<>(); + + private ModdedWhatCommands() { + } + + public static LiteralArgumentBuilder tree() { + return Commands.literal("what").requires(GATE) + .executes((CommandContext context) -> + inspectHere(context.getSource())) + .then(Commands.literal("here") + .executes((CommandContext context) -> + inspectHere(context.getSource()))) + .then(Commands.literal("biome") + .executes((CommandContext context) -> + inspectBiome(context.getSource()))) + .then(Commands.literal("region") + .executes((CommandContext context) -> + inspectRegion(context.getSource()))) + .then(Commands.literal("block") + .executes((CommandContext context) -> + inspectBlock(context.getSource()))) + .then(Commands.literal("hand") + .executes((CommandContext context) -> + inspectHand(context.getSource()))) + .then(Commands.literal("markers") + .then(Commands.argument("marker", StringArgumentType.greedyString()) + .suggests(MARKER_TYPES) + .executes((CommandContext context) -> + inspectMarkers( + context.getSource(), + StringArgumentType.getString(context, "marker"))))); + } + + static void clear() { + for (MarkerRun run : ACTIVE_MARKER_RUNS.values()) { + run.cancelled().set(true); + } + ACTIVE_MARKER_RUNS.clear(); + } + + private static int inspectHere(CommandSourceStack source) { + ServerPlayer player = source.getPlayer(); + if (player == null) { + return playerRequired(source, ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_2); + } + ServerLevel level = source.getLevel(); + Engine engine = IrisModdedCommands.engineFor(level); + if (engine == null) { + IrisModdedCommands.fail(source, IrisLanguage.plain( + ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_6)); + return 0; + } + BlockPos pos = player.blockPosition(); + int result = inspectBiome(source); + result &= inspectRegion(source); + int relativeY = pos.getY() - engine.getMinHeight(); + try { + IrisBiome cave = engine.getCaveOrMantleBiome(pos.getX(), relativeY, pos.getZ()); + IrisModdedCommands.ok(source, IrisLanguage.plain( + ModdedCommandMessages.IRIS_MODDED_COMMANDS_CAVE_BIOME, + MessageArgument.untrusted("value", cave == null + ? IrisLanguage.plain(RuntimeUiMessages.STATUS_NONE) + : cave.getLoadKey()))); + } catch (Throwable error) { + logLookupFailure(source, "cave biome", error, + ModdedCommandMessages.IRIS_MODDED_COMMANDS_CAVE_BIOME_LOOKUP_FAILED); + result = 0; + } + int surfaceY = level.getHeight(Heightmap.Types.WORLD_SURFACE, pos.getX(), pos.getZ()) - 1; + BlockState surface = level.getBlockState(new BlockPos(pos.getX(), surfaceY, pos.getZ())); + IrisModdedCommands.ok(source, IrisLanguage.plain( + ModdedCommandMessages.IRIS_MODDED_COMMANDS_SURFACE_BLOCK_Y, + MessageArgument.untrusted("value", BuiltInRegistries.BLOCK.getKey(surface.getBlock())), + MessageArgument.trusted("value2", surfaceY))); + sendPosition(source, pos); + return result; + } + + private static int inspectBiome(CommandSourceStack source) { + ServerPlayer player = source.getPlayer(); + if (player == null) { + return playerRequired(source, ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_2); + } + ServerLevel level = source.getLevel(); + BlockPos pos = player.blockPosition(); + NativeBiome nativeBiome = nativeBiome(level, pos); + Engine engine = IrisModdedCommands.engineFor(level); + if (engine == null) { + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WHAT_NON_IRIS_BIOME, + MessageArgument.untrusted("biome", nativeBiome.key()), + MessageArgument.trusted("id", nativeBiome.id()))); + return 1; + } + try { + IrisBiome biome = engine.getBiome( + pos.getX(), pos.getY() - engine.getMinHeight(), pos.getZ()); + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WHAT_IRIS_BIOME, + MessageArgument.untrusted("biome", biome.getLoadKey()), + MessageArgument.untrusted("name", biome.getName()))); + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WHAT_DERIVATIVE_BIOME, + MessageArgument.untrusted("biome", biome.getDerivativeKey()))); + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WHAT_NATIVE_BIOME, + MessageArgument.untrusted("biome", nativeBiome.key()), + MessageArgument.trusted("id", nativeBiome.id()))); + return 1; + } catch (Throwable error) { + logLookupFailure(source, "biome", error, + ModdedCommandMessages.IRIS_MODDED_COMMANDS_BIOME_LOOKUP_FAILED_2); + return 0; + } + } + + private static int inspectRegion(CommandSourceStack source) { + ServerPlayer player = source.getPlayer(); + if (player == null) { + return playerRequired(source, ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_2); + } + Engine engine = IrisModdedCommands.engineFor(source.getLevel()); + if (engine == null) { + IrisModdedCommands.fail(source, IrisLanguage.plain( + ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_6)); + return 0; + } + BlockPos pos = player.blockPosition(); + int centerX = (pos.getX() & ~15) + 8; + int centerZ = (pos.getZ() & ~15) + 8; + try { + IrisRegion region = engine.getRegion(centerX, centerZ); + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WHAT_IRIS_REGION, + MessageArgument.untrusted("region", region.getLoadKey()), + MessageArgument.untrusted("name", region.getName()))); + return 1; + } catch (Throwable error) { + logLookupFailure(source, "region", error, + ModdedCommandMessages.IRIS_MODDED_COMMANDS_REGION_LOOKUP_FAILED_2); + return 0; + } + } + + private static int inspectHand(CommandSourceStack source) { + ServerPlayer player = source.getPlayer(); + if (player == null) { + return playerRequired(source, ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_IT_INSPECTS); + } + ItemStack stack = player.getMainHandItem(); + if (stack.isEmpty()) { + IrisModdedCommands.fail(source, IrisLanguage.plain( + ModdedCommandMessages.IRIS_MODDED_COMMANDS_YOUR_MAIN_HAND_IS_EMPTY)); + return 0; + } + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WHAT_MATERIAL, + MessageArgument.untrusted("material", BuiltInRegistries.ITEM.getKey(stack.getItem())))); + if (stack.getItem() instanceof BlockItem blockItem) { + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WHAT_FULL_STATE, + MessageArgument.untrusted( + "state", ModdedBlockState.serialize(blockItem.getBlock().defaultBlockState())))); + } + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WHAT_ITEM_COUNT, + MessageArgument.trusted("count", stack.getCount()))); + return 1; + } + + private static int inspectBlock(CommandSourceStack source) { + ServerPlayer player = source.getPlayer(); + if (player == null) { + return playerRequired(source, ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_IT_INSPECTS_2); + } + HitResult hit = player.pick(128.0D, 1.0F, false); + if (!(hit instanceof BlockHitResult blockHit) || hit.getType() != HitResult.Type.BLOCK) { + IrisModdedCommands.fail(source, IrisLanguage.plain( + ModdedCommandMessages.IRIS_MODDED_COMMANDS_LOOK_AT_BLOCK_NOT_SKY)); + return 0; + } + ServerLevel level = source.getLevel(); + BlockPos pos = blockHit.getBlockPos(); + BlockState state = level.getBlockState(pos); + PlatformBlockState platform = ModdedBlockState.of(state, null); + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WHAT_MATERIAL, + MessageArgument.untrusted("material", BuiltInRegistries.BLOCK.getKey(state.getBlock())))); + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WHAT_FULL_STATE, + MessageArgument.untrusted("state", ModdedBlockState.serialize(state)))); + sendPosition(source, pos); + sendProperties(source, platform); + sendObject(source, level, pos); + sendBlockEntity(source, level, pos); + return 1; + } + + private static void sendPosition(CommandSourceStack source, BlockPos pos) { + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WHAT_POSITION, + MessageArgument.trusted("x", pos.getX()), + MessageArgument.trusted("y", pos.getY()), + MessageArgument.trusted("z", pos.getZ()), + MessageArgument.trusted("chunkX", pos.getX() >> 4), + MessageArgument.trusted("chunkZ", pos.getZ() >> 4))); + } + + private static void sendProperties(CommandSourceStack source, PlatformBlockState platform) { + List flags = propertyNames(platform); + if (flags.isEmpty()) { + IrisModdedCommands.ok(source, IrisLanguage.plain(IrisMessages.MODDED_PROPERTIES_NONE)); + return; + } + IrisModdedCommands.ok(source, IrisLanguage.plain( + IrisMessages.MODDED_PROPERTIES, + MessageArgument.untrusted("properties", String.join(", ", flags)))); + } + + static List propertyNames(PlatformBlockState platform) { + List flags = new ArrayList<>(); + addFlag(flags, platform.isSolid(), RuntimeUiMessages.WHAT_FLAG_SOLID); + addFlag(flags, platform.isFluid(), RuntimeUiMessages.WHAT_FLAG_FLUID); + addFlag(flags, platform.isWater(), RuntimeUiMessages.WHAT_FLAG_WATER); + addFlag(flags, platform.isWaterLogged(), RuntimeUiMessages.WHAT_FLAG_WATERLOGGED); + addFlag(flags, platform.isStorage(), RuntimeUiMessages.WHAT_FLAG_STORAGE); + addFlag(flags, platform.isLit(), RuntimeUiMessages.WHAT_FLAG_LIT); + addFlag(flags, platform.isFoliage(), RuntimeUiMessages.WHAT_FLAG_FOLIAGE); + addFlag(flags, platform.isFoliagePlantable(), RuntimeUiMessages.WHAT_FLAG_PLANTABLE_FOLIAGE); + addFlag(flags, platform.isDecorant(), RuntimeUiMessages.WHAT_FLAG_DECORANT); + addFlag(flags, platform.isOre(), RuntimeUiMessages.WHAT_FLAG_ORE); + addFlag(flags, platform.hasTileEntity(), RuntimeUiMessages.WHAT_FLAG_BLOCK_ENTITY); + return List.copyOf(flags); + } + + private static void addFlag(List flags, boolean enabled, + TextKey message) { + if (enabled) { + flags.add(IrisLanguage.plain(message)); + } + } + + private static void sendObject(CommandSourceStack source, ServerLevel level, BlockPos pos) { + Engine engine = IrisModdedCommands.engineFor(level); + if (engine == null) { + return; + } + try { + String object = engine.getObjectPlacementKey( + pos.getX(), pos.getY() - engine.getMinHeight(), pos.getZ()); + if (object != null) { + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WHAT_OBJECT, + MessageArgument.untrusted("object", object))); + } + } catch (Throwable error) { + LOGGER.error("Iris object lookup failed for /iris what block at {}, {}, {}", + pos.getX(), pos.getY(), pos.getZ(), error); + } + } + + private static void sendBlockEntity(CommandSourceStack source, + ServerLevel level, BlockPos pos) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity == null) { + return; + } + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WHAT_BLOCK_ENTITY, + MessageArgument.untrusted( + "type", BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(blockEntity.getType())))); + if (blockEntity instanceof RandomizableContainer container) { + ResourceKey lootTable = container.getLootTable(); + if (lootTable != null) { + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WHAT_LOOT_TABLE, + MessageArgument.untrusted("loot", lootTable.identifier()))); + } + } + if (blockEntity instanceof SpawnerBlockEntity) { + CompoundTag tag = blockEntity.saveWithoutMetadata(level.registryAccess()); + CompoundTag spawnData = tag.getCompound("SpawnData").orElse(null); + CompoundTag entity = spawnData == null ? null : spawnData.getCompound("entity").orElse(null); + String entityId = entity == null ? "" : entity.getStringOr("id", ""); + if (!entityId.isBlank()) { + IrisModdedCommands.ok(source, IrisLanguage.plain( + RuntimeUiMessages.WHAT_SPAWNER_ENTITY, + MessageArgument.untrusted("entity", entityId))); + } + } + } + + private static int inspectMarkers(CommandSourceStack source, String markerRaw) { + ServerPlayer player = source.getPlayer(); + if (player == null) { + return playerRequired(source, ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_MARKERS_RENDER); + } + ServerLevel level = source.getLevel(); + Engine engine = IrisModdedCommands.engineFor(level); + if (engine == null) { + IrisModdedCommands.fail(source, IrisLanguage.plain( + ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_7)); + return 0; + } + ModdedScheduler scheduler = ModdedEngineBootstrap.schedulerOrNull(); + if (scheduler == null) { + IrisModdedCommands.fail(source, IrisLanguage.plain( + ModdedCommandMessages.IRIS_MODDED_COMMANDS_MARKER_SCAN_FAILED, + MessageArgument.untrusted("value", "scheduler unavailable"))); + return 0; + } + String marker = markerRaw.trim(); + BlockPos origin = player.blockPosition(); + MarkerRun run = new MarkerRun( + player.getUUID(), player, level, engine, marker, + origin.getX() >> 4, origin.getZ() >> 4, new AtomicBoolean()); + MarkerRun previous = ACTIVE_MARKER_RUNS.put(player.getUUID(), run); + if (previous != null) { + previous.cancelled().set(true); + } + IrisModdedCommands.ok(source, IrisLanguage.plain( + ModdedCommandMessages.IRIS_MODDED_COMMANDS_SCANNING_MARKERS_AROUND_YOU, + MessageArgument.untrusted("marker", marker))); + scheduler.async(() -> scanMarkers(source, scheduler, run)); + return 1; + } + + private static void scanMarkers(CommandSourceStack source, + ModdedScheduler scheduler, MarkerRun run) { + List hits = new ArrayList<>(); + MatterMarker marker = new MatterMarker(run.marker()); + try (GenerationSessionLease lease = run.engine().acquireGenerationLease("modded_what_markers"); + IrisContext.Scope ignored = IrisContext.open(run.engine(), lease.sessionId(), null)) { + for (int chunkX = run.chunkX() - 4; chunkX <= run.chunkX() + 4; chunkX++) { + for (int chunkZ = run.chunkZ() - 4; chunkZ <= run.chunkZ() + 4; chunkZ++) { + if (run.cancelled().get()) { + return; + } + for (IrisPosition position : run.engine().getMantle().findMarkers(chunkX, chunkZ, marker)) { + hits.add(new BlockPos(position.getX(), position.getY(), position.getZ())); + if (hits.size() >= MAX_MARKERS) { + break; + } + } + if (hits.size() >= MAX_MARKERS) { + break; + } + } + if (hits.size() >= MAX_MARKERS) { + break; + } + } + scheduler.global(() -> renderMarkerBatch(source, scheduler, run, hits, 0)); + } catch (GenerationSessionException error) { + markerFailure(source, scheduler, run, error); + } catch (Throwable error) { + markerFailure(source, scheduler, run, error); + } + } + + private static void renderMarkerBatch(CommandSourceStack source, + ModdedScheduler scheduler, MarkerRun run, + List hits, int from) { + if (!active(run)) { + return; + } + int to = Math.min(hits.size(), from + MARKER_BATCH_SIZE); + for (int index = from; index < to; index++) { + BlockPos hit = hits.get(index); + run.level().sendParticles(run.player(), MARKER_DUST, true, true, + hit.getX() + 0.5D, hit.getY() + 1.0D, hit.getZ() + 0.5D, + 3, 0.2D, 0.2D, 0.2D, 0.0D); + } + if (to < hits.size()) { + scheduler.laterGlobal(() -> renderMarkerBatch(source, scheduler, run, hits, to), 1); + return; + } + ACTIVE_MARKER_RUNS.remove(run.playerId(), run); + IrisModdedCommands.ok(source, IrisLanguage.plain( + ModdedCommandMessages.IRIS_MODDED_COMMANDS_FOUND_NEARBY_MARKER_S, + MessageArgument.trusted("value", hits.size()), + MessageArgument.untrusted("marker", run.marker()))); + } + + private static boolean active(MarkerRun run) { + return !run.cancelled().get() + && ACTIVE_MARKER_RUNS.get(run.playerId()) == run + && !run.player().hasDisconnected() + && !run.player().isRemoved() + && run.player().level() == run.level() + && !run.engine().isClosing() + && !run.engine().isClosed(); + } + + private static void markerFailure(CommandSourceStack source, + ModdedScheduler scheduler, MarkerRun run, Throwable error) { + LOGGER.error("Iris marker scan failed for {}", run.marker(), error); + scheduler.global(() -> { + if (ACTIVE_MARKER_RUNS.remove(run.playerId(), run)) { + IrisModdedCommands.fail(source, IrisLanguage.plain( + ModdedCommandMessages.IRIS_MODDED_COMMANDS_MARKER_SCAN_FAILED, + MessageArgument.untrusted("value", error.getClass().getSimpleName()))); + } + }); + } + + private static NativeBiome nativeBiome(ServerLevel level, BlockPos pos) { + Holder holder = level.getBiome(pos); + String key = holder.unwrapKey() + .map((ResourceKey resourceKey) -> resourceKey.identifier().toString()) + .orElse(IrisLanguage.plain(RuntimeUiMessages.STATUS_UNREGISTERED)); + Registry registry = level.registryAccess().lookupOrThrow(Registries.BIOME); + return new NativeBiome(key, registry.getId(holder.value())); + } + + private static int playerRequired(CommandSourceStack source, + TextKey message) { + IrisModdedCommands.fail(source, IrisLanguage.plain(message)); + return 0; + } + + private static void logLookupFailure(CommandSourceStack source, + String operation, Throwable error, + TextKey message) { + LOGGER.error("Iris /what {} lookup failed in {}", operation, + source.getLevel().dimension().identifier(), error); + IrisModdedCommands.fail(source, IrisLanguage.plain( + message, + MessageArgument.untrusted("value", error.getClass().getSimpleName()))); + } + + private record NativeBiome(String key, int id) { + } + + private record MarkerRun( + UUID playerId, + ServerPlayer player, + ServerLevel level, + Engine engine, + String marker, + int chunkX, + int chunkZ, + AtomicBoolean cancelled + ) { + } +} diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java index 76bb74977..4530475cc 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java @@ -175,7 +175,7 @@ public final class ModdedWorldCommands { } IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_PACK_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("pack2", pack))); Thread thread = new Thread(() -> { - boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), pack, "master", + boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), pack, "master", false, (String line) -> server.execute(() -> IrisModdedCommands.ok(source, line))); server.execute(() -> { if (!installed || !packFolder.isDirectory()) { @@ -278,7 +278,7 @@ public final class ModdedWorldCommands { } IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_PACK_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS_2, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("pack2", pack))); Thread thread = new Thread(() -> { - boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), pack, "master", + boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), pack, "master", false, (String line) -> server.execute(() -> IrisModdedCommands.ok(source, line))); server.execute(() -> { if (!installed || !packFolder.isDirectory()) { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedEngineMaintenanceService.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedEngineMaintenanceService.java index adab07301..af2ab2e4e 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedEngineMaintenanceService.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedEngineMaintenanceService.java @@ -56,8 +56,12 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi @Override public void onEnable() { ExecutorService current = service; - if (current != null && !current.isShutdown()) { - return; + if (current != null) { + if (!current.isTerminated()) { + throw new IllegalStateException( + "Iris engine maintenance cannot restart while prior workers are still active"); + } + service = null; } IrisSettings.IrisSettingsEngineSVC settings = IrisSettings.get().getPerformance().getEngineSVC(); @@ -75,9 +79,9 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi @Override public void onDisable() { ExecutorService active = service; - service = null; boolean drained = shutdownAndDrain(active); if (drained) { + service = null; inFlight.clear(); } lastSavedAt.clear(); diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/de_de.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/de_de.json index 5d7ad9294..a0d6d7ddf 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/de_de.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/de_de.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "Vorgenerierungs-HUD umschalten" + "key.irisworldgen.toggle_pregen_hud": "Vorgenerierungs-HUD umschalten", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/en_us.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/en_us.json index f5898c8f4..59a31d305 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/en_us.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/en_us.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "Toggle Pregen HUD" + "key.irisworldgen.toggle_pregen_hud": "Toggle Pregen HUD", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/es_es.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/es_es.json index d7f5e8154..c436c7fd4 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/es_es.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/es_es.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "Alternar HUD de pregeneración" + "key.irisworldgen.toggle_pregen_hud": "Alternar HUD de pregeneración", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/fi_fi.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/fi_fi.json index 709f4bbc4..b468c26e9 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/fi_fi.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/fi_fi.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "Vaihda esigeneroinnin HUD" + "key.irisworldgen.toggle_pregen_hud": "Vaihda esigeneroinnin HUD", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/fr_fr.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/fr_fr.json index b8e0a7d64..ba54dc964 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/fr_fr.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/fr_fr.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "Activer ou désactiver le HUD de prégénération" + "key.irisworldgen.toggle_pregen_hud": "Activer ou désactiver le HUD de prégénération", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/he_il.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/he_il.json index 65fc2c31e..542f08a02 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/he_il.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/he_il.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "הפעלה או כיבוי של תצוגת הקדם-יצירה" + "key.irisworldgen.toggle_pregen_hud": "הפעלה או כיבוי של תצוגת הקדם-יצירה", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/it_it.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/it_it.json index 9c6561666..7c5d4caa9 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/it_it.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/it_it.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "Attiva o disattiva l'HUD di pregenerazione" + "key.irisworldgen.toggle_pregen_hud": "Attiva o disattiva l'HUD di pregenerazione", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/ja_jp.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/ja_jp.json index 4a358a209..8efdde88c 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/ja_jp.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/ja_jp.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "事前生成 HUD の切り替え" + "key.irisworldgen.toggle_pregen_hud": "事前生成 HUD の切り替え", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/ko_kr.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/ko_kr.json index 928e4a804..ae734e26b 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/ko_kr.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/ko_kr.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "사전 생성 HUD 전환" + "key.irisworldgen.toggle_pregen_hud": "사전 생성 HUD 전환", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/lt_lt.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/lt_lt.json index cbb18aee1..741346668 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/lt_lt.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/lt_lt.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "Perjungti išankstinio generavimo HUD" + "key.irisworldgen.toggle_pregen_hud": "Perjungti išankstinio generavimo HUD", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/nl_nl.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/nl_nl.json index 3b41afc5b..f5ff6b361 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/nl_nl.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/nl_nl.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "HUD voor vooraf genereren in- of uitschakelen" + "key.irisworldgen.toggle_pregen_hud": "HUD voor vooraf genereren in- of uitschakelen", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/pl_pl.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/pl_pl.json index 6859943b9..04cc94964 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/pl_pl.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/pl_pl.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "Przełącz interfejs wstępnego generowania" + "key.irisworldgen.toggle_pregen_hud": "Przełącz interfejs wstępnego generowania", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/pt_pt.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/pt_pt.json index b6d993275..1ee6ca276 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/pt_pt.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/pt_pt.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "Alternar HUD de pré-geração" + "key.irisworldgen.toggle_pregen_hud": "Alternar HUD de pré-geração", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/ru_ru.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/ru_ru.json index db25e5ee3..e5963a096 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/ru_ru.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/ru_ru.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "Переключить HUD предварительной генерации" + "key.irisworldgen.toggle_pregen_hud": "Переключить HUD предварительной генерации", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/tr_tr.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/tr_tr.json index e6357dcfd..bcb895cf3 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/tr_tr.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/tr_tr.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "Ön oluşturma HUD'unu aç veya kapat" + "key.irisworldgen.toggle_pregen_hud": "Ön oluşturma HUD'unu aç veya kapat", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/vi_vi.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/vi_vi.json index 632ffca94..919a5f8d9 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/vi_vi.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/vi_vi.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "Bật hoặc tắt HUD tạo trước" + "key.irisworldgen.toggle_pregen_hud": "Bật hoặc tắt HUD tạo trước", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/zh_cn.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/zh_cn.json index 0a7c01f68..b0a7a16c3 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/zh_cn.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/zh_cn.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "切换预生成 HUD" + "key.irisworldgen.toggle_pregen_hud": "切换预生成 HUD", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/zh_tw.json b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/zh_tw.json index d37bd5583..08194dbff 100644 --- a/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/zh_tw.json +++ b/adapters/modded-common/src/main/resources/assets/irisworldgen/lang/zh_tw.json @@ -1,4 +1,6 @@ { "key.categories.irisworldgen.iris": "Iris", - "key.irisworldgen.toggle_pregen_hud": "切換預先生成 HUD" + "key.irisworldgen.toggle_pregen_hud": "切換預先生成 HUD", + "key.irisworldgen.open_vision_map": "Open Iris Vision Map", + "key.irisworldgen.toggle_what_overlay": "Toggle Iris What Overlay" } diff --git a/adapters/modded-common/src/main/resources/irisworldgen.client.mixins.json b/adapters/modded-common/src/main/resources/irisworldgen.client.mixins.json new file mode 100644 index 000000000..c75511212 --- /dev/null +++ b/adapters/modded-common/src/main/resources/irisworldgen.client.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "art.arcane.iris.client.mixin", + "compatibilityLevel": "JAVA_25", + "client": [ + "IrisWorldOpenFlowsMixin", + "IrisWorldTypeEntryMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/client/IrisClientCursorTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/client/IrisClientCursorTest.java new file mode 100644 index 000000000..780d08012 --- /dev/null +++ b/adapters/modded-common/src/test/java/art/arcane/iris/client/IrisClientCursorTest.java @@ -0,0 +1,53 @@ +package art.arcane.iris.client; + +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import static org.junit.Assert.assertEquals; + +public class IrisClientCursorTest { + @Test + public void refreshesAnUnchangedPositionAfterTheRefreshInterval() { + AtomicLong clock = new AtomicLong(500L); + AtomicInteger frames = new AtomicInteger(); + IrisClientCursor cursor = new IrisClientCursor(frame -> frames.incrementAndGet(), clock::get); + + cursor.requestFor(10, 20); + clock.addAndGet(1_999L); + cursor.requestFor(10, 20); + clock.incrementAndGet(); + cursor.requestFor(10, 20); + + assertEquals(2, frames.get()); + } + + @Test + public void throttlesRapidPositionChanges() { + AtomicLong clock = new AtomicLong(500L); + AtomicInteger frames = new AtomicInteger(); + IrisClientCursor cursor = new IrisClientCursor(frame -> frames.incrementAndGet(), clock::get); + + cursor.requestFor(10, 20); + clock.addAndGet(499L); + cursor.requestFor(11, 20); + clock.incrementAndGet(); + cursor.requestFor(11, 20); + + assertEquals(2, frames.get()); + } + + @Test + public void clearAllowsImmediateRequestAtTheSamePosition() { + AtomicLong clock = new AtomicLong(500L); + AtomicInteger frames = new AtomicInteger(); + IrisClientCursor cursor = new IrisClientCursor(frame -> frames.incrementAndGet(), clock::get); + + cursor.requestFor(10, 20); + cursor.clear(); + cursor.requestFor(10, 20); + + assertEquals(2, frames.get()); + } +} diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/client/IrisClientDimensionTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/client/IrisClientDimensionTest.java new file mode 100644 index 000000000..bb51a28a6 --- /dev/null +++ b/adapters/modded-common/src/test/java/art/arcane/iris/client/IrisClientDimensionTest.java @@ -0,0 +1,25 @@ +package art.arcane.iris.client; + +import art.arcane.iris.spi.protocol.IrisMessage; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class IrisClientDimensionTest { + @Test + public void packSeedAndHeightChangesInvalidateDimensionCaches() { + IrisClientDimension dimension = new IrisClientDimension(); + IrisMessage.DimensionStatus initial = new IrisMessage.DimensionStatus( + "minecraft:overworld", "overworld", 1L, -64, 320, true); + + assertTrue(dimension.onDimensionStatus(initial)); + assertFalse(dimension.onDimensionStatus(initial)); + assertTrue(dimension.onDimensionStatus(new IrisMessage.DimensionStatus( + "minecraft:overworld", "other", 1L, -64, 320, true))); + assertTrue(dimension.onDimensionStatus(new IrisMessage.DimensionStatus( + "minecraft:overworld", "other", 2L, -64, 320, true))); + assertTrue(dimension.onDimensionStatus(new IrisMessage.DimensionStatus( + "minecraft:overworld", "other", 2L, 0, 256, true))); + } +} diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/client/IrisClientSessionTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/client/IrisClientSessionTest.java new file mode 100644 index 000000000..ef6f9f8bb --- /dev/null +++ b/adapters/modded-common/src/test/java/art/arcane/iris/client/IrisClientSessionTest.java @@ -0,0 +1,57 @@ +package art.arcane.iris.client; + +import art.arcane.iris.spi.protocol.IrisMessage; +import art.arcane.iris.spi.protocol.IrisProtocol; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class IrisClientSessionTest { + @Test + public void retriesHelloAndEventuallyMarksUnsupported() { + AtomicLong clock = new AtomicLong(); + AtomicInteger frames = new AtomicInteger(); + IrisClientSession session = new IrisClientSession(clock::get); + session.bind(frame -> frames.incrementAndGet()); + + session.sendHello(); + assertEquals(1, frames.get()); + assertEquals(IrisClientSession.State.AWAITING_HELLO, session.state()); + + for (int attempt = 0; attempt < 5; attempt++) { + clock.addAndGet(2_000L); + session.tick(); + } + + assertEquals(5, frames.get()); + assertEquals(IrisClientSession.State.UNSUPPORTED, session.state()); + } + + @Test + public void matchingHelloCompletesRetryingSession() { + AtomicLong clock = new AtomicLong(); + IrisClientSession session = new IrisClientSession(clock::get); + session.bind(frame -> { + }); + session.sendHello(); + + session.onServerHello(new IrisMessage.ServerHello( + IrisProtocol.PROTOCOL_VERSION, IrisProtocol.CAPABILITY_VISION, "Iris", true)); + + assertTrue(session.isReady()); + assertEquals("Iris", session.serverBrand()); + } + + @Test + public void incompatibleHelloIsRejected() { + IrisClientSession session = new IrisClientSession(); + session.onServerHello(new IrisMessage.ServerHello( + IrisProtocol.PROTOCOL_VERSION + 1, 0L, "Iris", true)); + + assertEquals(IrisClientSession.State.INCOMPATIBLE, session.state()); + } +} diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/IrisModLanguageAssetsTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/IrisModLanguageAssetsTest.java index edbe82e03..c11c983cd 100644 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/IrisModLanguageAssetsTest.java +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/IrisModLanguageAssetsTest.java @@ -29,7 +29,7 @@ public class IrisModLanguageAssetsTest { @Test public void minecraftLanguageAssetsMatchSharedLocaleManifest() throws Exception { JsonObject english = read("en_us"); - assertEquals(2, english.size()); + assertEquals(4, english.size()); for (String locale : VolmitLocales.nonEnglish()) { String minecraftLocale = VolmitLocales.minecraftCode(locale); @@ -73,9 +73,16 @@ public class IrisModLanguageAssetsTest { private Set resourceFiles() throws Exception { URL resource = IrisModLanguageAssetsTest.class.getClassLoader().getResource(ROOT); - assertNotNull("Missing mod language resource directory", resource); - assertEquals("file", resource.getProtocol()); - try (Stream paths = Files.list(Path.of(resource.toURI()))) { + Path directory; + if (resource != null && "file".equals(resource.getProtocol())) { + directory = Path.of(resource.toURI()); + } else { + String sources = System.getProperty("iris.moddedCommonSources"); + assertNotNull("Missing mod language resource directory and source root", sources); + directory = Path.of(sources).getParent() + .resolve("resources").resolve(ROOT); + } + try (Stream paths = Files.list(directory)) { return paths .filter(Files::isRegularFile) .map(path -> path.getFileName().toString()) diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedDimensionRegistryStoreTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedDimensionRegistryStoreTest.java new file mode 100644 index 000000000..43162f626 --- /dev/null +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedDimensionRegistryStoreTest.java @@ -0,0 +1,87 @@ +package art.arcane.iris.modded; + +import org.junit.Test; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +public class ModdedDimensionRegistryStoreTest { + @Test + public void registryRoundTripsPersistentDimensions() throws IOException { + Path root = Files.createTempDirectory("iris-dimension-registry"); + Path file = root.resolve("iris-dimensions.json"); + try { + List expected = List.of( + new ModdedDimensionRegistryStore.PersistentDimension( + "iris:first", "overworld", "overworld", 42L), + new ModdedDimensionRegistryStore.PersistentDimension( + "iris:second", "other", "surface", -9L)); + + ModdedDimensionRegistryStore.write(file, expected); + + assertEquals(expected, ModdedDimensionRegistryStore.load(file)); + } finally { + Files.deleteIfExists(file); + Files.deleteIfExists(root); + } + } + + @Test + public void malformedEntryDoesNotDiscardHealthyEntries() throws IOException { + Path root = Files.createTempDirectory("iris-dimension-registry-partial"); + Path file = root.resolve("iris-dimensions.json"); + try { + Files.writeString(file, """ + { + "dimensions": [ + {"id":"iris:good","pack":"overworld","dimension":"overworld","seed":7}, + {"id":"iris:broken","pack":"overworld"} + ] + } + """, StandardCharsets.UTF_8); + + assertEquals(List.of(new ModdedDimensionRegistryStore.PersistentDimension( + "iris:good", "overworld", "overworld", 7L)), + ModdedDimensionRegistryStore.load(file)); + } finally { + Files.deleteIfExists(file); + Files.deleteIfExists(root); + } + } + + @Test + public void truncatedRegistryNeverBecomesAnEmptySuccessfulLoad() throws IOException { + Path root = Files.createTempDirectory("iris-dimension-registry-truncated"); + Path file = root.resolve("iris-dimensions.json"); + try { + Files.writeString(file, "{\"dimensions\":[", StandardCharsets.UTF_8); + + assertThrows(IllegalStateException.class, + () -> ModdedDimensionRegistryStore.load(file)); + } finally { + Files.deleteIfExists(file); + Files.deleteIfExists(root); + } + } + + @Test + public void missingDimensionsArrayNeverBecomesAnEmptySuccessfulLoad() throws IOException { + Path root = Files.createTempDirectory("iris-dimension-registry-missing-root"); + Path file = root.resolve("iris-dimensions.json"); + try { + Files.writeString(file, "{}", StandardCharsets.UTF_8); + + assertThrows(IllegalStateException.class, + () -> ModdedDimensionRegistryStore.load(file)); + } finally { + Files.deleteIfExists(file); + Files.deleteIfExists(root); + } + } +} diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedDimensionTypeParityTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedDimensionTypeParityTest.java index 72bf68f54..68662d2a9 100644 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedDimensionTypeParityTest.java +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedDimensionTypeParityTest.java @@ -44,6 +44,7 @@ import static art.arcane.iris.engine.object.IrisDimensionTypeOptions.TriState.FA import static art.arcane.iris.engine.object.IrisDimensionTypeOptions.TriState.TRUE; 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.junit.Assert.fail; @@ -54,9 +55,14 @@ public class ModdedDimensionTypeParityTest { IrisDimension nether = dimension("nether", IrisEnvironment.NETHER, 0, 256, 256, new IrisDimensionTypeOptions()); IrisDimension end = dimension("the_end", IrisEnvironment.THE_END, 0, 256, 256, new IrisDimensionTypeOptions()); - assertEquals("irisworldgen:overworld", ModdedForcedDatapack.dimensionTypeRef(overworld)); - assertEquals("irisworldgen:nether", ModdedForcedDatapack.dimensionTypeRef(nether)); - assertEquals("irisworldgen:the_end", ModdedForcedDatapack.dimensionTypeRef(end)); + assertEquals("irisworldgen:packs/6f766572776f726c64/dimensions/6f766572776f726c64/dimension_type", + ModdedWorldgenIds.dimensionTypeRef("overworld", overworld.getLoadKey())); + assertEquals("irisworldgen:packs/6e6574686572/dimensions/6e6574686572/dimension_type", + ModdedWorldgenIds.dimensionTypeRef("nether", nether.getLoadKey())); + assertEquals("irisworldgen:packs/7468655f656e64/dimensions/7468655f656e64/dimension_type", + ModdedWorldgenIds.dimensionTypeRef("the_end", end.getLoadKey())); + assertNotEquals(ModdedWorldgenIds.dimensionTypeRef("first", "overworld"), + ModdedWorldgenIds.dimensionTypeRef("second", "overworld")); } @Test @@ -77,18 +83,18 @@ public class ModdedDimensionTypeParityTest { roots.add(packDirectory.toFile()); try { for (IrisDimension dimension : dimensions) { - ModdedForcedDatapack.writeDimensionType(roots, fixer, dimension); - Path output = packDirectory.resolve("data/irisworldgen/dimension_type/" - + dimension.getDimensionTypeKey() + ".json"); + ModdedForcedDatapack.writeDimensionType( + roots, fixer, dimension, "contracts", dimension.getLoadKey()); + Path output = typeFile(packDirectory, "contracts", dimension); assertTrue(Files.isRegularFile(output)); assertEquals(dimension.getDimensionType().toJson(fixer), Files.readString(output, StandardCharsets.UTF_8)); } - JSONObject overworldJson = readType(packDirectory, overworld); - JSONObject netherJson = readType(packDirectory, nether); - JSONObject endJson = readType(packDirectory, end); - JSONObject customJson = readType(packDirectory, custom); + JSONObject overworldJson = readType(packDirectory, "contracts", overworld); + JSONObject netherJson = readType(packDirectory, "contracts", nether); + JSONObject endJson = readType(packDirectory, "contracts", end); + JSONObject customJson = readType(packDirectory, "contracts", custom); assertTrue(overworldJson.getBoolean("has_skylight")); assertFalse(overworldJson.getBoolean("has_ceiling")); @@ -166,10 +172,16 @@ public class ModdedDimensionTypeParityTest { return dimension; } - private static JSONObject readType(Path packDirectory, IrisDimension dimension) throws IOException { - Path output = packDirectory.resolve("data/irisworldgen/dimension_type/" - + dimension.getDimensionTypeKey() + ".json"); - return new JSONObject(Files.readString(output, StandardCharsets.UTF_8)); + private static JSONObject readType(Path packDirectory, String pack, + IrisDimension dimension) throws IOException { + return new JSONObject(Files.readString( + typeFile(packDirectory, pack, dimension), StandardCharsets.UTF_8)); + } + + private static Path typeFile(Path packDirectory, String pack, IrisDimension dimension) { + String typeRef = ModdedWorldgenIds.dimensionTypeRef(pack, dimension.getLoadKey()); + return packDirectory.resolve("data/irisworldgen/dimension_type/") + .resolve(typeRef.substring(typeRef.indexOf(':') + 1) + ".json"); } private static void deleteTree(Path root) throws IOException { diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedForcedDatapackTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedForcedDatapackTest.java index 5938ea510..6ac12df55 100644 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedForcedDatapackTest.java +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedForcedDatapackTest.java @@ -39,6 +39,21 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; public class ModdedForcedDatapackTest { + @Test + public void packScopedIdsCannotCollideAndHaveReadableLabels() { + String first = ModdedWorldgenIds.presetRef("overworld", "overworld"); + String second = ModdedWorldgenIds.presetRef("other", "overworld"); + + assertFalse(first.equals(second)); + assertEquals("IRIS:Overworld", + ModdedWorldgenIds.displayName(first.substring(first.indexOf(':') + 1))); + assertEquals("IRIS:Other / Overworld", + ModdedWorldgenIds.displayName(second.substring(second.indexOf(':') + 1))); + assertEquals("iris:overworld", ModdedWorldgenIds.generatorIdentity("overworld")); + assertEquals("iris:other/overworld", + ModdedWorldgenIds.generatorIdentity("other:overworld")); + } + @Test public void scopesSharedCustomBiomeIdsByNamespace() { Map> seenBiomes = new LinkedHashMap<>(); diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedLifecycleFailureContractTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedLifecycleFailureContractTest.java index 381820af1..04a805d55 100644 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedLifecycleFailureContractTest.java +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedLifecycleFailureContractTest.java @@ -60,16 +60,15 @@ public class ModdedLifecycleFailureContractTest { } @Test - public void persistentReinjectionRethrowsTheOriginalCause() throws IOException { + public void persistentReinjectionQuarantinesBrokenEntriesAndContinues() throws IOException { String source = source("ModdedStartup.java"); String reinjection = method(source, "private static void reinjectPersistentDimensions("); String failure = catchBlock(reinjection); assertTrue(failure.contains("LOGGER.error(")); assertFalse(failure.contains("e.toString()")); - assertFalse(failure.contains("continue;")); - assertTrue(failure.contains("throw new IllegalStateException(")); - assertTrue(failure.contains(", e);")); + assertFalse(failure.contains("throw new IllegalStateException(")); + assertTrue(reinjection.contains("injected++;")); } @Test diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/IrisModdedCommandParityTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/IrisModdedCommandParityTest.java new file mode 100644 index 000000000..e67ec2661 --- /dev/null +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/IrisModdedCommandParityTest.java @@ -0,0 +1,84 @@ +package art.arcane.iris.modded.command; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.tree.CommandNode; +import net.minecraft.SharedConstants; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.server.Bootstrap; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +public class IrisModdedCommandParityTest { + @Test + public void registersPluginParityWhatCommandsAndAliases() { + SharedConstants.tryDetectVersion(); + Bootstrap.bootStrap(); + CommandDispatcher dispatcher = new CommandDispatcher<>(); + + IrisModdedCommands.register(dispatcher); + + CommandNode iris = child(dispatcher.getRoot(), "iris"); + CommandNode what = child(iris, "what"); + child(what, "here"); + child(what, "biome"); + child(what, "region"); + child(what, "block"); + child(what, "hand"); + child(what, "markers"); + child(iris, "dust"); + child(iris, "d"); + child(iris, "create"); + child(iris, "c"); + child(iris, "teleport"); + child(iris, "tp"); + child(iris, "height"); + child(iris, "worlds"); + child(iris, "accesslist"); + + CommandNode edit = child(iris, "edit"); + child(edit, "b"); + child(edit, "r"); + child(edit, "d"); + CommandNode studio = child(iris, "studio"); + child(studio, "package"); + child(studio, "pkg"); + + CommandNode download = child(iris, "download"); + CommandNode pack = child(download, "pack"); + child(pack, "force"); + child(pack, "overwrite"); + CommandNode branch = child(pack, "branch"); + child(branch, "force"); + child(branch, "overwrite"); + + assertSame(iris, child(dispatcher.getRoot(), "ir").getRedirect()); + assertSame(iris, child(dispatcher.getRoot(), "irs").getRedirect()); + } + + @Test + public void helpDocumentsParityCommandsAndPlatformStubs() { + assertTrue(ModdedCommandHelp.documents("what", "here")); + assertTrue(ModdedCommandHelp.documents("what", "biome")); + assertTrue(ModdedCommandHelp.documents("what", "region")); + assertTrue(ModdedCommandHelp.documents("what", "block")); + assertTrue(ModdedCommandHelp.documents("what", "hand")); + assertTrue(ModdedCommandHelp.documents("what", "markers")); + assertTrue(ModdedCommandHelp.documents("", "dust")); + assertTrue(ModdedCommandHelp.documents("", "teleport")); + assertTrue(ModdedCommandHelp.documents("", "c")); + assertTrue(ModdedCommandHelp.documents("edit", "b")); + assertTrue(ModdedCommandHelp.documents("studio", "pkg")); + assertTrue(ModdedCommandHelp.documents("object", "we")); + assertTrue(ModdedCommandHelp.documents("world", "mainworld")); + } + + private static CommandNode child( + CommandNode parent, String name) { + CommandNode child = parent.getChild(name); + assertNotNull(name, child); + return child; + } +} diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/IrisModdedStructureCommandTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/IrisModdedStructureCommandTest.java index 2191da7d1..69e88eb25 100644 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/IrisModdedStructureCommandTest.java +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/IrisModdedStructureCommandTest.java @@ -25,7 +25,7 @@ public class IrisModdedStructureCommandTest { assertTrue(source.contains("combineStructureKeys(irisKeys, nativeKeys)")); assertTrue(source.contains("irisGenerator.isNativeStructureReachable(holder)")); assertTrue(source.contains("LocateStatus.SEARCH_LIMIT_REACHED")); - assertTrue(source.contains("the density search safety limit was reached")); + assertTrue(source.contains("IRIS_MODDED_COMMANDS_UNABLE_LOCATE_IRIS_PLACED_STRUCTURE_DENSITY_SEARCH_SAFETY_LIMIT_WAS")); assertTrue(source.contains("int targetX = result.originX()")); assertTrue(source.contains("int targetY = result.baseY() + 2")); assertTrue(source.contains("int targetZ = result.originZ()")); diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModdedDustRevealerTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModdedDustRevealerTest.java new file mode 100644 index 000000000..9ed81e8cc --- /dev/null +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModdedDustRevealerTest.java @@ -0,0 +1,60 @@ +package art.arcane.iris.modded.command; + +import net.minecraft.core.BlockPos; +import org.junit.Test; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class ModdedDustRevealerTest { + @Test + public void placementClassificationCoversObjectDecorationAndTerrain() { + assertTrue(ModdedDustRevealer.placementLine(3, 10, 13, "tree") + .contains("object/stilt 'tree'")); + assertTrue(ModdedDustRevealer.placementLine(3, 10, 13, null) + .contains("decoration/object/stilt")); + assertTrue(ModdedDustRevealer.placementLine(0, 10, 10, "ore") + .contains("buried object 'ore'")); + assertTrue(ModdedDustRevealer.placementLine(-4, 10, 6, null) + .contains("depth 4")); + } + + @Test + public void revealTraversalUsesDiagonalAdjacencyButNotDisconnectedBlocks() { + Set object = Set.of( + new BlockPos(0, 0, 0), + new BlockPos(1, 1, 1), + new BlockPos(3, 3, 3)); + List hits = ModdedDustRevealer.collect( + new BlockPos(0, 0, 0), + "object", + -64, + -64, + 320, + new AtomicBoolean(), + (int x, int relativeY, int z) -> + object.contains(new BlockPos(x, relativeY - 64, z)) + ? "object" + : null); + + assertEquals(List.of(new BlockPos(0, 0, 0), new BlockPos(1, 1, 1)), hits); + } + + @Test + public void cancelledRevealDoesNoTraversal() { + List hits = ModdedDustRevealer.collect( + new BlockPos(0, 0, 0), + "object", + -64, + -64, + 320, + new AtomicBoolean(true), + (int x, int relativeY, int z) -> "object"); + + assertTrue(hits.isEmpty()); + } +} diff --git a/adapters/neoforge/build.gradle b/adapters/neoforge/build.gradle index b4c089c4a..188ea6c0b 100644 --- a/adapters/neoforge/build.gradle +++ b/adapters/neoforge/build.gradle @@ -146,6 +146,10 @@ neoForge { accessTransformers.from('src/main/resources/META-INF/accesstransformer.cfg') runs { + client { + client() + jvmArgument('-Xmx8G') + } server { server() String parity = providers.gradleProperty('irisParity').getOrNull() diff --git a/adapters/neoforge/src/main/java/art/arcane/iris/neoforge/IrisNeoForgeClient.java b/adapters/neoforge/src/main/java/art/arcane/iris/neoforge/IrisNeoForgeClient.java index b95a2234f..720267419 100644 --- a/adapters/neoforge/src/main/java/art/arcane/iris/neoforge/IrisNeoForgeClient.java +++ b/adapters/neoforge/src/main/java/art/arcane/iris/neoforge/IrisNeoForgeClient.java @@ -52,6 +52,9 @@ public final class IrisNeoForgeClient { }); NeoForge.EVENT_BUS.addListener((ClientPlayerNetworkEvent.LoggingIn event) -> IrisClient.onWorldJoin()); NeoForge.EVENT_BUS.addListener((ClientPlayerNetworkEvent.LoggingOut event) -> IrisClient.onDisconnect()); - NeoForge.EVENT_BUS.addListener((ClientTickEvent.Post event) -> IrisClientKeybinds.pollToggle()); + NeoForge.EVENT_BUS.addListener((ClientTickEvent.Post event) -> { + IrisClient.tick(); + IrisClientKeybinds.pollToggle(); + }); } } diff --git a/adapters/neoforge/src/main/resources/META-INF/accesstransformer.cfg b/adapters/neoforge/src/main/resources/META-INF/accesstransformer.cfg index 42e421a53..5a2faeeb3 100644 --- a/adapters/neoforge/src/main/resources/META-INF/accesstransformer.cfg +++ b/adapters/neoforge/src/main/resources/META-INF/accesstransformer.cfg @@ -1,4 +1,3 @@ public net.minecraft.server.MinecraftServer levels public net.minecraft.server.MinecraftServer executor public net.minecraft.server.MinecraftServer storageSource -public net.minecraft.core.MappedRegistry frozen diff --git a/adapters/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/adapters/neoforge/src/main/resources/META-INF/neoforge.mods.toml index 61728f775..5404c7245 100644 --- a/adapters/neoforge/src/main/resources/META-INF/neoforge.mods.toml +++ b/adapters/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -5,6 +5,9 @@ license = "GPL-3.0" [[mixins]] config = "irisworldgen.entity.mixins.json" +[[mixins]] +config = "irisworldgen.client.mixins.json" + [[mods]] modId = "irisworldgen" version = "${version}" diff --git a/core/src/main/java/art/arcane/iris/core/localization/ModdedHelpMessages.java b/core/src/main/java/art/arcane/iris/core/localization/ModdedHelpMessages.java index 788993874..1530c208c 100644 --- a/core/src/main/java/art/arcane/iris/core/localization/ModdedHelpMessages.java +++ b/core/src/main/java/art/arcane/iris/core/localization/ModdedHelpMessages.java @@ -354,6 +354,70 @@ public final class ModdedHelpMessages { "iris.modded.help.entry.command.network", "List network interfaces and their addresses" ); + public static final TextKey COMMAND_WHAT_HERE_INSPECT_CURRENT_IRIS_CONTEXT = TextKey.of( + "iris.modded.help.entry.command.what.here", + "Inspect the Iris biome, region, cave biome, surface and chunk at your position" + ); + public static final TextKey COMMAND_WHAT_BIOME_INSPECT_CURRENT_BIOME = TextKey.of( + "iris.modded.help.entry.command.what.biome", + "Inspect the Iris biome, configured derivative and registered native biome" + ); + public static final TextKey COMMAND_WHAT_REGION_INSPECT_CURRENT_REGION = TextKey.of( + "iris.modded.help.entry.command.what.region", + "Inspect the Iris region at your current chunk" + ); + public static final TextKey COMMAND_WHAT_BLOCK_INSPECT_TARGET_BLOCK = TextKey.of( + "iris.modded.help.entry.command.what.block", + "Inspect the targeted block state, properties, object and block entity" + ); + public static final TextKey COMMAND_WHAT_HAND_INSPECT_HELD_ITEM = TextKey.of( + "iris.modded.help.entry.command.what.hand", + "Inspect the held item and its default block state" + ); + public static final TextKey COMMAND_WHAT_MARKERS_REVEAL_NEARBY_MARKERS = TextKey.of( + "iris.modded.help.entry.command.what.markers", + "Reveal nearby Iris Mantle markers with particles" + ); + public static final TextKey COMMAND_HEIGHT_PRINT_WORLD_HEIGHT = TextKey.of( + "iris.modded.help.entry.command.height", + "Print the current dimension height range" + ); + public static final TextKey COMMAND_WORLDS_LIST_WORLD_ACCESS = TextKey.of( + "iris.modded.help.entry.command.worlds", + "List loaded dimensions and identify the Iris-generated dimensions" + ); + public static final TextKey COMMAND_MAINWORLD_CONFIGURE_PRIMARY_WORLD_PRESET = TextKey.of( + "iris.modded.help.entry.command.mainworld", + "Configure or clear the Iris primary-world preset used on restart" + ); + public static final TextKey COMMAND_OBJECT_WE_BUKKIT_ONLY = TextKey.of( + "iris.modded.help.entry.command.object.we", + "Explain why WorldEdit selection import requires the Bukkit plugin" + ); + public static final TextKey COMMAND_OBJECT_STUDIO_BUKKIT_ONLY = TextKey.of( + "iris.modded.help.entry.command.object.studio", + "Explain why the object studio world requires the Bukkit toolchain" + ); + public static final TextKey COMMAND_OBJECT_CONVERT_BUKKIT_ONLY = TextKey.of( + "iris.modded.help.entry.command.object.convert", + "Explain why schematic conversion requires the Bukkit plugin" + ); + public static final TextKey COMMAND_STUDIO_LOOT_BUKKIT_ONLY = TextKey.of( + "iris.modded.help.entry.command.studio.loot", + "Explain why the Bukkit loot simulation GUI is unavailable" + ); + public static final TextKey COMMAND_STUDIO_PROFILE_BUKKIT_ONLY = TextKey.of( + "iris.modded.help.entry.command.studio.profile", + "Explain why Bukkit pack profiling is unavailable" + ); + public static final TextKey COMMAND_STUDIO_SPAWN_BUKKIT_ONLY = TextKey.of( + "iris.modded.help.entry.command.studio.spawn", + "Explain why Bukkit Iris entity spawning is unavailable" + ); + public static final TextKey COMMAND_STUDIO_OBJECTS_BUKKIT_ONLY = TextKey.of( + "iris.modded.help.entry.command.studio.objects", + "Explain why the Bukkit chunk object report is unavailable" + ); private static final List KEYS = List.of( COMMAND_VERSION_PRINT_VERSION_INFORMATION, @@ -442,7 +506,23 @@ public final class ModdedHelpMessages { COMMAND_CAPTURE_EXPLAIN_BUKKIT_STRUCTURE_CAPTURE_WORKFLOW, COMMAND_VERIFY_REPORT_NATIVE_AND_IRIS_STRUCTURE_REACHABILITY_IN_THE_CURRENT_DIMENSION, COMMAND_SENTRY_SEND_A_TEST_EXCEPTION_TO_THE_IRIS_ERROR_REPORTER, - COMMAND_NETWORK_LIST_NETWORK_INTERFACES_AND_THEIR_ADDRESSES + COMMAND_NETWORK_LIST_NETWORK_INTERFACES_AND_THEIR_ADDRESSES, + COMMAND_WHAT_HERE_INSPECT_CURRENT_IRIS_CONTEXT, + COMMAND_WHAT_BIOME_INSPECT_CURRENT_BIOME, + COMMAND_WHAT_REGION_INSPECT_CURRENT_REGION, + COMMAND_WHAT_BLOCK_INSPECT_TARGET_BLOCK, + COMMAND_WHAT_HAND_INSPECT_HELD_ITEM, + COMMAND_WHAT_MARKERS_REVEAL_NEARBY_MARKERS, + COMMAND_HEIGHT_PRINT_WORLD_HEIGHT, + COMMAND_WORLDS_LIST_WORLD_ACCESS, + COMMAND_MAINWORLD_CONFIGURE_PRIMARY_WORLD_PRESET, + COMMAND_OBJECT_WE_BUKKIT_ONLY, + COMMAND_OBJECT_STUDIO_BUKKIT_ONLY, + COMMAND_OBJECT_CONVERT_BUKKIT_ONLY, + COMMAND_STUDIO_LOOT_BUKKIT_ONLY, + COMMAND_STUDIO_PROFILE_BUKKIT_ONLY, + COMMAND_STUDIO_SPAWN_BUKKIT_ONLY, + COMMAND_STUDIO_OBJECTS_BUKKIT_ONLY ); private ModdedHelpMessages() { diff --git a/core/src/main/java/art/arcane/iris/core/localization/RuntimeUiMessages.java b/core/src/main/java/art/arcane/iris/core/localization/RuntimeUiMessages.java index a867f6e3b..6ce205da9 100644 --- a/core/src/main/java/art/arcane/iris/core/localization/RuntimeUiMessages.java +++ b/core/src/main/java/art/arcane/iris/core/localization/RuntimeUiMessages.java @@ -92,6 +92,33 @@ public final class RuntimeUiMessages { public static final TextKey DUST_OBJECTS_IN_CHUNK = TextKey.of("iris.runtime.dust.objects_in_chunk", "Objects in chunk: {objects}"); public static final TextKey DUST_COPY_BUTTON = TextKey.of("iris.runtime.dust.copy_button", "[Click to copy these stats]"); public static final TextKey DUST_COPY_HOVER = TextKey.of("iris.runtime.dust.copy_hover", "Copy block stats to clipboard"); + public static final TextKey DUST_REVEAL_FAILED = TextKey.of("iris.runtime.dust.reveal_failed", "Object reveal failed; see the console for details."); + public static final TextKey WHAT_MATERIAL = TextKey.of("iris.runtime.what.material", "Material: {material}"); + public static final TextKey WHAT_FULL_STATE = TextKey.of("iris.runtime.what.full_state", "Full: {state}"); + public static final TextKey WHAT_ITEM_COUNT = TextKey.of("iris.runtime.what.item_count", "Count: {count}"); + public static final TextKey WHAT_IRIS_BIOME = TextKey.of("iris.runtime.what.iris_biome", "Iris biome: {biome} ({name})"); + public static final TextKey WHAT_DERIVATIVE_BIOME = TextKey.of("iris.runtime.what.derivative_biome", "Derivative biome: {biome}"); + public static final TextKey WHAT_NATIVE_BIOME = TextKey.of("iris.runtime.what.native_biome", "Registered biome: {biome} (ID: {id})"); + public static final TextKey WHAT_NON_IRIS_BIOME = TextKey.of("iris.runtime.what.non_iris_biome", "Non-Iris biome: {biome} (ID: {id})"); + public static final TextKey WHAT_IRIS_REGION = TextKey.of("iris.runtime.what.iris_region", "Iris region: {region} ({name})"); + public static final TextKey WHAT_POSITION = TextKey.of("iris.runtime.what.position", "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})"); + public static final TextKey WHAT_OBJECT = TextKey.of("iris.runtime.what.object", "Iris object: {object}"); + public static final TextKey WHAT_BLOCK_ENTITY = TextKey.of("iris.runtime.what.block_entity", "Block entity: {type}"); + public static final TextKey WHAT_LOOT_TABLE = TextKey.of("iris.runtime.what.loot_table", "Loot table: {loot}"); + public static final TextKey WHAT_SPAWNER_ENTITY = TextKey.of("iris.runtime.what.spawner_entity", "Spawner entity: {entity}"); + public static final TextKey WHAT_FLAG_SOLID = TextKey.of("iris.runtime.what.flag.solid", "solid"); + public static final TextKey WHAT_FLAG_FLUID = TextKey.of("iris.runtime.what.flag.fluid", "fluid"); + public static final TextKey WHAT_FLAG_WATER = TextKey.of("iris.runtime.what.flag.water", "water"); + public static final TextKey WHAT_FLAG_WATERLOGGED = TextKey.of("iris.runtime.what.flag.waterlogged", "waterlogged"); + public static final TextKey WHAT_FLAG_STORAGE = TextKey.of("iris.runtime.what.flag.storage", "storage (loot capable)"); + public static final TextKey WHAT_FLAG_LIT = TextKey.of("iris.runtime.what.flag.lit", "lit"); + public static final TextKey WHAT_FLAG_FOLIAGE = TextKey.of("iris.runtime.what.flag.foliage", "foliage"); + public static final TextKey WHAT_FLAG_PLANTABLE_FOLIAGE = TextKey.of("iris.runtime.what.flag.plantable_foliage", "plantable foliage"); + public static final TextKey WHAT_FLAG_DECORANT = TextKey.of("iris.runtime.what.flag.decorant", "decorant"); + public static final TextKey WHAT_FLAG_ORE = TextKey.of("iris.runtime.what.flag.ore", "ore"); + public static final TextKey WHAT_FLAG_BLOCK_ENTITY = TextKey.of("iris.runtime.what.flag.block_entity", "block entity"); + public static final TextKey WORLD_HEIGHT_RANGE = TextKey.of("iris.runtime.world.height_range", "World height: {minY} to {maxY}"); + public static final TextKey WORLD_HEIGHT_TOTAL = TextKey.of("iris.runtime.world.height_total", "Total height: {height}"); public static final TextKey PREGEN_STARTING = TextKey.of("iris.runtime.pregen.starting", "Iris Pregen starting..."); public static final TextKey PREGEN_HEADER = TextKey.of("iris.runtime.pregen.header", "Iris Pregen"); public static final TextKey PREGEN_BOSSBAR_PAUSED = TextKey.of("iris.runtime.pregen.bossbar.paused", "Iris Pregen {generated}/{total} {percent}% PAUSED"); @@ -196,6 +223,33 @@ public final class RuntimeUiMessages { DUST_OBJECTS_IN_CHUNK, DUST_COPY_BUTTON, DUST_COPY_HOVER, + DUST_REVEAL_FAILED, + WHAT_MATERIAL, + WHAT_FULL_STATE, + WHAT_ITEM_COUNT, + WHAT_IRIS_BIOME, + WHAT_DERIVATIVE_BIOME, + WHAT_NATIVE_BIOME, + WHAT_NON_IRIS_BIOME, + WHAT_IRIS_REGION, + WHAT_POSITION, + WHAT_OBJECT, + WHAT_BLOCK_ENTITY, + WHAT_LOOT_TABLE, + WHAT_SPAWNER_ENTITY, + WHAT_FLAG_SOLID, + WHAT_FLAG_FLUID, + WHAT_FLAG_WATER, + WHAT_FLAG_WATERLOGGED, + WHAT_FLAG_STORAGE, + WHAT_FLAG_LIT, + WHAT_FLAG_FOLIAGE, + WHAT_FLAG_PLANTABLE_FOLIAGE, + WHAT_FLAG_DECORANT, + WHAT_FLAG_ORE, + WHAT_FLAG_BLOCK_ENTITY, + WORLD_HEIGHT_RANGE, + WORLD_HEIGHT_TOTAL, PREGEN_STARTING, PREGEN_HEADER, PREGEN_BOSSBAR_PAUSED, diff --git a/core/src/main/java/art/arcane/iris/core/protocol/IrisCursorResolver.java b/core/src/main/java/art/arcane/iris/core/protocol/IrisCursorResolver.java index 6ecd109d2..57e26b722 100644 --- a/core/src/main/java/art/arcane/iris/core/protocol/IrisCursorResolver.java +++ b/core/src/main/java/art/arcane/iris/core/protocol/IrisCursorResolver.java @@ -31,7 +31,7 @@ public final class IrisCursorResolver { IrisBiome surfaceBiome = engine.getSurfaceBiome(blockX, blockZ); IrisRegion region = engine.getRegion(blockX, blockZ); IrisBiome caveBiome = engine.getCaveBiome(blockX, blockZ); - int height = engine.getHeight(blockX, blockZ); + int height = engine.getMinHeight() + engine.getHeight(blockX, blockZ); String biomeKey = keyOf(surfaceBiome == null ? null : surfaceBiome.getLoadKey()); String regionKey = keyOf(region == null ? null : region.getLoadKey()); String caveBiomeKey = keyOf(caveBiome == null ? null : caveBiome.getLoadKey()); diff --git a/core/src/main/java/art/arcane/iris/core/protocol/IrisProtocolServer.java b/core/src/main/java/art/arcane/iris/core/protocol/IrisProtocolServer.java index 77202cfcd..8e540add7 100644 --- a/core/src/main/java/art/arcane/iris/core/protocol/IrisProtocolServer.java +++ b/core/src/main/java/art/arcane/iris/core/protocol/IrisProtocolServer.java @@ -284,11 +284,11 @@ public final class IrisProtocolServer { } private void dispatch(IrisSession session, IrisMessage message) { + if (message instanceof IrisMessage.ClientHello clientHello) { + onClientHello(session, clientHello); + return; + } if (session.state() == IrisSession.State.AWAITING_HELLO) { - if (message instanceof IrisMessage.ClientHello clientHello) { - onClientHello(session, clientHello); - return; - } droppedBeforeHello.incrementAndGet(); return; } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java index f5265f0ec..979b68358 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java @@ -524,7 +524,11 @@ public class IrisDimension extends IrisRegistrant { public void installBiomes(IDataFixer fixer, DataProvider data, KList datapackRoots, KSet biomes) throws IOException { String namespace = getLoadKey().toLowerCase(Locale.ROOT); + installBiomes(fixer, data, datapackRoots, namespace, "", biomes); + } + public void installBiomes(IDataFixer fixer, DataProvider data, KList datapackRoots, + String namespace, String pathPrefix, KSet biomes) throws IOException { for (IrisBiome irisBiome : getAllBiomes(data)) { if (!irisBiome.isCustom()) { continue; @@ -542,12 +546,15 @@ public class IrisDimension extends IrisRegistrant { } for (File datapackRoot : datapackRoots) { - File output = new File(datapackRoot, "data/" + namespace + "/worldgen/biome/" + customBiomeId + ".json"); + String biomePath = pathPrefix.isBlank() + ? customBiomeId + : pathPrefix + "/" + customBiomeId; + File output = new File(datapackRoot, "data/" + namespace + "/worldgen/biome/" + biomePath + ".json"); IrisLogging.debug(" Installing Data Pack Biome: " + output.getPath()); output.getParentFile().mkdirs(); IO.writeAll(output, json); - installBiomeTags(datapackRoot, namespace + ":" + customBiomeId, customBiome.getTags()); + installBiomeTags(datapackRoot, namespace + ":" + biomePath, customBiome.getTags()); } } } diff --git a/core/src/main/resources/languages/de_DE.json b/core/src/main/resources/languages/de_DE.json index 9a5af30e2..037f102b4 100644 --- a/core/src/main/resources/languages/de_DE.json +++ b/core/src/main/resources/languages/de_DE.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "Geschwindigkeit: zwischengespeichert {chunksPerSecond} Chunks/s, {regionsPerMinute} Regionen/m, {chunksPerMinute} Chunks/m", "iris.desktop.pregen.time": "{remaining} verbleibend ({elapsed} abgelaufen)", "iris.desktop.pregen.method": "Erzeugungsmethode: {method}", - "iris.desktop.pregen.memory": "Speicher: {used} ({usage}) Druck: {pressure}/s" + "iris.desktop.pregen.memory": "Speicher: {used} ({usage}) Druck: {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/es_ES.json b/core/src/main/resources/languages/es_ES.json index b66ff092f..06421824b 100644 --- a/core/src/main/resources/languages/es_ES.json +++ b/core/src/main/resources/languages/es_ES.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "Velocidad: en caché {chunksPerSecond} chunks/s, {regionsPerMinute} regiones/m, {chunksPerMinute} chunks/m", "iris.desktop.pregen.time": "{remaining} restantes ({elapsed} transcurridos)", "iris.desktop.pregen.method": "Método de generación: {method}", - "iris.desktop.pregen.memory": "Memoria: {used} ({usage}) Presión: {pressure}/s" + "iris.desktop.pregen.memory": "Memoria: {used} ({usage}) Presión: {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/fi_FI.json b/core/src/main/resources/languages/fi_FI.json index 22b7f8822..8aa55d08b 100644 --- a/core/src/main/resources/languages/fi_FI.json +++ b/core/src/main/resources/languages/fi_FI.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "Nopeus: välimuisti {chunksPerSecond} chunkia/s, {regionsPerMinute} alueet/m {chunksPerMinute} chunkia/m", "iris.desktop.pregen.time": "{remaining} jäljellä ({elapsed} Kulunut)", "iris.desktop.pregen.method": "Generointimenetelmä: {method}", - "iris.desktop.pregen.memory": "Muisti: {used} ({usage}) Paine: {pressure}/s" + "iris.desktop.pregen.memory": "Muisti: {used} ({usage}) Paine: {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/fr_FR.json b/core/src/main/resources/languages/fr_FR.json index 76a390243..7bf5b5a0a 100644 --- a/core/src/main/resources/languages/fr_FR.json +++ b/core/src/main/resources/languages/fr_FR.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "Vitesse : cache {chunksPerSecond} chunks/s, {regionsPerMinute} régions/m, {chunksPerMinute} chunks/m", "iris.desktop.pregen.time": "{remaining} restantes ({elapsed} écoulées)", "iris.desktop.pregen.method": "Méthode de génération : {method}", - "iris.desktop.pregen.memory": "Mémoire : {used} ({usage}) Pression : {pressure}/s" + "iris.desktop.pregen.memory": "Mémoire : {used} ({usage}) Pression : {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspecter le biome Iris, la région, le biome souterrain, la surface et le chunk à votre position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/he_IL.json b/core/src/main/resources/languages/he_IL.json index 9c6d4e980..ae9f64c8d 100644 --- a/core/src/main/resources/languages/he_IL.json +++ b/core/src/main/resources/languages/he_IL.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "מהירות: במטמון {chunksPerSecond} צ'אנקים/s, {regionsPerMinute} אזורים/m, {chunksPerMinute} צ'אנקים/m", "iris.desktop.pregen.time": "{remaining} הנותרים ({elapsed} מת)", "iris.desktop.pregen.method": "שיטת דור: {method}", - "iris.desktop.pregen.memory": "זיכרון:{used} ({usage}לחץ:{pressure}/s" + "iris.desktop.pregen.memory": "זיכרון:{used} ({usage}לחץ:{pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/it_IT.json b/core/src/main/resources/languages/it_IT.json index 7b506be34..78b3f2cf1 100644 --- a/core/src/main/resources/languages/it_IT.json +++ b/core/src/main/resources/languages/it_IT.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "Velocità: cache {chunksPerSecond} chunk/s, {regionsPerMinute} regioni/m, {chunksPerMinute} chunk/m", "iris.desktop.pregen.time": "{remaining} rimanenti ({elapsed} trascorso)", "iris.desktop.pregen.method": "Metodo di generazione: {method}", - "iris.desktop.pregen.memory": "Memoria: {used} ({usage}) Pressione: {pressure}/s" + "iris.desktop.pregen.memory": "Memoria: {used} ({usage}) Pressione: {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/ja-JP.json b/core/src/main/resources/languages/ja-JP.json index ada9e2d3f..dd8e22377 100644 --- a/core/src/main/resources/languages/ja-JP.json +++ b/core/src/main/resources/languages/ja-JP.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "速度: キャッシュ {chunksPerSecond} チャンク/s、{regionsPerMinute} リージョン/m、{chunksPerMinute} チャンク/m", "iris.desktop.pregen.time": "残り {remaining}(経過 {elapsed})", "iris.desktop.pregen.method": "生成方法: {method}", - "iris.desktop.pregen.memory": "メモリ: {used}({usage})負荷: {pressure}/s" + "iris.desktop.pregen.memory": "メモリ: {used}({usage})負荷: {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/ko_KR.json b/core/src/main/resources/languages/ko_KR.json index 2690a5d23..0ae09a2d5 100644 --- a/core/src/main/resources/languages/ko_KR.json +++ b/core/src/main/resources/languages/ko_KR.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "속도: 캐시 {chunksPerSecond} 청크/s, {regionsPerMinute} 지구/m, {chunksPerMinute} 청크/m", "iris.desktop.pregen.time": "{remaining} 나머지 ({elapsed} 탈출)", "iris.desktop.pregen.method": "생성 방법: {method}", - "iris.desktop.pregen.memory": "기억: {used} ({usage}) 압력: {pressure}/s" + "iris.desktop.pregen.memory": "기억: {used} ({usage}) 압력: {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/lt_LT.json b/core/src/main/resources/languages/lt_LT.json index c24fa3062..40fcfe8f5 100644 --- a/core/src/main/resources/languages/lt_LT.json +++ b/core/src/main/resources/languages/lt_LT.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "Greitis: cached {chunksPerSecond} chunkai/s, {regionsPerMinute} regionai / m, {chunksPerMinute} chunkai / m", "iris.desktop.pregen.time": "{remaining} likę ({elapsed} praėjo)", "iris.desktop.pregen.method": "Gamybos būdas: {method}", - "iris.desktop.pregen.memory": "Atmintis: {used} ({usage}) Slėgis: {pressure}/s" + "iris.desktop.pregen.memory": "Atmintis: {used} ({usage}) Slėgis: {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/nl_NL.json b/core/src/main/resources/languages/nl_NL.json index e100a73c1..d19ed9ae8 100644 --- a/core/src/main/resources/languages/nl_NL.json +++ b/core/src/main/resources/languages/nl_NL.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "Snelheid: gecached {chunksPerSecond} chunks/s, {regionsPerMinute} regio's/m, {chunksPerMinute} chunks/m", "iris.desktop.pregen.time": "{remaining} resterende ({elapsed} verlopen)", "iris.desktop.pregen.method": "Generatiemethode: {method}", - "iris.desktop.pregen.memory": "Geheugen: {used} ({usage}) Druk {pressure}/s" + "iris.desktop.pregen.memory": "Geheugen: {used} ({usage}) Druk {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/pl_PL.json b/core/src/main/resources/languages/pl_PL.json index 5f0543d1a..53d19a098 100644 --- a/core/src/main/resources/languages/pl_PL.json +++ b/core/src/main/resources/languages/pl_PL.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "Prędkość: buforowana {chunksPerSecond} chunki/s, {regionsPerMinute} regiony / m, {chunksPerMinute} części / m", "iris.desktop.pregen.time": "{remaining} pozostałe ({elapsed} elapsed)", "iris.desktop.pregen.method": "Metoda wytwarzania: {method}", - "iris.desktop.pregen.memory": "Pamięć: {used} ({usage}) Ciśnienie: {pressure}/s" + "iris.desktop.pregen.memory": "Pamięć: {used} ({usage}) Ciśnienie: {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/pt_PT.json b/core/src/main/resources/languages/pt_PT.json index d7ff59b2e..9feb5f999 100644 --- a/core/src/main/resources/languages/pt_PT.json +++ b/core/src/main/resources/languages/pt_PT.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "Velocidade: em cache {chunksPerSecond} chunks/s, {regionsPerMinute} regiões/m, {chunksPerMinute} chunks/m", "iris.desktop.pregen.time": "{remaining} restantes ({elapsed} transcorrido)", "iris.desktop.pregen.method": "Método de geração: {method}", - "iris.desktop.pregen.memory": "Memória: {used} ({usage}) Pressão: {pressure}/s" + "iris.desktop.pregen.memory": "Memória: {used} ({usage}) Pressão: {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/ru_RU.json b/core/src/main/resources/languages/ru_RU.json index 7894d4334..59846f880 100644 --- a/core/src/main/resources/languages/ru_RU.json +++ b/core/src/main/resources/languages/ru_RU.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "Скорость: кэшировано {chunksPerSecond} чанки/s, {regionsPerMinute} области/м, {chunksPerMinute} чанки/м", "iris.desktop.pregen.time": "{remaining} оставшееся ({elapsed} истекший)", "iris.desktop.pregen.method": "Метод генерации: {method}", - "iris.desktop.pregen.memory": "Память: {used} ({usage}) Давление: {pressure}/s" + "iris.desktop.pregen.memory": "Память: {used} ({usage}) Давление: {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/tr_TR.json b/core/src/main/resources/languages/tr_TR.json index 5af68a95b..6d2c684ee 100644 --- a/core/src/main/resources/languages/tr_TR.json +++ b/core/src/main/resources/languages/tr_TR.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "Hız: Önbelli {chunksPerSecond} chunk/s, {regionsPerMinute} bölgeler/m, {chunksPerMinute} chunks /", "iris.desktop.pregen.time": "{remaining} kalan ({elapsed} elapd)", "iris.desktop.pregen.method": "Nesil yöntemi: {method}", - "iris.desktop.pregen.memory": "bellek: {used} ({usage}Baskı:) {pressure}/s" + "iris.desktop.pregen.memory": "bellek: {used} ({usage}Baskı:) {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/vi_VI.json b/core/src/main/resources/languages/vi_VI.json index 8e7034683..fc58c8e44 100644 --- a/core/src/main/resources/languages/vi_VI.json +++ b/core/src/main/resources/languages/vi_VI.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "Tốc độ: đã lưu tạm {chunksPerSecond} Chunk/s, {regionsPerMinute} Vùng/m, {chunksPerMinute} Chunk", "iris.desktop.pregen.time": "{remaining} còn lại ({elapsed} Mở rộng)", "iris.desktop.pregen.method": "Phương pháp thế hệ: {method}", - "iris.desktop.pregen.memory": "Bộ nhớ: {used} ({usage}) Áp lực: {pressure}/s" + "iris.desktop.pregen.memory": "Bộ nhớ: {used} ({usage}) Áp lực: {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/zh_CN.json b/core/src/main/resources/languages/zh_CN.json index 5c905801c..5bfb3aea2 100644 --- a/core/src/main/resources/languages/zh_CN.json +++ b/core/src/main/resources/languages/zh_CN.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "速度:已缓存 {chunksPerSecond} 块/s, {regionsPerMinute} 区域/米, {chunksPerMinute} 块/米", "iris.desktop.pregen.time": "{remaining} 剩余({elapsed} 已过期)", "iris.desktop.pregen.method": "生成方法 : {method}", - "iris.desktop.pregen.memory": "内存 : {used} ({usage})压力: {pressure}/s" + "iris.desktop.pregen.memory": "内存 : {used} ({usage})压力: {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/main/resources/languages/zh_TW.json b/core/src/main/resources/languages/zh_TW.json index f4fc21200..5565b003b 100644 --- a/core/src/main/resources/languages/zh_TW.json +++ b/core/src/main/resources/languages/zh_TW.json @@ -1427,6 +1427,49 @@ "iris.desktop.pregen.speed_cached": "速度:已快取 {chunksPerSecond} 塊/s, {regionsPerMinute} 區域/米, {chunksPerMinute} 塊/米", "iris.desktop.pregen.time": "{remaining} 剩餘({elapsed} 已過期)", "iris.desktop.pregen.method": "生成方法 : {method}", - "iris.desktop.pregen.memory": "記憶體 : {used} ({usage})壓力: {pressure}/s" + "iris.desktop.pregen.memory": "記憶體 : {used} ({usage})壓力: {pressure}/s", + "iris.modded.help.entry.command.what.here": "Inspect the Iris biome, region, cave biome, surface and chunk at your position", + "iris.modded.help.entry.command.what.biome": "Inspect the Iris biome, configured derivative and registered native biome", + "iris.modded.help.entry.command.what.region": "Inspect the Iris region at your current chunk", + "iris.modded.help.entry.command.what.block": "Inspect the targeted block state, properties, object and block entity", + "iris.modded.help.entry.command.what.hand": "Inspect the held item and its default block state", + "iris.modded.help.entry.command.what.markers": "Reveal nearby Iris Mantle markers with particles", + "iris.modded.help.entry.command.height": "Print the current dimension height range", + "iris.modded.help.entry.command.worlds": "List loaded dimensions and identify the Iris-generated dimensions", + "iris.modded.help.entry.command.mainworld": "Configure or clear the Iris primary-world preset used on restart", + "iris.modded.help.entry.command.object.we": "Explain why WorldEdit selection import requires the Bukkit plugin", + "iris.modded.help.entry.command.object.studio": "Explain why the object studio world requires the Bukkit toolchain", + "iris.modded.help.entry.command.object.convert": "Explain why schematic conversion requires the Bukkit plugin", + "iris.modded.help.entry.command.studio.loot": "Explain why the Bukkit loot simulation GUI is unavailable", + "iris.modded.help.entry.command.studio.profile": "Explain why Bukkit pack profiling is unavailable", + "iris.modded.help.entry.command.studio.spawn": "Explain why Bukkit Iris entity spawning is unavailable", + "iris.modded.help.entry.command.studio.objects": "Explain why the Bukkit chunk object report is unavailable", + "iris.runtime.dust.reveal_failed": "Object reveal failed; see the console for details.", + "iris.runtime.what.material": "Material: {material}", + "iris.runtime.what.full_state": "Full: {state}", + "iris.runtime.what.item_count": "Count: {count}", + "iris.runtime.what.iris_biome": "Iris biome: {biome} ({name})", + "iris.runtime.what.derivative_biome": "Derivative biome: {biome}", + "iris.runtime.what.native_biome": "Registered biome: {biome} (ID: {id})", + "iris.runtime.what.non_iris_biome": "Non-Iris biome: {biome} (ID: {id})", + "iris.runtime.what.iris_region": "Iris region: {region} ({name})", + "iris.runtime.what.position": "Position: {x}, {y}, {z} (chunk {chunkX}, {chunkZ})", + "iris.runtime.what.object": "Iris object: {object}", + "iris.runtime.what.block_entity": "Block entity: {type}", + "iris.runtime.what.loot_table": "Loot table: {loot}", + "iris.runtime.what.spawner_entity": "Spawner entity: {entity}", + "iris.runtime.what.flag.solid": "solid", + "iris.runtime.what.flag.fluid": "fluid", + "iris.runtime.what.flag.water": "water", + "iris.runtime.what.flag.waterlogged": "waterlogged", + "iris.runtime.what.flag.storage": "storage (loot capable)", + "iris.runtime.what.flag.lit": "lit", + "iris.runtime.what.flag.foliage": "foliage", + "iris.runtime.what.flag.plantable_foliage": "plantable foliage", + "iris.runtime.what.flag.decorant": "decorant", + "iris.runtime.what.flag.ore": "ore", + "iris.runtime.what.flag.block_entity": "block entity", + "iris.runtime.world.height_range": "World height: {minY} to {maxY}", + "iris.runtime.world.height_total": "Total height: {height}" } } diff --git a/core/src/test/java/art/arcane/iris/core/protocol/IrisCursorResolverTest.java b/core/src/test/java/art/arcane/iris/core/protocol/IrisCursorResolverTest.java index f7f23a228..a7f430c67 100644 --- a/core/src/test/java/art/arcane/iris/core/protocol/IrisCursorResolverTest.java +++ b/core/src/test/java/art/arcane/iris/core/protocol/IrisCursorResolverTest.java @@ -40,7 +40,7 @@ public class IrisCursorResolverTest { cave.setLoadKey("iris:lush_cave"); IrisDimension dimension = new IrisDimension(); dimension.setLoadKey("overworld"); - Engine engine = engine(biome, region, cave, 72, dimension); + Engine engine = engine(biome, region, cave, 72, -64, dimension); IrisMessage.CursorInfo info = IrisCursorResolver.resolve(engine, 100, -200); @@ -49,7 +49,7 @@ public class IrisCursorResolverTest { assertEquals("iris:plains", info.biomeKey()); assertEquals("iris:temperate", info.regionKey()); assertEquals("iris:lush_cave", info.caveBiomeKey()); - assertEquals(72, info.height()); + assertEquals(8, info.height()); assertEquals("overworld", info.dimensionKey()); } @@ -57,7 +57,7 @@ public class IrisCursorResolverTest { public void missingCaveAndNullKeysCollapseToEmptyStrings() { IrisDimension dimension = new IrisDimension(); dimension.setLoadKey("overworld"); - Engine engine = engine(null, null, null, 0, dimension); + Engine engine = engine(null, null, null, 0, -64, dimension); IrisMessage.CursorInfo info = IrisCursorResolver.resolve(engine, 0, 0); @@ -67,12 +67,13 @@ public class IrisCursorResolverTest { assertEquals("overworld", info.dimensionKey()); } - private static Engine engine(IrisBiome biome, IrisRegion region, IrisBiome cave, int height, IrisDimension dimension) { + private static Engine engine(IrisBiome biome, IrisRegion region, IrisBiome cave, int height, int minHeight, IrisDimension dimension) { return (Engine) Proxy.newProxyInstance(Engine.class.getClassLoader(), new Class[]{Engine.class}, (proxy, method, args) -> switch (method.getName()) { case "getSurfaceBiome" -> biome; case "getRegion" -> region; case "getCaveBiome" -> cave; case "getHeight" -> height; + case "getMinHeight" -> minHeight; case "getDimension" -> dimension; case "toString" -> "proxyEngine"; case "hashCode" -> System.identityHashCode(proxy); diff --git a/core/src/test/java/art/arcane/iris/core/protocol/IrisProtocolServerTest.java b/core/src/test/java/art/arcane/iris/core/protocol/IrisProtocolServerTest.java index 8ab247abe..90c7a97ee 100644 --- a/core/src/test/java/art/arcane/iris/core/protocol/IrisProtocolServerTest.java +++ b/core/src/test/java/art/arcane/iris/core/protocol/IrisProtocolServerTest.java @@ -90,6 +90,25 @@ public class IrisProtocolServerTest { assertTrue(answer.irisActive()); } + @Test + public void repeatedHelloResendsServerHelloForPacketLossRecovery() { + RecordingTransport transport = new RecordingTransport(); + IrisSessionRegistry registry = new IrisSessionRegistry(); + IrisProtocolServer server = new IrisProtocolServer(registry, SERVER_CAPABILITIES, BRAND, true); + IrisSession session = new IrisSession("s1", transport); + registry.register(session); + byte[] hello = IrisMessageCodec.encode(new IrisMessage.ClientHello( + IrisProtocol.PROTOCOL_VERSION, IrisProtocol.CAPABILITY_PREGEN)); + + server.onClientFrame("s1", hello); + server.onClientFrame("s1", hello); + + assertEquals(IrisSession.State.READY, session.state()); + assertEquals(2, transport.sent.size()); + assertTrue(transport.sent.get(0) instanceof IrisMessage.ServerHello); + assertTrue(transport.sent.get(1) instanceof IrisMessage.ServerHello); + } + @Test public void readySessionReceivesProgressAndEndBroadcasts() { RecordingTransport transport = new RecordingTransport(); @@ -493,6 +512,7 @@ public class IrisProtocolServerTest { case "getRegion" -> region; case "getCaveBiome" -> cave; case "getHeight" -> height; + case "getMinHeight" -> 0; case "getDimension" -> dimension; case "toString" -> "proxyEngine"; case "hashCode" -> System.identityHashCode(proxy);