This commit is contained in:
Brian Neumann-Fopiano
2026-07-27 13:46:24 -05:00
parent d5a55ccfcf
commit 74741ac83e
112 changed files with 3393 additions and 1025 deletions
@@ -97,11 +97,20 @@ public final class IrisClient {
} }
public static void onWorldJoin() { public static void onWorldJoin() {
clearWorldState();
SESSION.sendHello(); SESSION.sendHello();
} }
public static void onDisconnect() { public static void onDisconnect() {
SESSION.reset(); SESSION.reset();
clearWorldState();
}
public static void tick() {
SESSION.tick();
}
private static void clearWorldState() {
PREGEN.clear(); PREGEN.clear();
DIMENSION.clear(); DIMENSION.clear();
TILES.clear(); TILES.clear();
@@ -7,6 +7,7 @@ import java.util.function.LongSupplier;
public final class IrisClientCursor { public final class IrisClientCursor {
private static final long MIN_REQUEST_INTERVAL_MILLIS = 500L; 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 ClientPacketSink sink;
private final LongSupplier clock; private final LongSupplier clock;
@@ -27,10 +28,11 @@ public final class IrisClientCursor {
} }
public synchronized void requestFor(int blockX, int blockZ) { 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; return;
} }
long now = clock.getAsLong();
if (now - lastRequestMillis < MIN_REQUEST_INTERVAL_MILLIS) { if (now - lastRequestMillis < MIN_REQUEST_INTERVAL_MILLIS) {
return; return;
} }
@@ -11,7 +11,7 @@ public final class IrisClientDimension {
if (previous == null) { if (previous == null) {
return true; return true;
} }
return previous.irisWorld() != incoming.irisWorld() || !previous.dimensionKey().equals(incoming.dimensionKey()); return !previous.equals(incoming);
} }
public IrisMessage.DimensionStatus status() { public IrisMessage.DimensionStatus status() {
@@ -4,21 +4,35 @@ import art.arcane.iris.spi.protocol.IrisMessage;
import art.arcane.iris.spi.protocol.IrisMessageCodec; import art.arcane.iris.spi.protocol.IrisMessageCodec;
import art.arcane.iris.spi.protocol.IrisProtocol; import art.arcane.iris.spi.protocol.IrisProtocol;
import java.util.function.LongSupplier;
public final class IrisClientSession { 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 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 State state;
private volatile long serverCapabilities; private volatile long serverCapabilities;
private volatile boolean irisActive; private volatile boolean irisActive;
private volatile String serverBrand; private volatile String serverBrand;
private volatile ClientPacketSink sink; private volatile ClientPacketSink sink;
private volatile long nextHelloAt;
private volatile int helloAttempts;
public IrisClientSession() { public IrisClientSession() {
this(System::currentTimeMillis);
}
IrisClientSession(LongSupplier clock) {
this.clock = clock;
this.state = State.IDLE; this.state = State.IDLE;
this.serverCapabilities = 0L; this.serverCapabilities = 0L;
this.irisActive = false; this.irisActive = false;
this.serverBrand = ""; this.serverBrand = "";
this.sink = null; this.sink = null;
this.nextHelloAt = Long.MAX_VALUE;
this.helloAttempts = 0;
} }
public void bind(ClientPacketSink boundSink) { public void bind(ClientPacketSink boundSink) {
@@ -46,16 +60,39 @@ public final class IrisClientSession {
} }
public void sendHello() { 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; ClientPacketSink activeSink = sink;
if (activeSink == null) { if (activeSink == null) {
return; return;
} }
byte[] frame = IrisMessageCodec.encode(new IrisMessage.ClientHello(IrisProtocol.PROTOCOL_VERSION, CLIENT_CAPABILITIES)); byte[] frame = IrisMessageCodec.encode(new IrisMessage.ClientHello(IrisProtocol.PROTOCOL_VERSION, CLIENT_CAPABILITIES));
state = State.AWAITING_HELLO; state = State.AWAITING_HELLO;
helloAttempts++;
nextHelloAt = clock.getAsLong() + HELLO_RETRY_MILLIS;
activeSink.send(frame); activeSink.send(frame);
} }
public void onServerHello(IrisMessage.ServerHello hello) { public void onServerHello(IrisMessage.ServerHello hello) {
if (hello.protocolVersion() != IrisProtocol.PROTOCOL_VERSION) {
this.state = State.INCOMPATIBLE;
return;
}
this.serverCapabilities = hello.capabilities(); this.serverCapabilities = hello.capabilities();
this.irisActive = hello.irisActive(); this.irisActive = hello.irisActive();
this.serverBrand = hello.serverBrand(); this.serverBrand = hello.serverBrand();
@@ -67,11 +104,15 @@ public final class IrisClientSession {
this.serverCapabilities = 0L; this.serverCapabilities = 0L;
this.irisActive = false; this.irisActive = false;
this.serverBrand = ""; this.serverBrand = "";
this.nextHelloAt = Long.MAX_VALUE;
this.helloAttempts = 0;
} }
public enum State { public enum State {
IDLE, IDLE,
AWAITING_HELLO, AWAITING_HELLO,
READY READY,
UNSUPPORTED,
INCOMPATIBLE
} }
} }
@@ -45,7 +45,7 @@ public final class IrisVisionScreen extends Screen {
private double centerBlockZ; private double centerBlockZ;
private int zoom; private int zoom;
private boolean initialized; private boolean initialized;
private String renderedDimensionKey; private IrisMessage.DimensionStatus renderedDimension;
public IrisVisionScreen() { public IrisVisionScreen() {
super(Component.literal(IrisLanguage.plain(ClientUiMessages.VISION_TITLE))); super(Component.literal(IrisLanguage.plain(ClientUiMessages.VISION_TITLE)));
@@ -54,7 +54,7 @@ public final class IrisVisionScreen extends Screen {
this.centerBlockZ = 0.0D; this.centerBlockZ = 0.0D;
this.zoom = DEFAULT_ZOOM; this.zoom = DEFAULT_ZOOM;
this.initialized = false; this.initialized = false;
this.renderedDimensionKey = null; this.renderedDimension = null;
} }
@Override @Override
@@ -290,12 +290,12 @@ public final class IrisVisionScreen extends Screen {
} }
private void syncWorld(IrisMessage.DimensionStatus status) { private void syncWorld(IrisMessage.DimensionStatus status) {
if (renderedDimensionKey == null) { if (renderedDimension == null) {
renderedDimensionKey = status.dimensionKey(); renderedDimension = status;
return; return;
} }
if (!renderedDimensionKey.equals(status.dimensionKey())) { if (!renderedDimension.equals(status)) {
renderedDimensionKey = status.dimensionKey(); renderedDimension = status;
releaseTextures(); releaseTextures();
centerOnPlayer(); centerOnPlayer();
} }
@@ -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<WorldPreset> preset = worldType.preset();
if (preset == null) {
return false;
}
Optional<ResourceKey<WorldPreset>> key = preset.unwrapKey();
return key.isPresent() && "irisworldgen".equals(key.get().identifier().getNamespace());
}
private static boolean iris$containsIrisGenerator(WorldStem worldStem) {
Registry<LevelStem> dimensions = worldStem.registries()
.compositeAccess()
.lookupOrThrow(Registries.LEVEL_STEM);
for (LevelStem dimension : dimensions) {
if (dimension.generator() instanceof IrisModdedChunkGenerator) {
return true;
}
}
return false;
}
}
@@ -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<WorldPreset> preset;
@Inject(method = "describePreset", at = @At("HEAD"), cancellable = true)
private void iris$describePreset(CallbackInfoReturnable<Component> info) {
Optional<ResourceKey<WorldPreset>> 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));
}
}
}
+4
View File
@@ -172,6 +172,10 @@ tasks.named('test').configure {
loom { loom {
accessWidenerPath = file('src/main/resources/irisworldgen.accesswidener') accessWidenerPath = file('src/main/resources/irisworldgen.accesswidener')
runs { runs {
client {
runDir(providers.gradleProperty('irisClientRunDir').getOrElse('run'))
vmArg('-Xmx8G')
}
server { server {
String parity = providers.gradleProperty('irisParity').getOrNull() String parity = providers.gradleProperty('irisParity').getOrNull()
if (parity != null) { if (parity != null) {
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -371
View File
@@ -1,371 +1,2 @@
[19:42:28] [Test worker/INFO]: Iris registered custom content provider 'iris_deferred_test' [15:08:50] [Test worker/INFO]: Iris object undo service ready (bounded to 32 paste(s) per player)
[19:42:28] [Test worker/ERROR]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService [15:08:50] [Test worker/INFO]: [STDOUT]: [Iris/INFO] Iris /iris command tree registered
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)
@@ -45,6 +45,9 @@ public final class IrisFabricClient implements ClientModInitializer {
KeyMappingHelper.registerKeyMapping(IrisClientKeybinds.OPEN_MAP); KeyMappingHelper.registerKeyMapping(IrisClientKeybinds.OPEN_MAP);
KeyMappingHelper.registerKeyMapping(IrisClientKeybinds.TOGGLE_WHAT); KeyMappingHelper.registerKeyMapping(IrisClientKeybinds.TOGGLE_WHAT);
HudElementRegistry.addLast(IrisClient.HUD_ELEMENT_ID, (graphics, delta) -> IrisClientHud.render(graphics)); 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();
});
} }
} }
@@ -20,17 +20,22 @@ package art.arcane.iris.fabric.mixin;
import art.arcane.iris.fabric.FabricForcedDatapackSources; import art.arcane.iris.fabric.FabricForcedDatapackSources;
import net.minecraft.server.packs.repository.PackRepository; import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.server.packs.repository.RepositorySource;
import net.minecraft.server.packs.repository.ServerPacksSource; 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.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; 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) @Mixin(PackRepository.class)
public class ServerPacksSourceMixin { public class PackRepositoryMixin {
@Inject(method = "createPackRepository(Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;)Lnet/minecraft/server/packs/repository/PackRepository;", at = @At("RETURN")) @Inject(method = "<init>", at = @At("RETURN"))
private static void iris$addForcedDatapackSource(LevelStorageSource.LevelStorageAccess storage, CallbackInfoReturnable<PackRepository> info) { private void iris$addForcedDatapackSource(RepositorySource[] sources, CallbackInfo info) {
FabricForcedDatapackSources.attach(info.getReturnValue()); for (RepositorySource source : sources) {
if (source instanceof ServerPacksSource) {
FabricForcedDatapackSources.attach((PackRepository) (Object) this);
return;
}
}
} }
} }
@@ -11,7 +11,14 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"environment": "*", "environment": "*",
"accessWidener": "irisworldgen.accesswidener", "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": { "entrypoints": {
"main": ["art.arcane.iris.fabric.IrisFabricBootstrap"], "main": ["art.arcane.iris.fabric.IrisFabricBootstrap"],
"client": ["art.arcane.iris.fabric.IrisFabricClient"] "client": ["art.arcane.iris.fabric.IrisFabricClient"]
@@ -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/MinecraftServer storageSource Lnet/minecraft/world/level/storage/LevelStorageSource$LevelStorageAccess;
accessible field net/minecraft/server/packs/repository/PackRepository sources Ljava/util/Set; accessible field net/minecraft/server/packs/repository/PackRepository sources Ljava/util/Set;
mutable 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
@@ -6,7 +6,7 @@
"mixins": [ "mixins": [
"BlockItemMixin", "BlockItemMixin",
"BlockMixin", "BlockMixin",
"ServerPacksSourceMixin" "PackRepositoryMixin"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1
+7 -1
View File
@@ -174,6 +174,12 @@ dependencies {
minecraft { minecraft {
accessTransformer.from(file('src/main/resources/META-INF/accesstransformer.cfg')) accessTransformer.from(file('src/main/resources/META-INF/accesstransformer.cfg'))
runs { 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') { register('server') {
workingDir = layout.projectDirectory.dir('run') workingDir = layout.projectDirectory.dir('run')
String parity = providers.gradleProperty('irisParity').getOrNull() 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")) delete(layout.buildDirectory.file("libs/Iris-${project.version}+mc${minecraftVersion}-forge.jar"))
} }
manifest { manifest {
attributes('MixinConfigs': 'irisworldgen.entity.mixins.json') attributes('MixinConfigs': 'irisworldgen.entity.mixins.json,irisworldgen.client.mixins.json')
} }
archiveFileName.set(irisArtifactName('Forge', "${minecraftVersion}+${loaderDisplayVersion(forgeVersion)}")) archiveFileName.set(irisArtifactName('Forge', "${minecraftVersion}+${loaderDisplayVersion(forgeVersion)}"))
configurations = [project.configurations.named('bundle').get()] configurations = [project.configurations.named('bundle').get()]
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+61 -13
View File
@@ -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 [26Jul2026 15:03:10.625] [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 [26Jul2026 15:03:10.627] [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 [26Jul2026 15:03:10.627] [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' [26Jul2026 15:03:12.446] [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.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 java.lang.RuntimeException: second disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:55) ~[test/:?] at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:55) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] 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 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.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [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 java.lang.RuntimeException: first disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:54) ~[test/:?] at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:54) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] 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 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.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [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 java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?] at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] 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 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.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [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 java.lang.RuntimeException: enable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:31) ~[test/:?] at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:31) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] 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 Suppressed: java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?] at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
... 42 more ... 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 java.lang.IllegalStateException: stop request failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$stopRequestFailureForcesNonzeroResult$1(ModdedWorldCheckTest.java:235) ~[test/:?] at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$stopRequestFailureForcesNonzeroResult$1(ModdedWorldCheckTest.java:235) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:179) ~[main/:?] 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 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.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [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 java.lang.IllegalStateException: shutdown wait failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$shutdownWaitFailureForcesNonzeroExit$0(ModdedWorldCheckTest.java:261) ~[test/:?] at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$shutdownWaitFailureForcesNonzeroExit$0(ModdedWorldCheckTest.java:261) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:194) ~[main/:?] 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 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.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [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 java.lang.IllegalStateException: check failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$thrownCheckStillRequestsStop$0(ModdedWorldCheckTest.java:221) ~[test/:?] at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$thrownCheckStillRequestsStop$0(ModdedWorldCheckTest.java:221) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:174) ~[main/:?] 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 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.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [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' [26Jul2026 15:03:12.577] [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.578] [Test worker/ERROR] [Iris/]: Iris custom content provider discovery failed
java.lang.RuntimeException: provider init failed java.lang.RuntimeException: provider init failed
at art.arcane.iris.modded.api.ModdedCustomContentRegistryTest.failedDiscoveryPublishesNothingAndPreservesTheCause(ModdedCustomContentRegistryTest.java:39) ~[test/:?] at art.arcane.iris.modded.api.ModdedCustomContentRegistryTest.failedDiscoveryPublishesNothingAndPreservesTheCause(ModdedCustomContentRegistryTest.java:39) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] 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 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.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [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)
+58 -10
View File
@@ -1,5 +1,52 @@
[20Jul2026 19:42:38.772] [Test worker/INFO] [Iris/]: Iris registered custom content provider 'iris_deferred_test' [26Jul2026 15:03:12.446] [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.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 java.lang.RuntimeException: second disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:55) ~[test/:?] at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:55) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] 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 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.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [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 java.lang.RuntimeException: first disable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:54) ~[test/:?] at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndAggregatesFailures(ModdedServiceManagerTest.java:54) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] 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 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.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [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 java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?] at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] 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 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.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [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 java.lang.RuntimeException: enable failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:31) ~[test/:?] at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:31) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] 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 Suppressed: java.lang.RuntimeException: cleanup failed
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?] at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32) ~[test/:?]
... 42 more ... 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 java.lang.IllegalStateException: stop request failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$stopRequestFailureForcesNonzeroResult$1(ModdedWorldCheckTest.java:235) ~[test/:?] at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$stopRequestFailureForcesNonzeroResult$1(ModdedWorldCheckTest.java:235) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:179) ~[main/:?] 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 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.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [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 java.lang.IllegalStateException: shutdown wait failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$shutdownWaitFailureForcesNonzeroExit$0(ModdedWorldCheckTest.java:261) ~[test/:?] at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$shutdownWaitFailureForcesNonzeroExit$0(ModdedWorldCheckTest.java:261) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:194) ~[main/:?] 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 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.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [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 java.lang.IllegalStateException: check failed
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$thrownCheckStillRequestsStop$0(ModdedWorldCheckTest.java:221) ~[test/:?] at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$thrownCheckStillRequestsStop$0(ModdedWorldCheckTest.java:221) ~[test/:?]
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:174) ~[main/:?] 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 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.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [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' [26Jul2026 15:03:12.577] [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.578] [Test worker/ERROR] [Iris/]: Iris custom content provider discovery failed
java.lang.RuntimeException: provider init failed java.lang.RuntimeException: provider init failed
at art.arcane.iris.modded.api.ModdedCustomContentRegistryTest.failedDiscoveryPublishesNothingAndPreservesTheCause(ModdedCustomContentRegistryTest.java:39) ~[test/:?] at art.arcane.iris.modded.api.ModdedCustomContentRegistryTest.failedDiscoveryPublishesNothingAndPreservesTheCause(ModdedCustomContentRegistryTest.java:39) ~[test/:?]
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?] 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 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.run(GradleWorkerMain.java:69) [gradle-worker.jar:?]
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) [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)
@@ -27,6 +27,7 @@ import net.minecraftforge.client.event.AddGuiOverlayLayersEvent;
import net.minecraftforge.client.event.ClientPlayerNetworkEvent; import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
import net.minecraftforge.client.event.InputEvent; import net.minecraftforge.client.event.InputEvent;
import net.minecraftforge.client.event.RegisterKeyMappingsEvent; import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.network.Channel; import net.minecraftforge.network.Channel;
import net.minecraftforge.network.PacketDistributor; import net.minecraftforge.network.PacketDistributor;
@@ -46,6 +47,8 @@ public final class IrisForgeClient {
ClientPlayerNetworkEvent.LoggingIn.BUS.addListener((ClientPlayerNetworkEvent.LoggingIn event) -> IrisClient.onWorldJoin()); ClientPlayerNetworkEvent.LoggingIn.BUS.addListener((ClientPlayerNetworkEvent.LoggingIn event) -> IrisClient.onWorldJoin());
ClientPlayerNetworkEvent.LoggingOut.BUS.addListener((ClientPlayerNetworkEvent.LoggingOut event) -> IrisClient.onDisconnect()); ClientPlayerNetworkEvent.LoggingOut.BUS.addListener((ClientPlayerNetworkEvent.LoggingOut event) -> IrisClient.onDisconnect());
InputEvent.Key.BUS.addListener((InputEvent.Key event) -> IrisClientKeybinds.pollToggle()); 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) { private static void sendToServer(byte[] frame) {
@@ -1,4 +1,3 @@
public net.minecraft.server.MinecraftServer levels public net.minecraft.server.MinecraftServer levels
public net.minecraft.server.MinecraftServer executor public net.minecraft.server.MinecraftServer executor
public net.minecraft.server.MinecraftServer storageSource public net.minecraft.server.MinecraftServer storageSource
public net.minecraft.core.MappedRegistry frozen
@@ -334,8 +334,7 @@ final class IrisModdedBiomeSource extends BiomeSource {
if (customBiome == null) { if (customBiome == null) {
return fallbackBiome(registry, quartX, quartY, quartZ, sampler); return fallbackBiome(registry, quartX, quartY, quartZ, sampler);
} }
biomeKey = engine.getDimension().getLoadKey().toLowerCase(Locale.ROOT) biomeKey = ModdedWorldgenIds.biomeRef(engine, customBiome.getId());
+ ":" + customBiome.getId().toLowerCase(Locale.ROOT);
} else if (resolution.underground()) { } else if (resolution.underground()) {
biomeKey = resolution.irisBiome().getGroundBiomeKey( biomeKey = resolution.irisBiome().getGroundBiomeKey(
resolution.rng(), engine, resolution.blockX(), resolution.blockY(), resolution.blockZ()); 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"); throw new IllegalStateException("Iris structure biome key lookup has no active engine runtime");
} }
LinkedHashSet<String> possible = new LinkedHashSet<>(); LinkedHashSet<String> possible = new LinkedHashSet<>();
String namespace = engine.getDimension().getLoadKey().toLowerCase(Locale.ROOT);
for (IrisBiome irisBiome : engine.getAllBiomes()) { for (IrisBiome irisBiome : engine.getAllBiomes()) {
String derivative = normalizeKey(irisBiome.getStructureDerivativeKey()); String derivative = normalizeKey(irisBiome.getStructureDerivativeKey());
if (derivative != null) { if (derivative != null) {
@@ -524,7 +522,7 @@ final class IrisModdedBiomeSource extends BiomeSource {
continue; continue;
} }
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) { for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
possible.add(namespace + ":" + customBiome.getId().toLowerCase(Locale.ROOT)); possible.add(ModdedWorldgenIds.biomeRef(engine, customBiome.getId()));
} }
} }
return Set.copyOf(possible); return Set.copyOf(possible);
@@ -26,13 +26,11 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.UUID;
public final class MainWorldService { public final class MainWorldService {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); 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 MARKER_NAME = "mainworld.pending";
private static final String[] VANILLA_DIMENSION_FOLDERS = { private static final String[] VANILLA_DIMENSION_FOLDERS = {
"region", "region",
@@ -54,8 +52,7 @@ public final class MainWorldService {
int colon = value.indexOf(':'); int colon = value.indexOf(':');
String pack = colon >= 0 ? value.substring(0, colon) : value; String pack = colon >= 0 ? value.substring(0, colon) : value;
String dimension = colon >= 0 ? value.substring(colon + 1) : value; String dimension = colon >= 0 ? value.substring(colon + 1) : value;
String presetKey = dimension.equals(pack) ? pack : pack + "_" + dimension; return ModdedWorldgenIds.presetRef(pack, dimension);
return PRESET_NAMESPACE + ":" + presetKey;
} }
public static void reconcileEarly() { public static void reconcileEarly() {
@@ -78,15 +75,22 @@ public final class MainWorldService {
return; return;
} }
String levelName = firstNonBlank(readProperty(properties, "level-name"), "world"); String levelName = firstNonBlank(readProperty(properties, "level-name"), "world");
wipeVanillaDimensions(instanceRoot().resolve(levelName)); Path worldRoot = resolveWorldRoot(levelName);
Path recovery = quarantineVanillaDimensions(worldRoot);
clearPending(); 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) { } catch (Throwable e) {
LOGGER.error("Iris main world reconciliation failed", 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) { 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 { try {
Path properties = instanceRoot().resolve("server.properties"); Path properties = instanceRoot().resolve("server.properties");
writeLevelProperties(properties, presetIdFor(packRef), seed); writeLevelProperties(properties, presetIdFor(packRef), seed);
@@ -164,24 +168,53 @@ public final class MainWorldService {
lines.add(prefix + value); lines.add(prefix + value);
} }
private static void wipeVanillaDimensions(Path worldRoot) throws IOException { private static Path resolveWorldRoot(String levelName) throws IOException {
Files.deleteIfExists(worldRoot.resolve("level.dat")); Path root = instanceRoot().toAbsolutePath().normalize();
Files.deleteIfExists(worldRoot.resolve("level.dat_old")); Path worldRoot = root.resolve(levelName).toAbsolutePath().normalize();
for (String folder : VANILLA_DIMENSION_FOLDERS) { if (worldRoot.equals(root) || !worldRoot.startsWith(root)) {
deleteRecursively(worldRoot.resolve(folder)); throw new IOException("Unsafe level-name path outside the server instance: " + levelName);
} }
return worldRoot;
} }
private static void deleteRecursively(Path path) throws IOException { private static Path quarantineVanillaDimensions(Path worldRoot) throws IOException {
if (!Files.exists(path)) { Path recovery = markerFile().getParent().resolve("mainworld-recovery-" + UUID.randomUUID());
List<Path> 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<Path> moved) throws IOException {
if (!Files.exists(source)) {
return; return;
} }
List<Path> entries = new ArrayList<>(); Path relative = worldRoot.relativize(source);
try (Stream<Path> walk = Files.walk(path)) { Path target = recovery.resolve(relative);
walk.sorted(Comparator.comparingInt(Path::getNameCount).reversed()).forEach(entries::add); Files.createDirectories(target.getParent());
} try {
for (Path entry : entries) { Files.move(source, target);
Files.deleteIfExists(entry); 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;
} }
} }
@@ -23,6 +23,7 @@ import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisBiomeCustom; import art.arcane.iris.engine.object.IrisBiomeCustom;
import art.arcane.iris.spi.PlatformBiome; import art.arcane.iris.spi.PlatformBiome;
import art.arcane.iris.spi.PlatformBiomeWriter; import art.arcane.iris.spi.PlatformBiomeWriter;
import art.arcane.iris.util.project.context.IrisContext;
import net.minecraft.core.Registry; import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries; import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier; import net.minecraft.resources.Identifier;
@@ -51,7 +52,7 @@ public final class ModdedBiomeWriter implements PlatformBiomeWriter {
if (registry == null) { if (registry == null) {
return 0; return 0;
} }
int direct = idForKey(registry, key); int direct = idForKey(registry, scopedBiomeKey(key));
if (direct >= 0) { if (direct >= 0) {
return direct; return direct;
} }
@@ -62,6 +63,19 @@ public final class ModdedBiomeWriter implements PlatformBiomeWriter {
return fallbackId(registry); 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 @Override
public List<PlatformBiome> allBiomes() { public List<PlatformBiome> allBiomes() {
Registry<Biome> registry = biomeRegistry(); Registry<Biome> registry = biomeRegistry();
@@ -152,11 +152,21 @@ public final class ModdedDimensionManager {
} }
public static Handle createPersistent(MinecraftServer server, String dimensionId, String pack, String packDimensionKey, long seed) { 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)); ModdedDimensionRegistryStore.put(server, new ModdedDimensionRegistryStore.PersistentDimension(dimensionId, pack, packDimensionKey, seed));
try { try {
return create(server, dimensionId, pack, packDimensionKey, seed); return create(server, dimensionId, pack, packDimensionKey, seed);
} catch (Throwable e) { } 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) { if (e instanceof RuntimeException runtimeException) {
throw runtimeException; throw runtimeException;
} }
@@ -168,9 +178,21 @@ public final class ModdedDimensionManager {
} }
public static boolean removePersistent(MinecraftServer server, String dimensionId, boolean wipeStorage) { 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); 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) { public static boolean remove(MinecraftServer server, String dimensionId, boolean wipeStorage) {
@@ -273,10 +295,10 @@ public final class ModdedDimensionManager {
private static Holder<DimensionType> resolveDimensionType(RegistryAccess registryAccess, String pack, String packDimensionKey) { private static Holder<DimensionType> resolveDimensionType(RegistryAccess registryAccess, String pack, String packDimensionKey) {
Registry<DimensionType> registry = registryAccess.lookupOrThrow(Registries.DIMENSION_TYPE); Registry<DimensionType> registry = registryAccess.lookupOrThrow(Registries.DIMENSION_TYPE);
IrisDimension dimension = loadPackDimension(pack, packDimensionKey); IrisDimension dimension = loadPackDimension(pack, packDimensionKey);
String typeRef = ModdedForcedDatapack.dimensionTypeRef(dimension); String typeRef = ModdedWorldgenIds.dimensionTypeRef(pack, packDimensionKey);
ResourceKey<DimensionType> typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, Identifier.parse(typeRef)); ResourceKey<DimensionType> typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, Identifier.parse(typeRef));
ModdedRuntimeRegistry.ensureCustomBiomes(registryAccess, dimension, pack); ModdedRuntimeRegistry.ensureCustomBiomes(registryAccess, dimension, pack);
ModdedRuntimeRegistry.ensureDimensionType(registryAccess, registry, typeKey, typeRef, dimension); ModdedRuntimeRegistry.ensureDimensionType(registry, typeKey, typeRef);
return ModdedForcedDatapack.requireRegisteredDimensionType( return ModdedForcedDatapack.requireRegisteredDimensionType(
typeRef, registry.get(typeKey), pack, packDimensionKey); typeRef, registry.get(typeKey), pack, packDimensionKey);
} }
@@ -26,10 +26,13 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
@@ -43,7 +46,10 @@ public final class ModdedDimensionRegistryStore {
} }
public static List<PersistentDimension> load(MinecraftServer server) { public static List<PersistentDimension> load(MinecraftServer server) {
Path file = storeFile(server); return load(storeFile(server));
}
static List<PersistentDimension> load(Path file) {
if (!Files.isRegularFile(file)) { if (!Files.isRegularFile(file)) {
return new ArrayList<>(); return new ArrayList<>();
} }
@@ -51,34 +57,39 @@ public final class ModdedDimensionRegistryStore {
JSONObject root = new JSONObject(Files.readString(file, StandardCharsets.UTF_8)); JSONObject root = new JSONObject(Files.readString(file, StandardCharsets.UTF_8));
JSONArray entries = root.optJSONArray("dimensions"); JSONArray entries = root.optJSONArray("dimensions");
if (entries == null) { if (entries == null) {
return new ArrayList<>(); throw new IllegalArgumentException("registry root has no dimensions array");
} }
Map<String, PersistentDimension> deduplicated = new LinkedHashMap<>(); Map<String, PersistentDimension> deduplicated = new LinkedHashMap<>();
for (int index = 0; index < entries.length(); index++) { for (int index = 0; index < entries.length(); index++) {
JSONObject entry = entries.getJSONObject(index); try {
String id = entry.optString("id", null); JSONObject entry = entries.getJSONObject(index);
if (id == null) { String id = required(entry, "id", index, file);
continue; 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()); return new ArrayList<>(deduplicated.values());
} catch (RuntimeException | IOException e) { } catch (RuntimeException | IOException e) {
LOGGER.error("Iris persistent dimension registry at {} is invalid; ignoring it", file, e); throw new IllegalStateException("Iris persistent dimension registry at " + file
return new ArrayList<>(); + " 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) { public static synchronized void put(MinecraftServer server, PersistentDimension dimension) {
Map<String, PersistentDimension> current = index(load(server)); Map<String, PersistentDimension> current = index(load(server));
current.put(dimension.id(), dimension); current.put(dimension.id(), dimension);
@@ -100,8 +111,19 @@ public final class ModdedDimensionRegistryStore {
return map; 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<PersistentDimension> dimensions) { private static void write(MinecraftServer server, List<PersistentDimension> dimensions) {
Path file = storeFile(server); write(storeFile(server), dimensions);
}
static void write(Path file, List<PersistentDimension> dimensions) {
JSONArray entries = new JSONArray(); JSONArray entries = new JSONArray();
for (PersistentDimension dimension : dimensions) { for (PersistentDimension dimension : dimensions) {
JSONObject entry = new JSONObject(); JSONObject entry = new JSONObject();
@@ -113,13 +135,29 @@ public final class ModdedDimensionRegistryStore {
} }
JSONObject root = new JSONObject(); JSONObject root = new JSONObject();
root.put("dimensions", entries); root.put("dimensions", entries);
Path temp = file.resolveSibling(FILE_NAME + ".tmp");
try { try {
Files.createDirectories(file.getParent()); Files.createDirectories(file.getParent());
Path temp = file.resolveSibling(FILE_NAME + ".tmp");
Files.writeString(temp, root.toString(2), StandardCharsets.UTF_8); 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) { } 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);
} }
} }
@@ -47,13 +47,19 @@ public final class ModdedDimensionStorage {
public static void wipe(MinecraftServer server, ResourceKey<Level> dimension) { public static void wipe(MinecraftServer server, ResourceKey<Level> dimension) {
File storageFolder = storageFolder(server, dimension); File storageFolder = storageFolder(server, dimension);
for (String folder : CHUNK_DATA_FOLDERS) { try {
deleteRecursively(new File(storageFolder, folder).toPath()); 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()); 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)) { if (!Files.exists(root)) {
return; return;
} }
@@ -61,8 +67,6 @@ public final class ModdedDimensionStorage {
for (Path path : walk.sorted(Comparator.reverseOrder()).toList()) { for (Path path : walk.sorted(Comparator.reverseOrder()).toList()) {
Files.deleteIfExists(path); Files.deleteIfExists(path);
} }
} catch (IOException e) {
LOGGER.error("Iris failed to wipe dimension storage at {}", root, e);
} }
} }
} }
@@ -262,7 +262,10 @@ public final class ModdedEngineBootstrap {
selfTest(moddedLoader.getClass().getClassLoader()); selfTest(moddedLoader.getClass().getClassLoader());
bind(); bind();
IrisLanguage.initialize(); IrisLanguage.initialize();
MainWorldService.reconcileEarly(); ModdedStartup.prefetchDefaultPack();
if (!moddedLoader.clientEnvironment()) {
MainWorldService.reconcileEarly();
}
chunkGeneratorRegistration.run(); chunkGeneratorRegistration.run();
ModdedIrisLog.info("Iris chunk generator registered as irisworldgen:iris"); ModdedIrisLog.info("Iris chunk generator registered as irisworldgen:iris");
armParityProbe(); armParityProbe();
@@ -23,6 +23,9 @@ import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.RuntimeUiMessages; import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.iris.core.nms.datapack.DataVersion; import art.arcane.iris.core.nms.datapack.DataVersion;
import art.arcane.iris.core.nms.datapack.IDataFixer; 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.IrisDimension;
import art.arcane.iris.engine.object.IrisDimensionType; import art.arcane.iris.engine.object.IrisDimensionType;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
@@ -80,8 +83,17 @@ public final class ModdedForcedDatapack {
} }
private static Pack buildPack() { private static Pack buildPack() {
Path directory = regenerate(); try {
return requireReadablePack(directory); 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) { private static Pack requireReadablePack(Path directory) {
@@ -138,9 +150,6 @@ public final class ModdedForcedDatapack {
} }
private static void writeStagedPack(Path stagingDirectory) throws IOException { private static void writeStagedPack(Path stagingDirectory) throws IOException {
File packFolder = stagingDirectory.toFile();
KList<File> folders = new KList<>();
folders.add(packFolder);
Map<String, KSet<String>> seenBiomes = new LinkedHashMap<>(); Map<String, KSet<String>> seenBiomes = new LinkedHashMap<>();
IDataFixer fixer = DataVersion.getLatest().get(); IDataFixer fixer = DataVersion.getLatest().get();
@@ -154,7 +163,7 @@ public final class ModdedForcedDatapack {
if (packs != null) { if (packs != null) {
Arrays.sort(packs, Comparator.comparing(File::getName)); Arrays.sort(packs, Comparator.comparing(File::getName));
for (File pack : packs) { for (File pack : packs) {
if (installPack(pack, fixer, folders, seenBiomes, presetIds)) { if (stagePack(pack, fixer, stagingDirectory, seenBiomes, presetIds)) {
packCount++; packCount++;
} }
} }
@@ -173,6 +182,88 @@ public final class ModdedForcedDatapack {
} }
} }
private static boolean stagePack(File sourcePack, IDataFixer fixer, Path stagingDirectory,
Map<String, KSet<String>> seenBiomes,
KList<String> 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<String, KSet<String>> packBiomes = new LinkedHashMap<>();
KList<String> packPresetIds = new KList<>();
KList<File> 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<Path> entries = new ArrayList<>();
try (Stream<Path> 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<String, KSet<String>> destination,
Map<String, KSet<String>> source) {
for (Map.Entry<String, KSet<String>> entry : source.entrySet()) {
destination.computeIfAbsent(entry.getKey(), ignored -> new KSet<>())
.addAll(entry.getValue());
}
}
private static boolean installPack(File packFolder, IDataFixer fixer, KList<File> folders, private static boolean installPack(File packFolder, IDataFixer fixer, KList<File> folders,
Map<String, KSet<String>> seenBiomes, Map<String, KSet<String>> seenBiomes,
KList<String> presetIds) throws IOException { KList<String> presetIds) throws IOException {
@@ -198,12 +289,15 @@ public final class ModdedForcedDatapack {
throw new IllegalStateException("Iris pack '" + packName + "' dimension '" throw new IllegalStateException("Iris pack '" + packName + "' dimension '"
+ dimensionKey + "' did not load while building the forced datapack"); + 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, dimension.installBiomes(fixer, () -> data, folders,
biomesForNamespace(seenBiomes, dimension.getLoadKey())); biomesForNamespace(seenBiomes, dimension.getLoadKey()));
writeDimensionType(folders, fixer, dimension); writeDimensionType(folders, fixer, dimension, packName, dimensionKey);
String presetKey = dimensionKey.equals(packName) ? packName : packName + "_" + dimensionKey; String presetRef = ModdedWorldgenIds.presetRef(packName, dimensionKey);
writeWorldPreset(folders, dimension, packName, dimensionKey, presetKey); writeWorldPreset(folders, packName, dimensionKey, presetRef);
presetIds.add("irisworldgen:" + presetKey); presetIds.add(presetRef);
} }
return true; return true;
} }
@@ -212,10 +306,6 @@ public final class ModdedForcedDatapack {
return biomes.computeIfAbsent(namespace, ignored -> new KSet<>()); return biomes.computeIfAbsent(namespace, ignored -> new KSet<>());
} }
public static String dimensionTypeRef(IrisDimension dimension) {
return "irisworldgen:" + dimension.getDimensionTypeKey();
}
static <T> T requireRegisteredDimensionType(String typeRef, Optional<T> registeredType, static <T> T requireRegisteredDimensionType(String typeRef, Optional<T> registeredType,
String pack, String packDimensionKey) { String pack, String packDimensionKey) {
return registeredType.orElseThrow(() -> new IllegalStateException( 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.")); + packDimensionKey + "' is not loaded. Restart the server so the forced Iris datapack registers it before creating the world."));
} }
private static void writeWorldPreset(KList<File> folders, IrisDimension dimension, String packName, String dimensionKey, String presetKey) throws IOException { private static void writeWorldPreset(KList<File> folders, String packName, String dimensionKey,
String presetRef) throws IOException {
String dimensionRef = dimensionKey.equals(packName) ? packName : packName + ":" + dimensionKey; 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) { 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.createDirectories(output.getParent());
Files.writeString(output, json, StandardCharsets.UTF_8); 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); Files.writeString(output, json, StandardCharsets.UTF_8);
} }
static void writeDimensionType(KList<File> folders, IDataFixer fixer, IrisDimension dimension) throws IOException { static void writeDimensionType(KList<File> folders, IDataFixer fixer, IrisDimension dimension,
String pack, String packDimensionKey) throws IOException {
IrisDimensionType type = dimension.getDimensionType(); IrisDimensionType type = dimension.getDimensionType();
String json = type.toJson(fixer); 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) { 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.createDirectories(output.getParent());
Files.writeString(output, json, StandardCharsets.UTF_8); 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);
}
} }
} }
@@ -28,6 +28,7 @@ import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -35,11 +36,13 @@ public final class ModdedPackInstaller {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Pattern PACK_NAME = Pattern.compile("[a-z0-9_-]+"); 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 Pattern BRANCH_NAME = Pattern.compile("[A-Za-z0-9._-]+");
private static final ConcurrentHashMap<String, Object> INSTALL_LOCKS = new ConcurrentHashMap<>();
private ModdedPackInstaller() { private ModdedPackInstaller() {
} }
public static boolean install(Path configDir, String pack, String branch, Consumer<String> feedback) { public static boolean install(Path configDir, String pack, String branch,
boolean forceOverwrite, Consumer<String> feedback) {
if (pack == null || !PACK_NAME.matcher(pack).matches()) { if (pack == null || !PACK_NAME.matcher(pack).matches()) {
feedback.accept(IrisLanguage.plain(PackDownloadMessages.INVALID_PACK_NAME, MessageArgument.untrusted("pack", String.valueOf(pack)))); feedback.accept(IrisLanguage.plain(PackDownloadMessages.INVALID_PACK_NAME, MessageArgument.untrusted("pack", String.valueOf(pack))));
return false; return false;
@@ -49,20 +52,31 @@ public final class ModdedPackInstaller {
return false; return false;
} }
File packs = configDir.resolve("irisworldgen").resolve("packs").toFile(); Object installLock = INSTALL_LOCKS.computeIfAbsent(pack, key -> new Object());
try { synchronized (installLock) {
if (PackDownloader.isDefaultOverworld(pack)) { File packs = configDir.resolve("irisworldgen").resolve("packs").toFile();
return PackDownloader.downloadDefaultOverworld(packs, true, feedback) != null; 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;
} }
} }
} }
@@ -48,10 +48,10 @@ public final class ModdedPlatform implements IrisPlatform {
public ModdedPlatform(ModdedLoader loader) { public ModdedPlatform(ModdedLoader loader) {
this.loader = loader; this.loader = loader;
this.registries = new ModdedRegistries(loader::currentServer); this.registries = new ModdedRegistries(ModdedEngineBootstrap::currentServer);
this.scheduler = new ModdedScheduler(); this.scheduler = new ModdedScheduler();
this.structureHooks = new ModdedStructureHooks(loader::currentServer); this.structureHooks = new ModdedStructureHooks(ModdedEngineBootstrap::currentServer);
this.biomeWriter = new ModdedBiomeWriter(loader::currentServer); this.biomeWriter = new ModdedBiomeWriter(ModdedEngineBootstrap::currentServer);
} }
public static void errorSink(Consumer<Throwable> sink) { public static void errorSink(Consumer<Throwable> sink) {
@@ -63,7 +63,7 @@ public final class ModdedPlatform implements IrisPlatform {
} }
public MinecraftServer server() { public MinecraftServer server() {
return loader.currentServer(); return ModdedEngineBootstrap.currentServer();
} }
public ModdedScheduler moddedScheduler() { public ModdedScheduler moddedScheduler() {
@@ -137,7 +137,7 @@ public final class ModdedPlatform implements IrisPlatform {
@Override @Override
public void dispatchConsoleCommand(String command) { public void dispatchConsoleCommand(String command) {
ModdedServerCommands.dispatch(loader.currentServer(), command); ModdedServerCommands.dispatch(ModdedEngineBootstrap.currentServer(), command);
} }
@Override @Override
@@ -64,6 +64,7 @@ public final class ModdedProtocolHandler {
} }
SESSION_ENGINES.clear(); SESSION_ENGINES.clear();
SESSION_LEVELS.clear(); SESSION_LEVELS.clear();
dimensionSyncTicks = 0;
IrisSessionRegistry sessionRegistry = new IrisSessionRegistry(); IrisSessionRegistry sessionRegistry = new IrisSessionRegistry();
ModdedProtocolTransport serverTransport = new ModdedProtocolTransport(server, boundChannel); ModdedProtocolTransport serverTransport = new ModdedProtocolTransport(server, boundChannel);
IrisProtocolServer protocol = new IrisProtocolServer(sessionRegistry, SERVER_CAPABILITIES, brand(), true); IrisProtocolServer protocol = new IrisProtocolServer(sessionRegistry, SERVER_CAPABILITIES, brand(), true);
@@ -95,6 +96,7 @@ public final class ModdedProtocolHandler {
} }
SESSION_ENGINES.clear(); SESSION_ENGINES.clear();
SESSION_LEVELS.clear(); SESSION_LEVELS.clear();
dimensionSyncTicks = 0;
registry = null; registry = null;
protocolServer = null; protocolServer = null;
transport = null; transport = null;
@@ -19,53 +19,35 @@
package art.arcane.iris.modded; package art.arcane.iris.modded;
import art.arcane.iris.core.loader.IrisData; 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.IrisBiome;
import art.arcane.iris.engine.object.IrisBiomeCustom; import art.arcane.iris.engine.object.IrisBiomeCustom;
import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.util.common.data.DataProvider; 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.Registry;
import net.minecraft.core.RegistryAccess; import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries; import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier; import net.minecraft.resources.Identifier;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.dimension.DimensionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.Locale; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
public final class ModdedRuntimeRegistry { public final class ModdedRuntimeRegistry {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Object LOCK = new Object();
private ModdedRuntimeRegistry() { private ModdedRuntimeRegistry() {
} }
static void ensureDimensionType(RegistryAccess registryAccess, Registry<DimensionType> registry, static void ensureDimensionType(Registry<DimensionType> registry,
ResourceKey<DimensionType> typeKey, String typeRef, IrisDimension dimension) { ResourceKey<DimensionType> typeKey, String typeRef) {
if (registry.get(typeKey).isPresent()) { if (registry.get(typeKey).isPresent()) {
return; return;
} }
IDataFixer fixer = DataVersion.getLatest().get(); throw new IllegalStateException("Iris dimension type '" + typeRef
String json = dimension.getDimensionType().toJson(fixer); + "' is not synchronized. Restart after installing the pack before creating its world.");
DimensionType type = decode(registryAccess, DimensionType.DIRECT_CODEC, json, typeRef);
registerIntoFrozen(registry, typeKey, type, typeRef);
LOGGER.info("Iris registered runtime dimension type '{}'", typeRef);
} }
static void ensureCustomBiomes(RegistryAccess registryAccess, IrisDimension dimension, String pack) { static void ensureCustomBiomes(RegistryAccess registryAccess, IrisDimension dimension, String pack) {
@@ -76,10 +58,8 @@ public final class ModdedRuntimeRegistry {
Registry<Biome> registry = registryAccess.lookupOrThrow(Registries.BIOME); Registry<Biome> registry = registryAccess.lookupOrThrow(Registries.BIOME);
IrisData data = IrisData.get(packFolder); IrisData data = IrisData.get(packFolder);
DataProvider provider = () -> data; DataProvider provider = () -> data;
IDataFixer fixer = DataVersion.getLatest().get();
String namespace = dimension.getLoadKey().toLowerCase(Locale.ROOT);
Set<String> seen = new HashSet<>(); Set<String> seen = new HashSet<>();
int registered = 0; List<String> missing = new ArrayList<>();
for (IrisBiome irisBiome : dimension.getAllBiomes(provider)) { for (IrisBiome irisBiome : dimension.getAllBiomes(provider)) {
if (!irisBiome.isCustom()) { if (!irisBiome.isCustom()) {
continue; continue;
@@ -89,48 +69,17 @@ public final class ModdedRuntimeRegistry {
if (!seen.add(biomeId)) { if (!seen.add(biomeId)) {
continue; continue;
} }
String biomeRef = namespace + ":" + biomeId; String biomeRef = ModdedWorldgenIds.biomeRef(pack, dimension.getLoadKey(), biomeId);
ResourceKey<Biome> biomeKey = ResourceKey.create(Registries.BIOME, Identifier.parse(biomeRef)); ResourceKey<Biome> biomeKey = ResourceKey.create(Registries.BIOME, Identifier.parse(biomeRef));
if (registry.get(biomeKey).isPresent()) { if (registry.get(biomeKey).isEmpty()) {
continue; missing.add(biomeRef);
}
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> T decode(RegistryAccess registryAccess, Codec<T> codec, String json, String ref) {
JsonElement element = JsonParser.parseString(json);
RegistryOps<JsonElement> 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 <T> Holder.Reference<T> registerIntoFrozen(Registry<T> registry, ResourceKey<T> key, T value, String ref) {
if (!(registry instanceof MappedRegistry<T> mapped)) {
throw new IllegalStateException("Iris cannot register '" + ref + "' at runtime: "
+ registry.getClass().getName() + " is not a MappedRegistry");
}
synchronized (LOCK) {
Optional<Holder.Reference<T>> 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 (!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());
}
} }
} }
@@ -90,7 +90,6 @@ public final class ModdedServiceManager {
if (!enabled) { if (!enabled) {
return; return;
} }
enabled = false;
Throwable failure = null; Throwable failure = null;
ModdedService[] ordered = services.values().toArray(new ModdedService[0]); ModdedService[] ordered = services.values().toArray(new ModdedService[0]);
for (int i = ordered.length - 1; i >= 0; i--) { for (int i = ordered.length - 1; i >= 0; i--) {
@@ -109,6 +108,7 @@ public final class ModdedServiceManager {
if (failure != null) { if (failure != null) {
throw new IllegalStateException("One or more Iris services failed to disable", failure); throw new IllegalStateException("One or more Iris services failed to disable", failure);
} }
enabled = false;
} }
synchronized void rollback(Throwable failure) { synchronized void rollback(Throwable failure) {
@@ -95,10 +95,9 @@ public final class ModdedStartup {
PackValidationResult result = PackValidator.validate(packDir); PackValidationResult result = PackValidator.validate(packDir);
PackValidationRegistry.publish(result); PackValidationRegistry.publish(result);
if (!result.isLoadable()) { if (!result.isLoadable()) {
LOGGER.error("Iris pack '{}' FAILED validation - world/studio creation will be refused. Reasons:", result.getPackName()); LOGGER.error("Iris pack '{}' FAILED validation with {} blocking error(s); world/studio creation will be refused. First error: {}",
for (String reason : result.getBlockingErrors()) { result.getPackName(), result.getBlockingErrors().size(),
LOGGER.error(" - {}", reason); result.getBlockingErrors().getFirst());
}
} else if (!result.getWarnings().isEmpty()) { } else if (!result.getWarnings().isEmpty()) {
LOGGER.info("Iris pack '{}' validated ({} warning(s)).", result.getPackName(), result.getWarnings().size()); LOGGER.info("Iris pack '{}' validated ({} warning(s)).", result.getPackName(), result.getWarnings().size());
for (String warning : result.getWarnings()) { for (String warning : result.getWarnings()) {
@@ -168,8 +167,6 @@ public final class ModdedStartup {
if (e instanceof Error fatalError) { if (e instanceof Error fatalError) {
throw 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); 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()) { if (new File(packFolder, "dimensions/" + pack + ".json").isFile()) {
return; 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); 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) { if (!installed) {
LOGGER.warn("Iris default pack '{}' could not be downloaded; install it with /iris download {}", pack, pack); LOGGER.warn("Iris default pack '{}' could not be downloaded; install it with /iris download {}", pack, pack);
} }
@@ -111,7 +111,7 @@ public final class ModdedWorldEngines {
} }
long seed = seedOverride == Long.MIN_VALUE ? level.getSeed() : seedOverride; 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(); File worldFolder = DimensionType.getStorageFolder(level.dimension(), level.getServer().getWorldPath(LevelResource.ROOT)).toFile();
IrisWorld world = IrisWorld.builder() IrisWorld world = IrisWorld.builder()
.platformIdentity(level.dimension().identifier().toString()) .platformIdentity(level.dimension().identifier().toString())
@@ -139,12 +139,21 @@ public final class ModdedWorldEngines {
return engine; 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(); DimensionType actualType = level.dimensionType();
String actualTypeKey = level.dimensionTypeRegistration().unwrapKey() String actualTypeKey = level.dimensionTypeRegistration().unwrapKey()
.map(key -> key.identifier().toString()) .map(key -> key.identifier().toString())
.orElse("<unregistered>"); .orElse("<unregistered>");
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( IrisDimensionRuntimeContract actual = new IrisDimensionRuntimeContract(
actualTypeKey, actualTypeKey,
actualType.minY(), actualType.minY(),
@@ -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);
}
}
@@ -33,22 +33,21 @@ import art.arcane.iris.engine.framework.WrongEngineBroException;
import art.arcane.iris.engine.object.IrisBiome; import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisNativeStructureDecision; import art.arcane.iris.engine.object.IrisNativeStructureDecision;
import art.arcane.iris.engine.object.NativeStructureGenerationStatus; 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.engine.object.IrisRegion;
import art.arcane.iris.modded.IrisModdedChunkGenerator; import art.arcane.iris.modded.IrisModdedChunkGenerator;
import art.arcane.iris.modded.ModdedBlockState;
import art.arcane.iris.modded.ModdedDimensionManager; import art.arcane.iris.modded.ModdedDimensionManager;
import art.arcane.iris.modded.ModdedEngineBootstrap; import art.arcane.iris.modded.ModdedEngineBootstrap;
import art.arcane.iris.modded.ModdedLoader; import art.arcane.iris.modded.ModdedLoader;
import art.arcane.iris.modded.ModdedPackInstaller; 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.IrisLogging;
import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.project.context.IrisContext; import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.volmlib.util.collection.KMap; import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.math.Position2; import art.arcane.volmlib.util.math.Position2;
import art.arcane.volmlib.util.matter.MatterMarker;
import com.mojang.datafixers.util.Pair; import com.mojang.datafixers.util.Pair;
import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.LongArgumentType; import com.mojang.brigadier.arguments.LongArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
@@ -70,7 +69,6 @@ import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder; import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet; import net.minecraft.core.HolderSet;
import net.minecraft.core.Registry; import net.minecraft.core.Registry;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries; import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component; 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.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Relative; 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.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.Structure; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -123,8 +117,6 @@ public final class IrisModdedCommands {
private static final SuggestionProvider<CommandSourceStack> OBJECT_KEYS = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestObjectKeys(context, builder); private static final SuggestionProvider<CommandSourceStack> OBJECT_KEYS = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestObjectKeys(context, builder);
private static final SuggestionProvider<CommandSourceStack> STRUCTURE_KEYS = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestStructureKeys(context, builder); private static final SuggestionProvider<CommandSourceStack> STRUCTURE_KEYS = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestStructureKeys(context, builder);
private static final SuggestionProvider<CommandSourceStack> POI_TYPES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> SharedSuggestionProvider.suggest(List.of("buried_treasure"), builder); private static final SuggestionProvider<CommandSourceStack> POI_TYPES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> SharedSuggestionProvider.suggest(List.of("buried_treasure"), builder);
private static final SuggestionProvider<CommandSourceStack> MARKER_TYPES = (CommandContext<CommandSourceStack> 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<CommandSourceStack> PACK_NAMES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestPackNames(context, builder); static final SuggestionProvider<CommandSourceStack> PACK_NAMES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestPackNames(context, builder);
private static final SuggestionProvider<CommandSourceStack> DIMENSION_NAMES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestDimensionNames(context, builder); private static final SuggestionProvider<CommandSourceStack> DIMENSION_NAMES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestDimensionNames(context, builder);
@@ -152,21 +144,10 @@ public final class IrisModdedCommands {
.then(Commands.argument("dimension", StringArgumentType.greedyString()).suggests(DIMENSION_NAMES) .then(Commands.argument("dimension", StringArgumentType.greedyString()).suggests(DIMENSION_NAMES)
.executes((CommandContext<CommandSourceStack> context) -> info(context.getSource(), StringArgumentType.getString(context, "dimension"))))); .executes((CommandContext<CommandSourceStack> context) -> info(context.getSource(), StringArgumentType.getString(context, "dimension")))));
root.then(Commands.literal("what").requires(GATE) root.then(ModdedWhatCommands.tree());
.executes((CommandContext<CommandSourceStack> context) -> what(context.getSource()))
.then(Commands.literal("block")
.executes((CommandContext<CommandSourceStack> context) -> whatBlock(context.getSource())))
.then(Commands.literal("hand")
.executes((CommandContext<CommandSourceStack> context) -> whatHand(context.getSource())))
.then(Commands.literal("markers")
.then(Commands.argument("marker", StringArgumentType.greedyString()).suggests(MARKER_TYPES)
.executes((CommandContext<CommandSourceStack> context) -> whatMarkers(context.getSource(), StringArgumentType.getString(context, "marker"))))));
root.then(Commands.literal("tp").requires(GATE) root.then(teleportTree("teleport"));
.then(Commands.argument("dimension", DimensionArgument.dimension()).suggests(DIMENSION_NAMES) root.then(teleportTree("tp"));
.executes((CommandContext<CommandSourceStack> context) -> tp(context.getSource(), DimensionArgument.getDimension(context, "dimension"), null))
.then(Commands.argument("player", EntityArgument.player())
.executes((CommandContext<CommandSourceStack> context) -> tp(context.getSource(), DimensionArgument.getDimension(context, "dimension"), EntityArgument.getPlayer(context, "player"))))));
root.then(Commands.literal("evacuate").requires(GATE) root.then(Commands.literal("evacuate").requires(GATE)
.executes((CommandContext<CommandSourceStack> context) -> evacuate(context.getSource(), null)) .executes((CommandContext<CommandSourceStack> context) -> evacuate(context.getSource(), null))
@@ -178,6 +159,12 @@ public final class IrisModdedCommands {
root.then(Commands.literal("reload").requires(GATE) root.then(Commands.literal("reload").requires(GATE)
.executes((CommandContext<CommandSourceStack> context) -> reload(context.getSource()))); .executes((CommandContext<CommandSourceStack> context) -> reload(context.getSource())));
root.then(Commands.literal("height").requires(GATE)
.executes((CommandContext<CommandSourceStack> context) -> height(context.getSource())));
root.then(Commands.literal("worlds").requires(GATE)
.executes((CommandContext<CommandSourceStack> context) -> info(context.getSource(), null)));
root.then(Commands.literal("accesslist").requires(GATE)
.executes((CommandContext<CommandSourceStack> context) -> info(context.getSource(), null)));
root.then(gotoTree("goto")); root.then(gotoTree("goto"));
root.then(gotoTree("find")); root.then(gotoTree("find"));
@@ -202,11 +189,16 @@ public final class IrisModdedCommands {
root.then(Commands.literal("wand").requires(GATE) root.then(Commands.literal("wand").requires(GATE)
.executes((CommandContext<CommandSourceStack> context) -> ModdedObjectCommands.giveWand(context.getSource()))); .executes((CommandContext<CommandSourceStack> context) -> ModdedObjectCommands.giveWand(context.getSource())));
root.then(Commands.literal("dust").requires(GATE)
.executes((CommandContext<CommandSourceStack> context) -> ModdedObjectCommands.giveDust(context.getSource())));
root.then(Commands.literal("d").requires(GATE)
.executes((CommandContext<CommandSourceStack> context) -> ModdedObjectCommands.giveDust(context.getSource())));
root.then(ModdedObjectCommands.tree("object")); root.then(ModdedObjectCommands.tree("object"));
root.then(ModdedObjectCommands.tree("o")); root.then(ModdedObjectCommands.tree("o"));
root.then(editTree()); root.then(editTree());
root.then(createTree()); root.then(createTree("create"));
root.then(createTree("c"));
root.then(ModdedStudioCommands.tree("studio")); root.then(ModdedStudioCommands.tree("studio"));
root.then(ModdedStudioCommands.tree("std")); root.then(ModdedStudioCommands.tree("std"));
@@ -227,9 +219,15 @@ public final class IrisModdedCommands {
return root; return root;
} }
private static LiteralArgumentBuilder<CommandSourceStack> createTree() { private static LiteralArgumentBuilder<CommandSourceStack> createTree(String name) {
return Commands.literal("create").requires(GATE) return Commands.literal(name).requires(GATE)
.then(Commands.argument("name", StringArgumentType.word()) .then(Commands.argument("name", StringArgumentType.word())
.executes((CommandContext<CommandSourceStack> context) ->
ModdedWorldCommands.createWorld(
context.getSource(),
StringArgumentType.getString(context, "name"),
"overworld",
1337L))
.then(Commands.argument("pack", StringArgumentType.string()).suggests(PACK_NAMES) .then(Commands.argument("pack", StringArgumentType.string()).suggests(PACK_NAMES)
.executes((CommandContext<CommandSourceStack> context) -> ModdedWorldCommands.createWorld(context.getSource(), .executes((CommandContext<CommandSourceStack> context) -> ModdedWorldCommands.createWorld(context.getSource(),
StringArgumentType.getString(context, "name"), StringArgumentType.getString(context, "name"),
@@ -242,6 +240,17 @@ public final class IrisModdedCommands {
LongArgumentType.getLong(context, "seed")))))); LongArgumentType.getLong(context, "seed"))))));
} }
private static LiteralArgumentBuilder<CommandSourceStack> teleportTree(String name) {
return Commands.literal(name).requires(GATE)
.then(Commands.argument("dimension", DimensionArgument.dimension()).suggests(DIMENSION_NAMES)
.executes((CommandContext<CommandSourceStack> context) ->
tp(context.getSource(), DimensionArgument.getDimension(context, "dimension"), null))
.then(Commands.argument("player", EntityArgument.player())
.executes((CommandContext<CommandSourceStack> context) ->
tp(context.getSource(), DimensionArgument.getDimension(context, "dimension"),
EntityArgument.getPlayer(context, "player")))));
}
private static LiteralArgumentBuilder<CommandSourceStack> helpTree() { private static LiteralArgumentBuilder<CommandSourceStack> helpTree() {
return Commands.literal("help") return Commands.literal("help")
.executes((CommandContext<CommandSourceStack> context) -> ModdedCommandHelp.send(context.getSource(), "")) .executes((CommandContext<CommandSourceStack> context) -> ModdedCommandHelp.send(context.getSource(), ""))
@@ -252,9 +261,34 @@ public final class IrisModdedCommands {
private static LiteralArgumentBuilder<CommandSourceStack> downloadTree(String name) { private static LiteralArgumentBuilder<CommandSourceStack> downloadTree(String name) {
return Commands.literal(name).requires(GATE) return Commands.literal(name).requires(GATE)
.then(Commands.argument("pack", StringArgumentType.word()).suggests(PACK_NAMES) .then(Commands.argument("pack", StringArgumentType.word()).suggests(PACK_NAMES)
.executes((CommandContext<CommandSourceStack> context) -> download(context.getSource(), StringArgumentType.getString(context, "pack"), "stable")) .executes((CommandContext<CommandSourceStack> context) ->
download(context.getSource(),
StringArgumentType.getString(context, "pack"), "stable", false))
.then(Commands.literal("force")
.executes((CommandContext<CommandSourceStack> context) ->
download(context.getSource(),
StringArgumentType.getString(context, "pack"), "stable", true)))
.then(Commands.argument("overwrite", BoolArgumentType.bool())
.executes((CommandContext<CommandSourceStack> context) ->
download(context.getSource(),
StringArgumentType.getString(context, "pack"), "stable",
BoolArgumentType.getBool(context, "overwrite"))))
.then(Commands.argument("branch", StringArgumentType.word()) .then(Commands.argument("branch", StringArgumentType.word())
.executes((CommandContext<CommandSourceStack> context) -> download(context.getSource(), StringArgumentType.getString(context, "pack"), StringArgumentType.getString(context, "branch"))))); .executes((CommandContext<CommandSourceStack> context) ->
download(context.getSource(),
StringArgumentType.getString(context, "pack"),
StringArgumentType.getString(context, "branch"), false))
.then(Commands.literal("force")
.executes((CommandContext<CommandSourceStack> context) ->
download(context.getSource(),
StringArgumentType.getString(context, "pack"),
StringArgumentType.getString(context, "branch"), true)))
.then(Commands.argument("overwrite", BoolArgumentType.bool())
.executes((CommandContext<CommandSourceStack> context) ->
download(context.getSource(),
StringArgumentType.getString(context, "pack"),
StringArgumentType.getString(context, "branch"),
BoolArgumentType.getBool(context, "overwrite"))))));
} }
private static LiteralArgumentBuilder<CommandSourceStack> metricsTree(String name) { private static LiteralArgumentBuilder<CommandSourceStack> metricsTree(String name) {
@@ -380,11 +414,21 @@ public final class IrisModdedCommands {
.executes((CommandContext<CommandSourceStack> context) -> editBiome(context.getSource(), null)) .executes((CommandContext<CommandSourceStack> context) -> editBiome(context.getSource(), null))
.then(Commands.argument("key", StringArgumentType.greedyString()).suggests(BIOME_KEYS) .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(BIOME_KEYS)
.executes((CommandContext<CommandSourceStack> context) -> editBiome(context.getSource(), StringArgumentType.getString(context, "key"))))) .executes((CommandContext<CommandSourceStack> context) -> editBiome(context.getSource(), StringArgumentType.getString(context, "key")))))
.then(Commands.literal("b")
.executes((CommandContext<CommandSourceStack> context) -> editBiome(context.getSource(), null))
.then(Commands.argument("key", StringArgumentType.greedyString()).suggests(BIOME_KEYS)
.executes((CommandContext<CommandSourceStack> context) -> editBiome(context.getSource(), StringArgumentType.getString(context, "key")))))
.then(Commands.literal("region") .then(Commands.literal("region")
.executes((CommandContext<CommandSourceStack> context) -> editRegion(context.getSource(), null)) .executes((CommandContext<CommandSourceStack> context) -> editRegion(context.getSource(), null))
.then(Commands.argument("key", StringArgumentType.greedyString()).suggests(REGION_KEYS) .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(REGION_KEYS)
.executes((CommandContext<CommandSourceStack> context) -> editRegion(context.getSource(), StringArgumentType.getString(context, "key"))))) .executes((CommandContext<CommandSourceStack> context) -> editRegion(context.getSource(), StringArgumentType.getString(context, "key")))))
.then(Commands.literal("r")
.executes((CommandContext<CommandSourceStack> context) -> editRegion(context.getSource(), null))
.then(Commands.argument("key", StringArgumentType.greedyString()).suggests(REGION_KEYS)
.executes((CommandContext<CommandSourceStack> context) -> editRegion(context.getSource(), StringArgumentType.getString(context, "key")))))
.then(Commands.literal("dimension") .then(Commands.literal("dimension")
.executes((CommandContext<CommandSourceStack> context) -> editDimension(context.getSource())))
.then(Commands.literal("d")
.executes((CommandContext<CommandSourceStack> context) -> editDimension(context.getSource()))); .executes((CommandContext<CommandSourceStack> context) -> editDimension(context.getSource())));
} }
@@ -540,21 +584,18 @@ public final class IrisModdedCommands {
MessageArgument.untrusted("locale", IrisSettings.get().getGeneral().getLanguage()), MessageArgument.untrusted("locale", IrisSettings.get().getGeneral().getLanguage()),
MessageArgument.trusted("activeLocale", IrisLanguage.activeLocale()) MessageArgument.trusted("activeLocale", IrisLanguage.activeLocale())
)); ));
return 1; return 0;
} }
private static int whatHand(CommandSourceStack source) { private static int height(CommandSourceStack source) {
ServerPlayer player = source.getPlayer(); ServerLevel level = source.getLevel();
if (player == null) { IrisModdedCommands.ok(source, IrisLanguage.plain(
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_IT_INSPECTS)); RuntimeUiMessages.WORLD_HEIGHT_RANGE,
return 0; MessageArgument.trusted("minY", level.getMinY()),
} MessageArgument.trusted("maxY", level.getMaxY())));
ItemStack stack = player.getMainHandItem(); IrisModdedCommands.ok(source, IrisLanguage.plain(
if (stack.isEmpty()) { RuntimeUiMessages.WORLD_HEIGHT_TOTAL,
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_YOUR_MAIN_HAND_IS_EMPTY)); MessageArgument.trusted("height", level.getHeight())));
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())));
return 1; return 1;
} }
@@ -662,15 +703,20 @@ public final class IrisModdedCommands {
} }
iris++; iris++;
String dimensionId = level.dimension().identifier().toString(); 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; continue;
} }
Engine engine = irisGenerator.engineIfBound(); Engine engine = irisGenerator.engineIfBound();
if (engine == null) { 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; continue;
} }
lines.add(dimensionId + ": pack=" + engine.getDimension().getLoadKey() lines.add(irisIdentity + ": pack=" + engine.getDimension().getLoadKey()
+ " world=" + dimensionId
+ " seed=" + level.getSeed() + " seed=" + level.getSeed()
+ " height=" + engine.getMinHeight() + ".." + engine.getMaxHeight() + " height=" + engine.getMinHeight() + ".." + engine.getMaxHeight()
+ " generated=" + engine.getGenerated() + " generated=" + engine.getGenerated()
@@ -691,154 +737,6 @@ public final class IrisModdedCommands {
return 1; 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<String> 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<int[]> 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) { private static int gotoBiome(CommandSourceStack source, String key) {
ServerPlayer player = source.getPlayer(); ServerPlayer player = source.getPlayer();
if (player == null) { if (player == null) {
@@ -1253,9 +1151,26 @@ public final class IrisModdedCommands {
int blockX = (at.getX() << 4) + 8; int blockX = (at.getX() << 4) + 8;
int blockZ = (at.getZ() << 4) + 8; int blockZ = (at.getZ() << 4) + 8;
try (GenerationSessionLease lease = engine.acquireGenerationLease("modded_locator_teleport"); 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; int blockY = engine.getMinHeight() + engine.getHeight(blockX, blockZ, false) + 2;
player.teleportTo(level, blockX + 0.5D, blockY, blockZ + 0.5D, Set.<Relative>of(), player.getYRot(), player.getXRot(), false); boolean teleported = player.teleportTo(
level,
blockX + 0.5D,
blockY,
blockZ + 0.5D,
Set.<Relative>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))); 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) { } catch (GenerationSessionException e) {
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_ENGINE_CHANGED_WHILE_LOCATING_TRY_AGAIN, MessageArgument.untrusted("label", label))); 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; return 1;
} }
private static int download(CommandSourceStack source, String pack, String branch) { private static int download(CommandSourceStack source, String pack,
MinecraftServer server = source.getServer(); String branch, boolean forceOverwrite) {
boolean defaultOverworld = PackDownloader.isDefaultOverworld(pack); 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))); ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_DOWNLOADING_IRISDIMENSIONS, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("downloadSource", downloadSource)));
Thread thread = new Thread(() -> { ModdedScheduler scheduler = ModdedEngineBootstrap.schedulerOrNull();
boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), pack, branch, if (scheduler == null) {
(String message) -> server.execute(() -> ok(source, message))); 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) { 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 { } 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; return 1;
} }
@@ -1416,15 +1341,27 @@ public final class IrisModdedCommands {
private static CompletableFuture<Suggestions> suggestPackNames(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) { private static CompletableFuture<Suggestions> suggestPackNames(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
ModdedCommandFeedback.tab(context.getSource()); ModdedCommandFeedback.tab(context.getSource());
List<String> names = new ArrayList<>(); Set<String> names = new TreeSet<>();
names.add("overworld"); names.add("overworld");
try { try {
File packs = ModdedEngineBootstrap.loader().configDir().resolve("irisworldgen").resolve("packs").toFile(); File packs = ModdedEngineBootstrap.loader().configDir().resolve("irisworldgen").resolve("packs").toFile();
File[] children = packs.listFiles(); File[] children = packs.listFiles();
if (children != null) { if (children != null) {
for (File child : children) { for (File child : children) {
if (child.isDirectory() && !names.contains(child.getName())) { if (!child.isDirectory()) {
names.add(child.getName()); 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));
} }
} }
} }
@@ -18,18 +18,18 @@
package art.arcane.iris.modded.command; 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.CommandSourceStack;
import net.minecraft.commands.Commands; import net.minecraft.commands.Commands;
import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.HoverEvent; import net.minecraft.network.chat.HoverEvent;
import net.minecraft.network.chat.MutableComponent; 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.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -60,21 +60,24 @@ final class ModdedCommandHelp {
SECTIONS.put("", List.of( SECTIONS.put("", List.of(
Entry.command("version", "", ModdedHelpMessages.COMMAND_VERSION_PRINT_VERSION_INFORMATION), 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("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.group("find", ModdedHelpMessages.GROUP_FIND_FIND_AND_TELEPORT_TO_IRIS_BIOMES_REGIONS_OBJECTS_IRIS_STRUCTURES_NATIVE_STRUCTURES, "goto"),
Entry.command("tp", "<dimension> [player]", ModdedHelpMessages.COMMAND_TP_TELEPORT_YOURSELF_OR_A_NAMED_PLAYER_INTO_A_LOADED_IRIS_DIMENSION), Entry.command("teleport", "<dimension> [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("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("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("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("reload", "", ModdedHelpMessages.COMMAND_RELOAD_RELOAD_SETTINGS_JSON_ALSO_HOTLOADED_AUTOMATICALLY_EVERY_3S),
Entry.command("download", "<pack> [branch]", ModdedHelpMessages.COMMAND_DOWNLOAD_DOWNLOAD_A_PACK_PROJECT, "dl"), Entry.command("download", "<pack> [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("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.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.group("pregen", ModdedHelpMessages.GROUP_PREGEN_PREGENERATE_AN_IRIS_DIMENSION, "pregenerate"),
Entry.command("wand", "", ModdedHelpMessages.COMMAND_WAND_GET_AN_IRIS_OBJECT_WAND), 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("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.group("edit", ModdedHelpMessages.GROUP_EDIT_OPEN_PACK_BIOME_REGION_AND_DIMENSION_JSON_FILES_IN_YOUR_DESKTOP_EDITOR),
Entry.command("create", "<name> <pack|pack:dimensionKey> [seed]", ModdedHelpMessages.COMMAND_CREATE_CREATE_AND_INJECT_A_PERSISTENT_IRIS_DIMENSION_QUOTE_PACK_DIMENSIONKEY_TO_PICK), Entry.command("create", "<name> [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("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("pack", ModdedHelpMessages.GROUP_PACK_PACK_VALIDATION_AND_MAINTENANCE, "pk"),
Entry.group("world", ModdedHelpMessages.GROUP_WORLD_RUNTIME_IRIS_DIMENSION_CREATION_REMOVAL_AND_STATUS, "w"), 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.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") 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", "<marker>", ModdedHelpMessages.COMMAND_WHAT_MARKERS_REVEAL_NEARBY_MARKERS)
));
SECTIONS.put("find", List.of( SECTIONS.put("find", List.of(
Entry.command("biome", "<key>", ModdedHelpMessages.COMMAND_BIOME_FIND_AN_IRIS_BIOME), Entry.command("biome", "<key>", ModdedHelpMessages.COMMAND_BIOME_FIND_AN_IRIS_BIOME),
Entry.command("region", "<key>", ModdedHelpMessages.COMMAND_REGION_FIND_AN_IRIS_REGION), Entry.command("region", "<key>", ModdedHelpMessages.COMMAND_REGION_FIND_AN_IRIS_REGION),
@@ -92,9 +103,9 @@ final class ModdedCommandHelp {
)); ));
SECTIONS.put("goto", SECTIONS.get("find")); SECTIONS.put("goto", SECTIONS.get("find"));
SECTIONS.put("edit", List.of( 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("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), 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) Entry.command("dimension", "", ModdedHelpMessages.COMMAND_DIMENSION_OPEN_THE_CURRENT_PACK_S_DIMENSION_JSON_IN_YOUR_DESKTOP_EDITOR, "d")
)); ));
SECTIONS.put("pregen", List.of( SECTIONS.put("pregen", List.of(
Entry.command("start", "<radius> [dimension] [at] [x] [z] [gui] [sync] [nocache]", ModdedHelpMessages.COMMAND_START_START_PREGENERATION_RADIUS_IN_BLOCKS_RESUMABLE_CHECKPOINT_CACHE_ON_BY_DEFAULT_CENTER), Entry.command("start", "<radius> [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", "<key>", ModdedHelpMessages.COMMAND_ANALYZE_SHOW_OBJECT_COMPOSITION), Entry.command("analyze", "<key>", ModdedHelpMessages.COMMAND_ANALYZE_SHOW_OBJECT_COMPOSITION),
Entry.command("shrink", "<key>", ModdedHelpMessages.COMMAND_SHRINK_SHRINK_AN_OBJECT_TO_ITS_MINIMUM_SIZE), Entry.command("shrink", "<key>", ModdedHelpMessages.COMMAND_SHRINK_SHRINK_AN_OBJECT_TO_ITS_MINIMUM_SIZE),
Entry.command("plausibilize", "<key|prefix/> [dryrun=true] [reach=N]", ModdedHelpMessages.COMMAND_PLAUSIBILIZE_GROW_BRANCHES_SO_TREE_LEAVES_SURVIVE_VANILLA_DECAY), Entry.command("plausibilize", "<key|prefix/> [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("o", SECTIONS.get("object"));
SECTIONS.put("studio", List.of( SECTIONS.put("studio", List.of(
Entry.command("create", "<name> [template]", ModdedHelpMessages.COMMAND_CREATE_CREATE_A_NEW_PACK_PROJECT, "+"), 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), 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("version", "[pack]", ModdedHelpMessages.COMMAND_VERSION_PRINT_A_PACK_VERSION),
Entry.command("regions", "[radius]", ModdedHelpMessages.COMMAND_REGIONS_CALCULATE_NEARBY_REGION_DISTRIBUTION), Entry.command("regions", "[radius]", ModdedHelpMessages.COMMAND_REGIONS_CALCULATE_NEARBY_REGION_DISTRIBUTION),
Entry.command("open", "<pack> [seed]", ModdedHelpMessages.COMMAND_OPEN_OPEN_A_TEMPORARY_STUDIO_DIMENSION_FOR_A_PACK, "o"), Entry.command("open", "<pack> [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("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("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("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("std", SECTIONS.get("studio"));
SECTIONS.put("s", SECTIONS.get("studio")); SECTIONS.put("s", SECTIONS.get("studio"));
@@ -148,6 +166,7 @@ final class ModdedCommandHelp {
SECTIONS.put("world", List.of( SECTIONS.put("world", List.of(
Entry.command("enable", "<dimension> <pack|pack:dimensionKey> [seed|random]", ModdedHelpMessages.COMMAND_ENABLE_CREATE_AND_INJECT_A_PERSISTENT_IRIS_DIMENSION_AT_RUNTIME_DOWNLOADS_THE_PACK, "create"), Entry.command("enable", "<dimension> <pack|pack:dimensionKey> [seed|random]", ModdedHelpMessages.COMMAND_ENABLE_CREATE_AND_INJECT_A_PERSISTENT_IRIS_DIMENSION_AT_RUNTIME_DOWNLOADS_THE_PACK, "create"),
Entry.command("replace-overworld", "<pack|pack:dimensionKey> [seed|random]", ModdedHelpMessages.COMMAND_REPLACE_OVERWORLD_INJECT_AN_IRIS_PRIMARY_WORLD_AND_ROUTE_PLAYERS_THERE_INSTEAD_OF_THE), Entry.command("replace-overworld", "<pack|pack:dimensionKey> [seed|random]", ModdedHelpMessages.COMMAND_REPLACE_OVERWORLD_INJECT_AN_IRIS_PRIMARY_WORLD_AND_ROUTE_PLAYERS_THERE_INSTEAD_OF_THE),
Entry.command("mainworld", "<pack|pack:dimensionKey|off> [seed|random]", ModdedHelpMessages.COMMAND_MAINWORLD_CONFIGURE_PRIMARY_WORLD_PRESET),
Entry.command("disable", "<dimension>", ModdedHelpMessages.COMMAND_DISABLE_EVACUATE_AND_UNLOAD_AN_IRIS_DIMENSION_WORLD_DATA_ON_DISK_IS_KEPT), Entry.command("disable", "<dimension>", ModdedHelpMessages.COMMAND_DISABLE_EVACUATE_AND_UNLOAD_AN_IRIS_DIMENSION_WORLD_DATA_ON_DISK_IS_KEPT),
Entry.command("delete", "<dimension>", ModdedHelpMessages.COMMAND_DELETE_DISABLE_AN_IRIS_DIMENSION_AND_WIPE_ITS_CHUNK_AND_MANTLE_DATA_FROM, "remove", "rm"), Entry.command("delete", "<dimension>", 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"), Entry.command("list", "", ModdedHelpMessages.COMMAND_LIST_LIST_LOADED_IRIS_DIMENSIONS, "ls"),
@@ -183,6 +202,24 @@ final class ModdedCommandHelp {
private ModdedCommandHelp() { private ModdedCommandHelp() {
} }
static boolean documents(String section, String command) {
List<Entry> 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) { static int send(CommandSourceStack source, String path) {
Request request = parse(path); Request request = parse(path);
List<Entry> entries = SECTIONS.get(request.section()); List<Entry> entries = SECTIONS.get(request.section());
@@ -21,16 +21,31 @@ package art.arcane.iris.modded.command;
import art.arcane.iris.core.localization.IrisLanguage; import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.RuntimeUiMessages; import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.iris.engine.framework.Engine; 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.IrisBiome;
import art.arcane.iris.engine.object.IrisRegion; import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.modded.ModdedBlockState; 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 art.arcane.volmlib.util.localization.MessageArgument;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos; 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.particles.DustParticleOptions;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component; 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.ServerLevel;
import net.minecraft.server.level.ServerPlayer; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -40,12 +55,18 @@ import java.util.Deque;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; 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; import java.util.function.Supplier;
public final class ModdedDustRevealer { public final class ModdedDustRevealer {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); 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 DustParticleOptions REVEAL_DUST = new DustParticleOptions(0xFFD24A, 1.2F);
private static final ConcurrentHashMap<UUID, RevealRun> ACTIVE_RUNS = new ConcurrentHashMap<>();
private ModdedDustRevealer() { private ModdedDustRevealer() {
} }
@@ -53,76 +74,167 @@ public final class ModdedDustRevealer {
public static void reveal(ServerPlayer player, ServerLevel level, BlockPos pos) { public static void reveal(ServerPlayer player, ServerLevel level, BlockPos pos) {
Engine engine = IrisModdedCommands.engineFor(level); Engine engine = IrisModdedCommands.engineFor(level);
if (engine == null) { 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; return;
} }
describe(player, level, engine, pos); describe(player, level, engine, pos);
int relativeY = pos.getY() - engine.getMinHeight(); 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) { if (key == null) {
return; 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( player.sendSystemMessage(Component.literal(IrisLanguage.plain(
RuntimeUiMessages.DUST_FOUND_OBJECT, RuntimeUiMessages.DUST_FOUND_OBJECT,
MessageArgument.untrusted("object", key) MessageArgument.untrusted("object", key)
))); )));
MinecraftServer server = level.getServer();
BlockPos origin = pos.immutable(); RevealRun run = new RevealRun(
Thread thread = new Thread(() -> { player.getUUID(),
List<BlockPos> hits = collect(engine, level, origin, key); player,
server.execute(() -> { level,
for (BlockPos hit : hits) { engine,
level.sendParticles(player, REVEAL_DUST, true, true, pos.immutable(),
hit.getX() + 0.5D, hit.getY() + 0.5D, hit.getZ() + 0.5D, key,
3, 0.25D, 0.25D, 0.25D, 0.0D); level.getMinY(),
} level.getMaxY(),
player.sendSystemMessage(Component.literal(IrisLanguage.plain( new AtomicBoolean());
hits.size() >= MAX_HITS ? RuntimeUiMessages.DUST_REVEALED_CAPPED : RuntimeUiMessages.DUST_REVEALED, RevealRun previous = ACTIVE_RUNS.put(player.getUUID(), run);
MessageArgument.trusted("count", hits.size()), if (previous != null) {
MessageArgument.untrusted("object", key) previous.cancelled().set(true);
))); }
}); scheduler.async(() -> discover(scheduler, run));
}, "Iris Dust Revealer");
thread.setDaemon(true);
thread.start();
} }
private static List<BlockPos> 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<BlockPos> 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<BlockPos> 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<BlockPos> collect(BlockPos origin, String key, int engineMinY,
int minY, int maxY, AtomicBoolean cancelled,
ObjectPlacementLookup lookup) {
List<BlockPos> hits = new ArrayList<>(); List<BlockPos> hits = new ArrayList<>();
Set<BlockPos> visited = new HashSet<>(); Set<BlockPos> visited = new HashSet<>();
Deque<BlockPos> frontier = new ArrayDeque<>(); Deque<BlockPos> frontier = new ArrayDeque<>();
frontier.add(origin); frontier.add(origin);
visited.add(origin); visited.add(origin);
int minY = level.getMinY(); while (!frontier.isEmpty() && hits.size() < MAX_HITS && !cancelled.get()) {
int maxY = minY + level.getHeight(); BlockPos current = frontier.poll();
try { hits.add(current);
while (!frontier.isEmpty() && hits.size() < MAX_HITS) { for (int dx = -1; dx <= 1; dx++) {
BlockPos current = frontier.poll(); for (int dy = -1; dy <= 1; dy++) {
hits.add(current); for (int dz = -1; dz <= 1; dz++) {
for (int dx = -1; dx <= 1; dx++) { if (dx == 0 && dy == 0 && dz == 0) {
for (int dy = -1; dy <= 1; dy++) { continue;
for (int dz = -1; dz <= 1; dz++) { }
if (dx == 0 && dy == 0 && dz == 0) { BlockPos next = current.offset(dx, dy, dz);
continue; if (next.getY() < minY
} || next.getY() >= maxY
BlockPos next = current.offset(dx, dy, dz); || !visited.add(next)) {
if (next.getY() < minY || next.getY() >= maxY || visited.contains(next)) { continue;
continue; }
} String nextKey = lookup.at(
visited.add(next); next.getX(), next.getY() - engineMinY, next.getZ());
String nextKey = engine.getObjectPlacementKey(next.getX(), next.getY() - engine.getMinHeight(), next.getZ()); if (key.equals(nextKey)) {
if (key.equals(nextKey)) { frontier.add(next);
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<BlockPos> 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) { 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 z = pos.getZ();
int minHeight = engine.getMinHeight(); int minHeight = engine.getMinHeight();
int relativeY = y - minHeight; int relativeY = y - minHeight;
int surfaceRelative = safeInt(() -> engine.getHeight(x, z, true)); Integer surfaceRelative = safe(
int surfaceY = surfaceRelative + minHeight; "surface height lookup at " + coordinates(pos),
int offset = y - surfaceY; () -> 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)); String objectKey = safe(
IrisBiome surfaceBiome = safe(() -> engine.getSurfaceBiome(x, z)); "object lookup at " + coordinates(pos),
IrisBiome biomeHere = safe(() -> engine.getBiome(x, relativeY, z)); () -> engine.getObjectPlacementKey(x, relativeY, z));
IrisBiome caveBiome = safe(() -> engine.getCaveOrMantleBiome(x, relativeY, z)); IrisBiome surfaceBiome = safe(
IrisRegion region = safe(() -> engine.getRegion(x, z)); "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<String> lines = new ArrayList<>(); List<DustLine> lines = new ArrayList<>();
lines.add(IrisLanguage.plain( lines.add(new DustLine(IrisLanguage.plain(
RuntimeUiMessages.DUST_HEADER, RuntimeUiMessages.DUST_HEADER,
MessageArgument.trusted("x", x), MessageArgument.trusted("x", x),
MessageArgument.trusted("y", y), MessageArgument.trusted("y", y),
MessageArgument.trusted("z", z) MessageArgument.trusted("z", z)
)); ), false));
lines.add(IrisLanguage.plain( lines.add(new DustLine(IrisLanguage.plain(
RuntimeUiMessages.DUST_BLOCK, RuntimeUiMessages.DUST_BLOCK,
MessageArgument.untrusted("block", ModdedBlockState.serialize(level.getBlockState(pos))) MessageArgument.untrusted("block", ModdedBlockState.serialize(level.getBlockState(pos)))
)); ), false));
if (offset > 0) { if (offset != null && surfaceY != null) {
lines.add(IrisLanguage.plain( lines.add(new DustLine(positionLine(offset, surfaceY), true));
RuntimeUiMessages.DUST_POSITION_ABOVE, lines.add(new DustLine(
MessageArgument.trusted("offset", offset), placementLine(offset, surfaceRelative, relativeY, objectKey), true));
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)
));
} }
lines.add(IrisLanguage.plain( lines.add(new DustLine(IrisLanguage.plain(
RuntimeUiMessages.DUST_OBJECT_AT_BLOCK, RuntimeUiMessages.DUST_OBJECT_AT_BLOCK,
MessageArgument.untrusted( MessageArgument.untrusted(
"object", "object",
objectKey == null ? IrisLanguage.plain(RuntimeUiMessages.DUST_NONE) : objectKey objectKey == null
) ? IrisLanguage.plain(RuntimeUiMessages.DUST_NONE)
)); : objectKey)
if (surfaceBiome != null) { ), false));
lines.add(IrisLanguage.plain( if (objectKey == null) {
RuntimeUiMessages.DUST_SURFACE_BIOME, String columnObject = findColumnObject(engine, x, relativeY, z, minHeight);
MessageArgument.untrusted("biome", surfaceBiome.getLoadKey()) 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()))) { if (surfaceBiome != null) {
lines.add(IrisLanguage.plain( 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, RuntimeUiMessages.DUST_BIOME_AT_Y,
MessageArgument.untrusted("biome", biomeHere.getLoadKey()) MessageArgument.untrusted("biome", biomeHere.getLoadKey())
)); ), false));
} }
if (caveBiome != null && (surfaceBiome == null || !caveBiome.getLoadKey().equals(surfaceBiome.getLoadKey()))) { if (caveBiome != null
lines.add(IrisLanguage.plain( && (surfaceBiome == null
|| !caveBiome.getLoadKey().equals(surfaceBiome.getLoadKey()))) {
lines.add(new DustLine(IrisLanguage.plain(
RuntimeUiMessages.DUST_CAVE_BIOME, RuntimeUiMessages.DUST_CAVE_BIOME,
MessageArgument.untrusted("biome", caveBiome.getLoadKey()) 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) { if (region != null) {
lines.add(IrisLanguage.plain( lines.add(new DustLine(IrisLanguage.plain(
RuntimeUiMessages.DUST_REGION, RuntimeUiMessages.DUST_REGION,
MessageArgument.untrusted("region", region.getLoadKey()), MessageArgument.untrusted("region", region.getLoadKey()),
MessageArgument.untrusted("name", region.getName()) MessageArgument.untrusted("name", region.getName())
)); ), false));
} }
Set<String> objects = safe(() -> engine.getObjectsAt(x >> 4, z >> 4)); Set<String> objects = safe(
"chunk object lookup at " + (x >> 4) + ", " + (z >> 4),
() -> engine.getObjectsAt(x >> 4, z >> 4));
if (objects != null && !objects.isEmpty()) { if (objects != null && !objects.isEmpty()) {
lines.add(IrisLanguage.plain( lines.add(new DustLine(IrisLanguage.plain(
RuntimeUiMessages.DUST_OBJECTS_IN_CHUNK, RuntimeUiMessages.DUST_OBJECTS_IN_CHUNK,
MessageArgument.untrusted("objects", objects) MessageArgument.untrusted("objects", objects.stream().sorted().toList())
)); ), false));
}
for (String line : lines) {
player.sendSystemMessage(Component.literal(line));
} }
sendReport(player, lines);
} }
private static <T> T safe(Supplier<T> 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<Biome> holder = level.getBiome(pos);
String key = holder.unwrapKey()
.map((ResourceKey<Biome> resourceKey) -> resourceKey.identifier().toString())
.orElse(IrisLanguage.plain(RuntimeUiMessages.STATUS_UNREGISTERED));
Registry<Biome> registry = level.registryAccess().lookupOrThrow(Registries.BIOME);
return new NativeBiome(key, registry.getId(holder.value()));
}
private static void sendReport(ServerPlayer player, List<DustLine> 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> T safe(String operation, Supplier<T> supplier) {
try { try {
return supplier.get(); return supplier.get();
} catch (Throwable e) { } catch (Throwable error) {
LOGGER.error("Iris dust {} failed", operation, error);
return null; return null;
} }
} }
private static int safeInt(Supplier<Integer> supplier) { private static String coordinates(BlockPos pos) {
try { return pos.getX() + ", " + pos.getY() + ", " + pos.getZ();
Integer value = supplier.get(); }
return value == null ? 0 : value;
} catch (Throwable e) { private record DustLine(String text, boolean emphasis) {
return 0; }
}
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
) {
} }
} }
@@ -233,7 +233,7 @@ public final class ModdedObjectCommands {
return 1; return 1;
} }
private static int giveDust(CommandSourceStack source) { static int giveDust(CommandSourceStack source) {
ServerPlayer player = source.getPlayer(); ServerPlayer player = source.getPlayer();
if (player == null) { if (player == null) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_DUST_IS)); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_DUST_IS));
@@ -115,21 +115,11 @@ public final class ModdedStudioCommands {
root.executes((CommandContext<CommandSourceStack> context) -> ModdedCommandHelp.send(context.getSource(), name)); root.executes((CommandContext<CommandSourceStack> context) -> ModdedCommandHelp.send(context.getSource(), name));
root.then(Commands.literal("create") root.then(createTree("create"));
.then(Commands.argument("name", StringArgumentType.word()) root.then(createTree("+"));
.executes((CommandContext<CommandSourceStack> context) -> create(context.getSource(), StringArgumentType.getString(context, "name"), DEFAULT_TEMPLATE))
.then(Commands.argument("template", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
.executes((CommandContext<CommandSourceStack> context) -> create(context.getSource(), StringArgumentType.getString(context, "name"), StringArgumentType.getString(context, "template"))))));
root.then(Commands.literal("+")
.then(Commands.argument("name", StringArgumentType.word())
.executes((CommandContext<CommandSourceStack> context) -> create(context.getSource(), StringArgumentType.getString(context, "name"), DEFAULT_TEMPLATE))
.then(Commands.argument("template", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
.executes((CommandContext<CommandSourceStack> context) -> create(context.getSource(), StringArgumentType.getString(context, "name"), StringArgumentType.getString(context, "template"))))));
root.then(Commands.literal("package") root.then(packageTree("package"));
.executes((CommandContext<CommandSourceStack> context) -> pkg(context.getSource(), null)) root.then(packageTree("pkg"));
.then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
.executes((CommandContext<CommandSourceStack> context) -> pkg(context.getSource(), StringArgumentType.getString(context, "pack")))));
root.then(Commands.literal("version") root.then(Commands.literal("version")
.executes((CommandContext<CommandSourceStack> context) -> version(context.getSource(), null)) .executes((CommandContext<CommandSourceStack> context) -> version(context.getSource(), null))
@@ -173,6 +163,30 @@ public final class ModdedStudioCommands {
return root; return root;
} }
private static LiteralArgumentBuilder<CommandSourceStack> createTree(String name) {
return Commands.literal(name)
.executes((CommandContext<CommandSourceStack> context) ->
create(context.getSource(), "studio", DEFAULT_TEMPLATE))
.then(Commands.argument("name", StringArgumentType.word())
.executes((CommandContext<CommandSourceStack> context) ->
create(context.getSource(), StringArgumentType.getString(context, "name"), DEFAULT_TEMPLATE))
.then(Commands.argument("template", StringArgumentType.word())
.suggests(IrisModdedCommands.PACK_NAMES)
.executes((CommandContext<CommandSourceStack> context) ->
create(context.getSource(),
StringArgumentType.getString(context, "name"),
StringArgumentType.getString(context, "template")))));
}
private static LiteralArgumentBuilder<CommandSourceStack> packageTree(String name) {
return Commands.literal(name)
.executes((CommandContext<CommandSourceStack> context) -> pkg(context.getSource(), null))
.then(Commands.argument("pack", StringArgumentType.word())
.suggests(IrisModdedCommands.PACK_NAMES)
.executes((CommandContext<CommandSourceStack> context) ->
pkg(context.getSource(), StringArgumentType.getString(context, "pack"))));
}
public static void clear() { public static void clear() {
STUDIOS.clear(); STUDIOS.clear();
} }
@@ -368,7 +382,7 @@ public final class ModdedStudioCommands {
File packFolder = new File(ModdedPackCommands.packsRoot(), pack); File packFolder = new File(ModdedPackCommands.packsRoot(), pack);
if (!new File(packFolder, "dimensions/" + pack + ".json").isFile()) { 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)))); 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))); (String line) -> server.execute(() -> IrisModdedCommands.ok(source, line)));
if (!installed || !new File(packFolder, "dimensions/" + pack + ".json").isFile()) { 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)))); 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); File templateFolder = new File(packsRoot, template);
if (!new File(templateFolder, "dimensions/" + template + ".json").isFile()) { 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)))); 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))); (String line) -> server.execute(() -> IrisModdedCommands.ok(source, line)));
if (!installed || !new File(templateFolder, "dimensions/" + template + ".json").isFile()) { 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)))); 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))));
@@ -40,6 +40,7 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items; import net.minecraft.world.item.Items;
import net.minecraft.world.item.component.CustomData; import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.item.component.ItemLore; import net.minecraft.world.item.component.ItemLore;
import net.minecraft.world.item.component.TooltipDisplay;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -88,6 +89,8 @@ public final class ModdedWandService {
stack.set(DataComponents.UNBREAKABLE, Unit.INSTANCE); stack.set(DataComponents.UNBREAKABLE, Unit.INSTANCE);
stack.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, Boolean.TRUE); stack.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, Boolean.TRUE);
stack.set(DataComponents.CUSTOM_DATA, CustomData.of(flagTag(WAND_TAG))); stack.set(DataComponents.CUSTOM_DATA, CustomData.of(flagTag(WAND_TAG)));
stack.set(DataComponents.TOOLTIP_DISPLAY,
TooltipDisplay.DEFAULT.withHidden(DataComponents.UNBREAKABLE, true));
return stack; return stack;
} }
@@ -99,6 +102,8 @@ public final class ModdedWandService {
stack.set(DataComponents.UNBREAKABLE, Unit.INSTANCE); stack.set(DataComponents.UNBREAKABLE, Unit.INSTANCE);
stack.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, Boolean.TRUE); stack.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, Boolean.TRUE);
stack.set(DataComponents.CUSTOM_DATA, CustomData.of(flagTag(DUST_TAG))); stack.set(DataComponents.CUSTOM_DATA, CustomData.of(flagTag(DUST_TAG)));
stack.set(DataComponents.TOOLTIP_DISPLAY,
TooltipDisplay.DEFAULT.withHidden(DataComponents.UNBREAKABLE, true));
return stack; return stack;
} }
@@ -187,6 +192,8 @@ public final class ModdedWandService {
public static void clearAll() { public static void clearAll() {
SELECTIONS.clear(); SELECTIONS.clear();
ModdedDustRevealer.clear();
ModdedWhatCommands.clear();
} }
public static void serverTick(MinecraftServer server) { public static void serverTick(MinecraftServer server) {
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<CommandSourceStack> GATE =
Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private static final SuggestionProvider<CommandSourceStack> MARKER_TYPES =
(CommandContext<CommandSourceStack> 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<UUID, MarkerRun> ACTIVE_MARKER_RUNS = new ConcurrentHashMap<>();
private ModdedWhatCommands() {
}
public static LiteralArgumentBuilder<CommandSourceStack> tree() {
return Commands.literal("what").requires(GATE)
.executes((CommandContext<CommandSourceStack> context) ->
inspectHere(context.getSource()))
.then(Commands.literal("here")
.executes((CommandContext<CommandSourceStack> context) ->
inspectHere(context.getSource())))
.then(Commands.literal("biome")
.executes((CommandContext<CommandSourceStack> context) ->
inspectBiome(context.getSource())))
.then(Commands.literal("region")
.executes((CommandContext<CommandSourceStack> context) ->
inspectRegion(context.getSource())))
.then(Commands.literal("block")
.executes((CommandContext<CommandSourceStack> context) ->
inspectBlock(context.getSource())))
.then(Commands.literal("hand")
.executes((CommandContext<CommandSourceStack> context) ->
inspectHand(context.getSource())))
.then(Commands.literal("markers")
.then(Commands.argument("marker", StringArgumentType.greedyString())
.suggests(MARKER_TYPES)
.executes((CommandContext<CommandSourceStack> 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<String> 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<String> propertyNames(PlatformBlockState platform) {
List<String> 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<String> 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> 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<BlockPos> 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<BlockPos> 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<Biome> holder = level.getBiome(pos);
String key = holder.unwrapKey()
.map((ResourceKey<Biome> resourceKey) -> resourceKey.identifier().toString())
.orElse(IrisLanguage.plain(RuntimeUiMessages.STATUS_UNREGISTERED));
Registry<Biome> 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
) {
}
}
@@ -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))); 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(() -> { 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))); (String line) -> server.execute(() -> IrisModdedCommands.ok(source, line)));
server.execute(() -> { server.execute(() -> {
if (!installed || !packFolder.isDirectory()) { 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))); 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(() -> { 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))); (String line) -> server.execute(() -> IrisModdedCommands.ok(source, line)));
server.execute(() -> { server.execute(() -> {
if (!installed || !packFolder.isDirectory()) { if (!installed || !packFolder.isDirectory()) {
@@ -56,8 +56,12 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi
@Override @Override
public void onEnable() { public void onEnable() {
ExecutorService current = service; ExecutorService current = service;
if (current != null && !current.isShutdown()) { if (current != null) {
return; 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(); IrisSettings.IrisSettingsEngineSVC settings = IrisSettings.get().getPerformance().getEngineSVC();
@@ -75,9 +79,9 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi
@Override @Override
public void onDisable() { public void onDisable() {
ExecutorService active = service; ExecutorService active = service;
service = null;
boolean drained = shutdownAndDrain(active); boolean drained = shutdownAndDrain(active);
if (drained) { if (drained) {
service = null;
inFlight.clear(); inFlight.clear();
} }
lastSavedAt.clear(); lastSavedAt.clear();
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -1,4 +1,6 @@
{ {
"key.categories.irisworldgen.iris": "Iris", "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"
} }
@@ -0,0 +1,13 @@
{
"required": true,
"minVersion": "0.8",
"package": "art.arcane.iris.client.mixin",
"compatibilityLevel": "JAVA_25",
"client": [
"IrisWorldOpenFlowsMixin",
"IrisWorldTypeEntryMixin"
],
"injectors": {
"defaultRequire": 1
}
}
@@ -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());
}
}
@@ -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)));
}
}
@@ -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());
}
}
@@ -29,7 +29,7 @@ public class IrisModLanguageAssetsTest {
@Test @Test
public void minecraftLanguageAssetsMatchSharedLocaleManifest() throws Exception { public void minecraftLanguageAssetsMatchSharedLocaleManifest() throws Exception {
JsonObject english = read("en_us"); JsonObject english = read("en_us");
assertEquals(2, english.size()); assertEquals(4, english.size());
for (String locale : VolmitLocales.nonEnglish()) { for (String locale : VolmitLocales.nonEnglish()) {
String minecraftLocale = VolmitLocales.minecraftCode(locale); String minecraftLocale = VolmitLocales.minecraftCode(locale);
@@ -73,9 +73,16 @@ public class IrisModLanguageAssetsTest {
private Set<String> resourceFiles() throws Exception { private Set<String> resourceFiles() throws Exception {
URL resource = IrisModLanguageAssetsTest.class.getClassLoader().getResource(ROOT); URL resource = IrisModLanguageAssetsTest.class.getClassLoader().getResource(ROOT);
assertNotNull("Missing mod language resource directory", resource); Path directory;
assertEquals("file", resource.getProtocol()); if (resource != null && "file".equals(resource.getProtocol())) {
try (Stream<Path> paths = Files.list(Path.of(resource.toURI()))) { 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<Path> paths = Files.list(directory)) {
return paths return paths
.filter(Files::isRegularFile) .filter(Files::isRegularFile)
.map(path -> path.getFileName().toString()) .map(path -> path.getFileName().toString())
@@ -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<ModdedDimensionRegistryStore.PersistentDimension> 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);
}
}
}
@@ -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 art.arcane.iris.engine.object.IrisDimensionTypeOptions.TriState.TRUE;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; 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 nether = dimension("nether", IrisEnvironment.NETHER, 0, 256, 256, new IrisDimensionTypeOptions());
IrisDimension end = dimension("the_end", IrisEnvironment.THE_END, 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:packs/6f766572776f726c64/dimensions/6f766572776f726c64/dimension_type",
assertEquals("irisworldgen:nether", ModdedForcedDatapack.dimensionTypeRef(nether)); ModdedWorldgenIds.dimensionTypeRef("overworld", overworld.getLoadKey()));
assertEquals("irisworldgen:the_end", ModdedForcedDatapack.dimensionTypeRef(end)); 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 @Test
@@ -77,18 +83,18 @@ public class ModdedDimensionTypeParityTest {
roots.add(packDirectory.toFile()); roots.add(packDirectory.toFile());
try { try {
for (IrisDimension dimension : dimensions) { for (IrisDimension dimension : dimensions) {
ModdedForcedDatapack.writeDimensionType(roots, fixer, dimension); ModdedForcedDatapack.writeDimensionType(
Path output = packDirectory.resolve("data/irisworldgen/dimension_type/" roots, fixer, dimension, "contracts", dimension.getLoadKey());
+ dimension.getDimensionTypeKey() + ".json"); Path output = typeFile(packDirectory, "contracts", dimension);
assertTrue(Files.isRegularFile(output)); assertTrue(Files.isRegularFile(output));
assertEquals(dimension.getDimensionType().toJson(fixer), assertEquals(dimension.getDimensionType().toJson(fixer),
Files.readString(output, StandardCharsets.UTF_8)); Files.readString(output, StandardCharsets.UTF_8));
} }
JSONObject overworldJson = readType(packDirectory, overworld); JSONObject overworldJson = readType(packDirectory, "contracts", overworld);
JSONObject netherJson = readType(packDirectory, nether); JSONObject netherJson = readType(packDirectory, "contracts", nether);
JSONObject endJson = readType(packDirectory, end); JSONObject endJson = readType(packDirectory, "contracts", end);
JSONObject customJson = readType(packDirectory, custom); JSONObject customJson = readType(packDirectory, "contracts", custom);
assertTrue(overworldJson.getBoolean("has_skylight")); assertTrue(overworldJson.getBoolean("has_skylight"));
assertFalse(overworldJson.getBoolean("has_ceiling")); assertFalse(overworldJson.getBoolean("has_ceiling"));
@@ -166,10 +172,16 @@ public class ModdedDimensionTypeParityTest {
return dimension; return dimension;
} }
private static JSONObject readType(Path packDirectory, IrisDimension dimension) throws IOException { private static JSONObject readType(Path packDirectory, String pack,
Path output = packDirectory.resolve("data/irisworldgen/dimension_type/" IrisDimension dimension) throws IOException {
+ dimension.getDimensionTypeKey() + ".json"); return new JSONObject(Files.readString(
return new JSONObject(Files.readString(output, StandardCharsets.UTF_8)); 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 { private static void deleteTree(Path root) throws IOException {
@@ -39,6 +39,21 @@ import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class ModdedForcedDatapackTest { 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 @Test
public void scopesSharedCustomBiomeIdsByNamespace() { public void scopesSharedCustomBiomeIdsByNamespace() {
Map<String, KSet<String>> seenBiomes = new LinkedHashMap<>(); Map<String, KSet<String>> seenBiomes = new LinkedHashMap<>();
@@ -60,16 +60,15 @@ public class ModdedLifecycleFailureContractTest {
} }
@Test @Test
public void persistentReinjectionRethrowsTheOriginalCause() throws IOException { public void persistentReinjectionQuarantinesBrokenEntriesAndContinues() throws IOException {
String source = source("ModdedStartup.java"); String source = source("ModdedStartup.java");
String reinjection = method(source, "private static void reinjectPersistentDimensions("); String reinjection = method(source, "private static void reinjectPersistentDimensions(");
String failure = catchBlock(reinjection); String failure = catchBlock(reinjection);
assertTrue(failure.contains("LOGGER.error(")); assertTrue(failure.contains("LOGGER.error("));
assertFalse(failure.contains("e.toString()")); assertFalse(failure.contains("e.toString()"));
assertFalse(failure.contains("continue;")); assertFalse(failure.contains("throw new IllegalStateException("));
assertTrue(failure.contains("throw new IllegalStateException(")); assertTrue(reinjection.contains("injected++;"));
assertTrue(failure.contains(", e);"));
} }
@Test @Test
@@ -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<CommandSourceStack> dispatcher = new CommandDispatcher<>();
IrisModdedCommands.register(dispatcher);
CommandNode<CommandSourceStack> iris = child(dispatcher.getRoot(), "iris");
CommandNode<CommandSourceStack> 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<CommandSourceStack> edit = child(iris, "edit");
child(edit, "b");
child(edit, "r");
child(edit, "d");
CommandNode<CommandSourceStack> studio = child(iris, "studio");
child(studio, "package");
child(studio, "pkg");
CommandNode<CommandSourceStack> download = child(iris, "download");
CommandNode<CommandSourceStack> pack = child(download, "pack");
child(pack, "force");
child(pack, "overwrite");
CommandNode<CommandSourceStack> 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<CommandSourceStack> child(
CommandNode<CommandSourceStack> parent, String name) {
CommandNode<CommandSourceStack> child = parent.getChild(name);
assertNotNull(name, child);
return child;
}
}
@@ -25,7 +25,7 @@ public class IrisModdedStructureCommandTest {
assertTrue(source.contains("combineStructureKeys(irisKeys, nativeKeys)")); assertTrue(source.contains("combineStructureKeys(irisKeys, nativeKeys)"));
assertTrue(source.contains("irisGenerator.isNativeStructureReachable(holder)")); assertTrue(source.contains("irisGenerator.isNativeStructureReachable(holder)"));
assertTrue(source.contains("LocateStatus.SEARCH_LIMIT_REACHED")); 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 targetX = result.originX()"));
assertTrue(source.contains("int targetY = result.baseY() + 2")); assertTrue(source.contains("int targetY = result.baseY() + 2"));
assertTrue(source.contains("int targetZ = result.originZ()")); assertTrue(source.contains("int targetZ = result.originZ()"));
@@ -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<BlockPos> object = Set.of(
new BlockPos(0, 0, 0),
new BlockPos(1, 1, 1),
new BlockPos(3, 3, 3));
List<BlockPos> 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<BlockPos> 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());
}
}
+4
View File
@@ -146,6 +146,10 @@ neoForge {
accessTransformers.from('src/main/resources/META-INF/accesstransformer.cfg') accessTransformers.from('src/main/resources/META-INF/accesstransformer.cfg')
runs { runs {
client {
client()
jvmArgument('-Xmx8G')
}
server { server {
server() server()
String parity = providers.gradleProperty('irisParity').getOrNull() String parity = providers.gradleProperty('irisParity').getOrNull()
@@ -52,6 +52,9 @@ public final class IrisNeoForgeClient {
}); });
NeoForge.EVENT_BUS.addListener((ClientPlayerNetworkEvent.LoggingIn event) -> IrisClient.onWorldJoin()); NeoForge.EVENT_BUS.addListener((ClientPlayerNetworkEvent.LoggingIn event) -> IrisClient.onWorldJoin());
NeoForge.EVENT_BUS.addListener((ClientPlayerNetworkEvent.LoggingOut event) -> IrisClient.onDisconnect()); 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();
});
} }
} }
@@ -1,4 +1,3 @@
public net.minecraft.server.MinecraftServer levels public net.minecraft.server.MinecraftServer levels
public net.minecraft.server.MinecraftServer executor public net.minecraft.server.MinecraftServer executor
public net.minecraft.server.MinecraftServer storageSource public net.minecraft.server.MinecraftServer storageSource
public net.minecraft.core.MappedRegistry frozen
@@ -5,6 +5,9 @@ license = "GPL-3.0"
[[mixins]] [[mixins]]
config = "irisworldgen.entity.mixins.json" config = "irisworldgen.entity.mixins.json"
[[mixins]]
config = "irisworldgen.client.mixins.json"
[[mods]] [[mods]]
modId = "irisworldgen" modId = "irisworldgen"
version = "${version}" version = "${version}"
@@ -354,6 +354,70 @@ public final class ModdedHelpMessages {
"iris.modded.help.entry.command.network", "iris.modded.help.entry.command.network",
"List network interfaces and their addresses" "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<MessageKey> KEYS = List.of( private static final List<MessageKey> KEYS = List.of(
COMMAND_VERSION_PRINT_VERSION_INFORMATION, COMMAND_VERSION_PRINT_VERSION_INFORMATION,
@@ -442,7 +506,23 @@ public final class ModdedHelpMessages {
COMMAND_CAPTURE_EXPLAIN_BUKKIT_STRUCTURE_CAPTURE_WORKFLOW, COMMAND_CAPTURE_EXPLAIN_BUKKIT_STRUCTURE_CAPTURE_WORKFLOW,
COMMAND_VERIFY_REPORT_NATIVE_AND_IRIS_STRUCTURE_REACHABILITY_IN_THE_CURRENT_DIMENSION, COMMAND_VERIFY_REPORT_NATIVE_AND_IRIS_STRUCTURE_REACHABILITY_IN_THE_CURRENT_DIMENSION,
COMMAND_SENTRY_SEND_A_TEST_EXCEPTION_TO_THE_IRIS_ERROR_REPORTER, 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() { private ModdedHelpMessages() {
@@ -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_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_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_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_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_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"); 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_OBJECTS_IN_CHUNK,
DUST_COPY_BUTTON, DUST_COPY_BUTTON,
DUST_COPY_HOVER, 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_STARTING,
PREGEN_HEADER, PREGEN_HEADER,
PREGEN_BOSSBAR_PAUSED, PREGEN_BOSSBAR_PAUSED,
@@ -31,7 +31,7 @@ public final class IrisCursorResolver {
IrisBiome surfaceBiome = engine.getSurfaceBiome(blockX, blockZ); IrisBiome surfaceBiome = engine.getSurfaceBiome(blockX, blockZ);
IrisRegion region = engine.getRegion(blockX, blockZ); IrisRegion region = engine.getRegion(blockX, blockZ);
IrisBiome caveBiome = engine.getCaveBiome(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 biomeKey = keyOf(surfaceBiome == null ? null : surfaceBiome.getLoadKey());
String regionKey = keyOf(region == null ? null : region.getLoadKey()); String regionKey = keyOf(region == null ? null : region.getLoadKey());
String caveBiomeKey = keyOf(caveBiome == null ? null : caveBiome.getLoadKey()); String caveBiomeKey = keyOf(caveBiome == null ? null : caveBiome.getLoadKey());
@@ -284,11 +284,11 @@ public final class IrisProtocolServer {
} }
private void dispatch(IrisSession session, IrisMessage message) { 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 (session.state() == IrisSession.State.AWAITING_HELLO) {
if (message instanceof IrisMessage.ClientHello clientHello) {
onClientHello(session, clientHello);
return;
}
droppedBeforeHello.incrementAndGet(); droppedBeforeHello.incrementAndGet();
return; return;
} }
@@ -524,7 +524,11 @@ public class IrisDimension extends IrisRegistrant {
public void installBiomes(IDataFixer fixer, DataProvider data, KList<File> datapackRoots, KSet<String> biomes) throws IOException { public void installBiomes(IDataFixer fixer, DataProvider data, KList<File> datapackRoots, KSet<String> biomes) throws IOException {
String namespace = getLoadKey().toLowerCase(Locale.ROOT); String namespace = getLoadKey().toLowerCase(Locale.ROOT);
installBiomes(fixer, data, datapackRoots, namespace, "", biomes);
}
public void installBiomes(IDataFixer fixer, DataProvider data, KList<File> datapackRoots,
String namespace, String pathPrefix, KSet<String> biomes) throws IOException {
for (IrisBiome irisBiome : getAllBiomes(data)) { for (IrisBiome irisBiome : getAllBiomes(data)) {
if (!irisBiome.isCustom()) { if (!irisBiome.isCustom()) {
continue; continue;
@@ -542,12 +546,15 @@ public class IrisDimension extends IrisRegistrant {
} }
for (File datapackRoot : datapackRoots) { 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()); IrisLogging.debug(" Installing Data Pack Biome: " + output.getPath());
output.getParentFile().mkdirs(); output.getParentFile().mkdirs();
IO.writeAll(output, json); IO.writeAll(output, json);
installBiomeTags(datapackRoot, namespace + ":" + customBiomeId, customBiome.getTags()); installBiomeTags(datapackRoot, namespace + ":" + biomePath, customBiome.getTags());
} }
} }
} }
+44 -1
View File
@@ -1427,6 +1427,49 @@
"iris.desktop.pregen.speed_cached": "Geschwindigkeit: zwischengespeichert {chunksPerSecond} Chunks/s, {regionsPerMinute} Regionen/m, {chunksPerMinute} Chunks/m", "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.time": "{remaining} verbleibend ({elapsed} abgelaufen)",
"iris.desktop.pregen.method": "Erzeugungsmethode: {method}", "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}"
} }
} }
+44 -1
View File
@@ -1427,6 +1427,49 @@
"iris.desktop.pregen.speed_cached": "Velocidad: en caché {chunksPerSecond} chunks/s, {regionsPerMinute} regiones/m, {chunksPerMinute} chunks/m", "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.time": "{remaining} restantes ({elapsed} transcurridos)",
"iris.desktop.pregen.method": "Método de generación: {method}", "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}"
} }
} }
+44 -1
View File
@@ -1427,6 +1427,49 @@
"iris.desktop.pregen.speed_cached": "Nopeus: välimuisti {chunksPerSecond} chunkia/s, {regionsPerMinute} alueet/m {chunksPerMinute} chunkia/m", "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.time": "{remaining} jäljellä ({elapsed} Kulunut)",
"iris.desktop.pregen.method": "Generointimenetelmä: {method}", "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}"
} }
} }
+44 -1
View File
@@ -1427,6 +1427,49 @@
"iris.desktop.pregen.speed_cached": "Vitesse : cache {chunksPerSecond} chunks/s, {regionsPerMinute} régions/m, {chunksPerMinute} chunks/m", "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.time": "{remaining} restantes ({elapsed} écoulées)",
"iris.desktop.pregen.method": "Méthode de génération : {method}", "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}"
} }
} }
+44 -1
View File
@@ -1427,6 +1427,49 @@
"iris.desktop.pregen.speed_cached": "מהירות: במטמון {chunksPerSecond} צ'אנקים/s, {regionsPerMinute} אזורים/m, {chunksPerMinute} צ'אנקים/m", "iris.desktop.pregen.speed_cached": "מהירות: במטמון {chunksPerSecond} צ'אנקים/s, {regionsPerMinute} אזורים/m, {chunksPerMinute} צ'אנקים/m",
"iris.desktop.pregen.time": "{remaining} הנותרים ({elapsed} מת)", "iris.desktop.pregen.time": "{remaining} הנותרים ({elapsed} מת)",
"iris.desktop.pregen.method": "שיטת דור: {method}", "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}"
} }
} }
+44 -1
View File
@@ -1427,6 +1427,49 @@
"iris.desktop.pregen.speed_cached": "Velocità: cache {chunksPerSecond} chunk/s, {regionsPerMinute} regioni/m, {chunksPerMinute} chunk/m", "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.time": "{remaining} rimanenti ({elapsed} trascorso)",
"iris.desktop.pregen.method": "Metodo di generazione: {method}", "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}"
} }
} }
+44 -1
View File
@@ -1427,6 +1427,49 @@
"iris.desktop.pregen.speed_cached": "速度: キャッシュ {chunksPerSecond} チャンク/s、{regionsPerMinute} リージョン/m、{chunksPerMinute} チャンク/m", "iris.desktop.pregen.speed_cached": "速度: キャッシュ {chunksPerSecond} チャンク/s、{regionsPerMinute} リージョン/m、{chunksPerMinute} チャンク/m",
"iris.desktop.pregen.time": "残り {remaining}(経過 {elapsed}", "iris.desktop.pregen.time": "残り {remaining}(経過 {elapsed}",
"iris.desktop.pregen.method": "生成方法: {method}", "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}"
} }
} }

Some files were not shown because too many files have changed in this diff Show More