This commit is contained in:
Brian Neumann-Fopiano
2026-08-24 02:27:26 -04:00
parent fe5651f854
commit e20bb86f40
228 changed files with 5716 additions and 3577 deletions
@@ -1,130 +0,0 @@
package art.arcane.iris.core.nms.v26_2_R1;
import art.arcane.iris.core.lifecycle.WorldReplacementSeed;
import art.arcane.iris.util.common.scheduling.J;
import io.papermc.paper.world.saveddata.PaperLevelOverrides;
import io.papermc.paper.world.saveddata.PaperWorldMetadata;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.storage.PrimaryLevelData;
import net.minecraft.world.level.storage.SavedDataStorage;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.CraftServer;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
final class CurrentPaperWorldDataWriter {
private static final long SNAPSHOT_TIMEOUT_SECONDS = 30L;
private CurrentPaperWorldDataWriter() {
}
static void write(
Path sourceWorldDirectory,
Path targetWorldDirectory,
long seed
) throws IOException {
CraftServer craftServer = (CraftServer) Bukkit.getServer();
MinecraftServer server = craftServer.getHandle().getServer();
PaperLevelOverrides levelOverrides = captureLevelOverrides(craftServer, server);
Path targetWorld = targetWorldDirectory.toAbsolutePath().normalize();
UUID metadataUuid = UUID.randomUUID();
WorldReplacementSeed.copyWithAuthoritativeSeed(sourceWorldDirectory, targetWorld, seed);
try (SavedDataStorage savedDataStorage = new SavedDataStorage(
targetWorld.resolve("data"),
server.getFixerUpper(),
server.registryAccess()
)) {
savedDataStorage.set(PaperWorldMetadata.TYPE, new PaperWorldMetadata(metadataUuid));
savedDataStorage.set(PaperLevelOverrides.TYPE, levelOverrides);
}
List<Path> requiredDataFiles = List.of(
targetWorld.resolve("data/minecraft/world_gen_settings.dat"),
targetWorld.resolve("data/paper/metadata.dat"),
targetWorld.resolve("data/paper/level_overrides.dat")
);
for (Path requiredDataFile : requiredDataFiles) {
if (!Files.isRegularFile(requiredDataFile, LinkOption.NOFOLLOW_LINKS)) {
throw new IOException("Current Paper world data was not written: " + requiredDataFile);
}
}
long writtenSeed = WorldReplacementSeed.readAuthoritativeSeed(targetWorld);
if (writtenSeed != seed) {
throw new IOException("Current Paper world data did not retain the requested seed.");
}
try (SavedDataStorage verificationStorage = new SavedDataStorage(
targetWorld.resolve("data"),
server.getFixerUpper(),
server.registryAccess()
)) {
PaperWorldMetadata metadata = verificationStorage.get(PaperWorldMetadata.TYPE);
if (metadata == null || !metadataUuid.equals(metadata.uuid())) {
throw new IOException("Current Paper world metadata could not be verified.");
}
PaperLevelOverrides overrides = verificationStorage.get(PaperLevelOverrides.TYPE);
if (overrides == null || overrides.isInitialized()) {
throw new IOException("Current Paper level overrides could not be verified.");
}
}
}
private static PaperLevelOverrides captureLevelOverrides(
CraftServer craftServer,
MinecraftServer server
) throws IOException {
if (craftServer.isGlobalTickThread()) {
return createLevelOverrides(craftServer, server);
}
if (J.isFolia() && J.isPrimaryThread()) {
throw new IOException("Current Paper world data cannot be staged from a Folia region tick thread.");
}
CompletableFuture<PaperLevelOverrides> captured = new CompletableFuture<>();
boolean scheduled = J.runGlobal(() -> {
try {
captured.complete(createLevelOverrides(craftServer, server));
} catch (Throwable failure) {
captured.completeExceptionally(failure);
}
});
if (!scheduled) {
throw new IOException("Could not schedule the current Paper level-data snapshot on the global thread.");
}
try {
return captured.get(SNAPSHOT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (InterruptedException failure) {
Thread.currentThread().interrupt();
throw new IOException("Interrupted while capturing current Paper level data.", failure);
} catch (ExecutionException failure) {
throw new IOException("Could not capture current Paper level data.", failure.getCause());
} catch (TimeoutException failure) {
throw new IOException("Timed out while capturing current Paper level data.", failure);
}
}
private static PaperLevelOverrides createLevelOverrides(
CraftServer craftServer,
MinecraftServer server
) throws IOException {
if (!craftServer.isGlobalTickThread()) {
throw new IOException("Current Paper level data must be captured on the global tick thread.");
}
if (!(server.getWorldData().overworldData() instanceof PrimaryLevelData primaryLevelData)) {
throw new IOException("Paper primary level data is unavailable for current world data staging.");
}
return PaperLevelOverrides.createFromLiveLevelData(primaryLevelData);
}
}
@@ -154,7 +154,6 @@ import org.jetbrains.annotations.NotNull;
import java.awt.Color; import java.awt.Color;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@@ -1572,8 +1571,7 @@ public class NMSBinding implements INMSBinding {
injected.set(true); injected.set(true);
return true; return true;
} catch (Throwable e) { } catch (Throwable e) {
IrisLogging.error(C.RED + "Failed to inject Bukkit"); IrisLogging.reportError(C.RED + "Failed to inject Bukkit", e);
e.printStackTrace();
ResettableClassFileTransformer partialServerLevel = serverLevelTransformer; ResettableClassFileTransformer partialServerLevel = serverLevelTransformer;
ResettableClassFileTransformer partialStorageAccess = levelStorageAccessTransformer; ResettableClassFileTransformer partialStorageAccess = levelStorageAccessTransformer;
serverLevelTransformer = null; serverLevelTransformer = null;
@@ -1627,22 +1625,12 @@ public class NMSBinding implements INMSBinding {
try { try {
transformer.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION); transformer.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);
} catch (Throwable e) { } catch (Throwable e) {
IrisLogging.error(C.RED + "Failed to remove Bukkit world lifecycle injection"); IrisLogging.reportError(C.RED + "Failed to remove Bukkit world lifecycle injection", e);
e.printStackTrace();
} }
} }
} }
} }
@Override
public void writeCurrentPaperWorldData(
Path sourceWorldDirectory,
Path targetWorldDirectory,
long seed
) throws IOException {
CurrentPaperWorldDataWriter.write(sourceWorldDirectory, targetWorldDirectory, seed);
}
@Override @Override
public boolean awaitServerShutdownBoundary(long timeout, TimeUnit unit) { public boolean awaitServerShutdownBoundary(long timeout, TimeUnit unit) {
MinecraftServer server = ((CraftServer) Bukkit.getServer()).getHandle().getServer(); MinecraftServer server = ((CraftServer) Bukkit.getServer()).getHandle().getServer();
@@ -1,132 +0,0 @@
package art.arcane.iris.core.nms.v26_2_R1;
import art.arcane.iris.core.nms.INMSBinding;
import org.junit.Test;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class NMSBindingCurrentPaperWorldDataContractTest {
@Test
public void bindingDelegatesWithoutLinkingPaperSavedDataClasses() throws Exception {
String bindingSource = Files.readString(bindingSourcePath()).replace("\r\n", "\n");
String writer = section(
bindingSource,
"public void writeCurrentPaperWorldData(",
"public boolean awaitServerShutdownBoundary("
);
assertTrue(writer.contains("CurrentPaperWorldDataWriter.write("));
assertFalse(bindingSource.contains("PaperWorldMetadata"));
assertFalse(bindingSource.contains("PaperLevelOverrides"));
assertFalse(bindingSource.contains("io.papermc.paper.world.saveddata"));
InputStream classResource = NMSBindingCurrentPaperWorldDataContractTest.class
.getResourceAsStream("NMSBinding.class");
assertNotNull(classResource);
try (InputStream input = classResource) {
String classFile = new String(input.readAllBytes(), StandardCharsets.ISO_8859_1);
assertFalse(classFile.contains("PaperWorldMetadata"));
assertFalse(classFile.contains("PaperLevelOverrides"));
assertFalse(classFile.contains("io/papermc/paper/world/saveddata"));
}
}
@Test
public void stagesAllCurrentPaperWorldDataFromLiveServerState() throws Exception {
String writer = Files.readString(writerSourcePath()).replace("\r\n", "\n");
assertTrue(writer.contains("WorldReplacementSeed.copyWithAuthoritativeSeed("));
assertTrue(writer.contains("UUID metadataUuid = UUID.randomUUID()"));
assertTrue(writer.contains("new PaperWorldMetadata(metadataUuid)"));
assertTrue(writer.contains("captureLevelOverrides(craftServer, server)"));
assertTrue(writer.indexOf("captureLevelOverrides(craftServer, server)")
< writer.indexOf("WorldReplacementSeed.copyWithAuthoritativeSeed("));
assertTrue(writer.contains("new SavedDataStorage("));
assertTrue(writer.contains("server.getFixerUpper()"));
assertTrue(writer.contains("server.registryAccess()"));
assertTrue(writer.contains("data/minecraft/world_gen_settings.dat"));
assertTrue(writer.contains("data/paper/metadata.dat"));
assertTrue(writer.contains("data/paper/level_overrides.dat"));
assertTrue(writer.contains("WorldReplacementSeed.readAuthoritativeSeed(targetWorld)"));
assertTrue(writer.contains("verificationStorage.get(PaperWorldMetadata.TYPE)"));
assertTrue(writer.contains("metadataUuid.equals(metadata.uuid())"));
assertTrue(writer.contains("verificationStorage.get(PaperLevelOverrides.TYPE)"));
assertTrue(writer.contains("overrides == null || overrides.isInitialized()"));
assertTrue(writer.contains("Files.isRegularFile(requiredDataFile, LinkOption.NOFOLLOW_LINKS)"));
assertFalse(writer.toLowerCase().contains("migrat"));
assertFalse(writer.toLowerCase().contains("fallback"));
}
@Test
public void capturesOnlyLiveLevelOverridesOnTheGlobalThread() throws Exception {
String source = Files.readString(writerSourcePath()).replace("\r\n", "\n");
String capture = section(
source,
"private static PaperLevelOverrides captureLevelOverrides(",
"private static PaperLevelOverrides createLevelOverrides("
);
String create = section(
source,
"private static PaperLevelOverrides createLevelOverrides(",
"\n }\n}"
);
assertTrue(capture.contains("craftServer.isGlobalTickThread()"));
assertTrue(capture.contains("J.isFolia() && J.isPrimaryThread()"));
assertTrue(capture.contains("J.runGlobal("));
assertTrue(capture.contains("createLevelOverrides(craftServer, server)"));
assertTrue(capture.contains("captured.get(SNAPSHOT_TIMEOUT_SECONDS"));
assertTrue(capture.contains("Thread.currentThread().interrupt()"));
assertTrue(create.contains("if (!craftServer.isGlobalTickThread())"));
assertTrue(create.indexOf("if (!craftServer.isGlobalTickThread())")
< create.indexOf("server.getWorldData().overworldData()"));
assertTrue(create.contains("PaperLevelOverrides.createFromLiveLevelData(primaryLevelData)"));
assertFalse(capture.contains("WorldReplacementSeed"));
assertFalse(capture.contains("SavedDataStorage"));
assertFalse(capture.contains("Files."));
assertFalse(create.contains("WorldReplacementSeed"));
assertFalse(create.contains("SavedDataStorage"));
assertFalse(create.contains("Files."));
}
@Test
public void unsupportedBindingsRejectCurrentPaperWorldDataStaging() {
INMSBinding binding = (INMSBinding) Proxy.newProxyInstance(
INMSBinding.class.getClassLoader(),
new Class<?>[]{INMSBinding.class},
(proxy, method, arguments) -> InvocationHandler.invokeDefault(proxy, method, arguments)
);
UnsupportedOperationException error = assertThrows(
UnsupportedOperationException.class,
() -> binding.writeCurrentPaperWorldData(Path.of("source"), Path.of("target"), 1L)
);
assertTrue(error.getMessage().contains("does not support current Paper world data staging"));
}
private static Path bindingSourcePath() {
return Path.of(System.getProperty("iris.nmsBindingSource"));
}
private static Path writerSourcePath() {
return bindingSourcePath().resolveSibling("CurrentPaperWorldDataWriter.java");
}
private static String section(String source, String startMarker, String endMarker) {
int start = source.indexOf(startMarker);
int end = source.indexOf(endMarker, start);
assertTrue("Missing source section starting with " + startMarker, start >= 0);
assertTrue("Missing source section ending with " + endMarker, end > start);
return source.substring(start, end);
}
}
@@ -172,9 +172,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
try { try {
InstanceState.updateInstanceId(); InstanceState.updateInstanceId();
} catch (Throwable ex) { } catch (Throwable ex) {
System.err.println("[Iris] Failed to update instance id: " + ex.getClass().getSimpleName() IrisLogging.reportError("Failed to update the Iris instance id.", ex);
+ (ex.getMessage() == null ? "" : " - " + ex.getMessage()));
ex.printStackTrace();
} }
} }
@@ -350,7 +348,6 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
try { try {
object.run(); object.run();
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace();
Iris.reportError(e); Iris.reportError(e);
} }
}, RNG.r.i(100, 1200)); }, RNG.r.i(100, 1200));
@@ -490,7 +487,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
pw.close(); pw.close();
Iris.info("DUMPED! See " + fi.getAbsolutePath()); Iris.info("DUMPED! See " + fi.getAbsolutePath());
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); Iris.reportError("Failed to write the Iris thread dump.", e);
} }
} }
@@ -661,7 +658,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
(EngineWorldManagerProvider) IrisWorldManager::new); (EngineWorldManagerProvider) IrisWorldManager::new);
IrisServices.register(WorldDeletionQueue.class, pendingWorldDeletes); IrisServices.register(WorldDeletionQueue.class, pendingWorldDeletes);
IrisServices.register(ManagedWorldLoader.class, (ManagedWorldLoader) this::loadManagedWorld); IrisServices.register(ManagedWorldLoader.class, (ManagedWorldLoader) this::loadManagedWorld);
SettingsHotloadWatch watch = new SettingsHotloadWatch(getDataFile("settings.json")); SettingsHotloadWatch watch = new SettingsHotloadWatch(getDataFile("iris.json"));
settingsHotloadWatch = watch; settingsHotloadWatch = watch;
// Stale-temp cleanup must complete before services enable: StudioSVC.onEnable downloads // Stale-temp cleanup must complete before services enable: StudioSVC.onEnable downloads
// packs through cache/temp on an async thread, and a concurrent delete of that folder // packs through cache/temp on an async thread, and a concurrent delete of that folder
@@ -774,7 +771,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
private void autoStartStudio() { private void autoStartStudio() {
if (IrisSettings.get().getStudio().isAutoStartDefaultStudio()) { if (IrisSettings.get().getStudio().isAutoStartDefaultStudio()) {
Iris.info("Starting up auto Studio!"); Iris.debug("Starting up auto Studio!");
try { try {
Player r = new KList<>(getServer().getOnlinePlayers()).getRandom(); Player r = new KList<>(getServer().getOnlinePlayers()).getRandom();
Iris.service(StudioSVC.class).open(r != null ? new VolmitSender(r) : getSender(), 1337, IrisSettings.get().getGenerator().getDefaultWorldType(), (w) -> { Iris.service(StudioSVC.class).open(r != null ? new VolmitSender(r) : getSender(), 1337, IrisSettings.get().getGenerator().getDefaultWorldType(), (w) -> {
@@ -782,7 +779,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
final Location spawn = w.getSpawnLocation(); final Location spawn = w.getSpawnLocation();
for (Player i : getServer().getOnlinePlayers()) { for (Player i : getServer().getOnlinePlayers()) {
final Runnable playerTask = () -> { final Runnable playerTask = () -> {
i.setGameMode(GameMode.CREATIVE); i.setGameMode(GameMode.SPECTATOR);
BukkitPlatform.teleportAsync(i, spawn); BukkitPlatform.teleportAsync(i, spawn);
}; };
if (!J.runEntity(i, playerTask)) { if (!J.runEntity(i, playerTask)) {
@@ -802,10 +799,9 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
audiences = new Bindings.Adventure(this); audiences = new Bindings.Adventure(this);
BukkitPlatform.hostAudiences(audiences); BukkitPlatform.hostAudiences(audiences);
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace();
IrisSettings.get().getGeneral().setUseConsoleCustomColors(false); IrisSettings.get().getGeneral().setUseConsoleCustomColors(false);
IrisSettings.get().getGeneral().setUseCustomColorsIngame(false); IrisSettings.get().getGeneral().setUseCustomColorsIngame(false);
Iris.error("Failed to setup Adventure API... No custom colors :("); Iris.reportError("Failed to set up Adventure; custom colors are disabled.", e);
} }
} }
@@ -903,14 +899,14 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
if (IrisToolbelt.isServerStopping()) { if (IrisToolbelt.isServerStopping()) {
quiesceRuntimeForServerShutdown("pre-unload:" + reason); quiesceRuntimeForServerShutdown("pre-unload:" + reason);
startPostStopFinisher(); startPostStopFinisher();
Iris.info("Pre-unload hook deferred generator teardown until Paper closes its chunk schedulers."); Iris.debug("Pre-unload hook deferred generator teardown until Paper closes its chunk schedulers.");
return; return;
} }
if (alreadyDrained.get()) { if (alreadyDrained.get()) {
Iris.info("Pre-unload hook skipped; Iris already drained."); Iris.debug("Pre-unload hook skipped; Iris already drained.");
return; return;
} }
Iris.info("BileTools pre-unload hook fired (" + reason + "). Freezing all Iris worlds."); Iris.debug("BileTools pre-unload hook fired (" + reason + "). Freezing all Iris worlds.");
drainOnce("pre-unload:" + reason, 45L); drainOnce("pre-unload:" + reason, 45L);
} }
@@ -1088,7 +1084,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
} }
} }
if (generators.isEmpty()) { if (generators.isEmpty()) {
Iris.info("No Iris worlds to freeze."); Iris.debug("No Iris worlds to freeze.");
return; return;
} }
@@ -1112,7 +1108,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
try { try {
CompletableFuture.allOf(closes.toArray(new CompletableFuture<?>[0])) CompletableFuture.allOf(closes.toArray(new CompletableFuture<?>[0]))
.get(timeoutSeconds, TimeUnit.SECONDS); .get(timeoutSeconds, TimeUnit.SECONDS);
Iris.info("All Iris chunk generators parked. Safe to unload."); Iris.debug("All Iris chunk generators parked. Safe to unload.");
} catch (TimeoutException e) { } catch (TimeoutException e) {
Iris.warn("Iris generator drain timed out after " + timeoutSeconds + "s; unload proceeding anyway."); Iris.warn("Iris generator drain timed out after " + timeoutSeconds + "s; unload proceeding anyway.");
} catch (InterruptedException e) { } catch (InterruptedException e) {
@@ -1201,7 +1197,6 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
try { try {
Iris.syncJobs.next().run(); Iris.syncJobs.next().run();
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace();
Iris.reportError(e); Iris.reportError(e);
} }
} }
@@ -1209,7 +1204,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
} }
private void bstats() { private void bstats() {
if (IrisSettings.get().getGeneral().isPluginMetrics()) { if (IrisSettings.get().getGeneral().isMetrics()) {
Bindings.setupBstats(this); Bindings.setupBstats(this);
} }
} }
@@ -242,7 +242,7 @@ public class CommandDeveloper implements DirectorExecutor {
try (CountingDataInputStream in = CountingDataInputStream.wrap(new BufferedInputStream(new FileInputStream(base)))) { try (CountingDataInputStream in = CountingDataInputStream.wrap(new BufferedInputStream(new FileInputStream(base)))) {
TectonicPlate.read(1088, in, true, IrisEngineMantle.createRuntimeDataAdapter(activeEngine.getData()), IrisEngineMantle.createRuntimeHooks()); TectonicPlate.read(1088, in, true, IrisEngineMantle.createRuntimeDataAdapter(activeEngine.getData()), IrisEngineMantle.createRuntimeHooks());
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); Iris.reportError("Failed to inspect the Iris tectonic plate.", e);
} }
} else { } else {
Matter.read(section); Matter.read(section);
@@ -282,7 +282,7 @@ public class CommandDeveloper implements DirectorExecutor {
MCAFile MCARegion = MCAUtil.read(mca); MCAFile MCARegion = MCAUtil.read(mca);
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); Iris.reportError("Failed to inspect Minecraft region files.", e);
} }
} }
@@ -339,7 +339,7 @@ public class CommandDeveloper implements DirectorExecutor {
} }
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); Iris.reportError("Failed to inspect server network interfaces.", e);
} }
} }
@@ -22,22 +22,16 @@ import art.arcane.iris.Iris;
import art.arcane.iris.core.BukkitWorldReconciler; import art.arcane.iris.core.BukkitWorldReconciler;
import art.arcane.iris.platform.bukkit.BukkitPlatform; import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.IrisStartupValidation;
import art.arcane.iris.core.DatapackInstallResult;
import art.arcane.iris.core.IrisWorldStorage; import art.arcane.iris.core.IrisWorldStorage;
import art.arcane.iris.core.IrisWorlds; import art.arcane.iris.core.IrisWorlds;
import art.arcane.iris.core.PendingWorldReplacementManager; import art.arcane.iris.core.PendingWorldReplacementManager;
import art.arcane.iris.core.ServerConfigurator; import art.arcane.iris.core.ServerConfigurator;
import art.arcane.iris.core.lifecycle.BukkitWorldConfiguration;
import art.arcane.iris.core.lifecycle.IrisWorldRemovalService; import art.arcane.iris.core.lifecycle.IrisWorldRemovalService;
import art.arcane.iris.core.lifecycle.LifecycleOperationCoordinator; import art.arcane.iris.core.lifecycle.LifecycleOperationCoordinator;
import art.arcane.iris.core.lifecycle.WorldLifecycleService; import art.arcane.iris.core.lifecycle.WorldLifecycleService;
import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.core.pack.AtomicDirectoryPublisher;
import art.arcane.iris.core.pack.PackDownloader; import art.arcane.iris.core.pack.PackDownloader;
import art.arcane.iris.core.pack.PackDirectoryResolver; import art.arcane.iris.core.pack.PackDirectoryResolver;
import art.arcane.iris.core.pack.PackValidationRegistry;
import art.arcane.iris.core.service.StudioSVC; import art.arcane.iris.core.service.StudioSVC;
import art.arcane.iris.core.tools.IrisToolbelt; import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.platform.PlatformChunkGenerator; import art.arcane.iris.engine.platform.PlatformChunkGenerator;
@@ -62,9 +56,7 @@ import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -160,16 +152,6 @@ public class CommandIris implements DirectorExecutor {
return; return;
} }
if (J.isFolia()) {
boolean staged = stageFoliaWorldCreation(worldName, dimension, seed);
if (!staged) {
return;
}
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_WORLD_STAGING_COMPLETED_RESTARTING_SERVER_GENERATE_LOAD, MessageArgument.untrusted("worldName", worldName)));
ServerConfigurator.restart("Iris staged Folia world \"" + worldName + "\" for startup.");
return;
}
try { try {
IrisToolbelt.createWorld() IrisToolbelt.createWorld()
.dimension(resolvedType) .dimension(resolvedType)
@@ -263,116 +245,6 @@ public class CommandIris implements DirectorExecutor {
} }
} }
private boolean stageFoliaWorldCreation(String name, IrisDimension dimension, long seed) {
try {
IrisStartupValidation.requireWorldCreationReady();
PackValidationRegistry.requireLoadable(
dimension.getLoader().getDataFolder().getName());
} catch (RuntimeException exception) {
sender().sendMessage(C.RED + exception.getMessage());
return false;
}
NamespacedKey worldKey = IrisWorldStorage.managedKeyFromName(name);
LifecycleOperationCoordinator.Lease worldLease = null;
File worldFolder = IrisWorldStorage.requireSafeManagedDimensionRoot(worldKey);
Path stagedWorld = null;
try {
LifecycleOperationCoordinator coordinator = LifecycleOperationCoordinator.get();
worldLease = coordinator.acquire(
LifecycleOperationCoordinator.Domain.WORLD_MUTATION,
LifecycleOperationCoordinator.OperationKind.WORLD_CREATE,
worldKey.toString());
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_RUNTIME_WORLD_CREATION_IS_DISABLED_ON_FOLIA));
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_PREPARING_WORLD_FILES_BUKKIT_YML_NEXT_STARTUP));
if (worldFolder.exists()) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_THAT_FOLDER_ALREADY_EXISTS));
return false;
}
DatapackInstallResult datapackResult = ServerConfigurator.installDataPacksIfChanged(true);
if (!datapackResult.succeeded()) {
sender().sendMessage(C.RED + "Failed to compile the Iris datapack. No world files were staged.");
return false;
}
Path targetWorld = worldFolder.toPath().toAbsolutePath().normalize();
Path namespaceRoot = targetWorld.getParent();
if (namespaceRoot == null) {
throw new IOException("Iris world target has no namespace directory: " + targetWorld);
}
Files.createDirectories(namespaceRoot);
stagedWorld = Files.createTempDirectory(namespaceRoot, ".iris-create-" + worldKey.getKey() + "-");
Path sourceOverworld = IrisWorldStorage.dimensionRoot(
IrisWorldStorage.levelRoot(),
NamespacedKey.minecraft("overworld")
).toPath();
INMS.get().writeCurrentPaperWorldData(sourceOverworld, stagedWorld, seed);
IrisDimension installed = Iris.service(StudioSVC.class).installIntoWorld(
sender(),
dimension,
stagedWorld.toFile()
);
if (installed == null) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_FAILED_STAGE_WORLD_FILES_DIMENSION, MessageArgument.untrusted("value", dimension.getLoadKey())));
return false;
}
try (AtomicDirectoryPublisher.Publication publication = AtomicDirectoryPublisher.publishAbsent(
stagedWorld,
targetWorld
)) {
stagedWorld = null;
if (!registerWorldInBukkitYml(worldKey, dimension.getLoadKey(), seed)) {
return false;
}
publication.commit();
}
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_STAGED_IRIS_WORLD_WITH_GENERATOR_IRIS_SEED, MessageArgument.untrusted("name", name), MessageArgument.untrusted("value", dimension.getLoadKey()), MessageArgument.untrusted("seed", seed)));
return true;
} catch (LifecycleOperationCoordinator.BusyException e) {
sender().sendMessage(C.YELLOW + e.getMessage());
return false;
} catch (Throwable e) {
sender().sendMessage(C.RED + "Failed to stage the complete Iris world: " + e.getMessage());
Iris.reportError("Failed to stage complete Folia world \"" + worldKey + "\".", e);
return false;
} finally {
if (stagedWorld != null) {
deleteDirectorySafely(stagedWorld.toFile());
}
if (worldLease != null) {
worldLease.close();
}
}
}
private boolean registerWorldInBukkitYml(NamespacedKey worldKey, String dimension, Long seed) {
String configuredWorldName = IrisWorldStorage.configuredWorldName(
worldKey,
IrisWorldStorage.levelRoot().getName()
);
try {
BukkitWorldConfiguration.register(BUKKIT_YML, configuredWorldName, dimension, seed);
Iris.info("Registered \"" + configuredWorldName + "\" in bukkit.yml");
return true;
} catch (IOException e) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_FAILED_UPDATE_BUKKIT_YML, MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
Iris.error("Failed to update bukkit.yml!");
Iris.reportError(e);
return false;
}
}
private void deleteDirectorySafely(File directory) {
try {
AtomicDirectoryPublisher.deleteTree(directory.toPath());
} catch (IOException e) {
Iris.reportError("Failed to roll back staged world folder \"" + directory.getAbsolutePath() + "\".", e);
}
}
private boolean reportExpectedCreationInterruption(Throwable failure) { private boolean reportExpectedCreationInterruption(Throwable failure) {
Throwable current = failure; Throwable current = failure;
while (current != null) { while (current != null) {
@@ -24,6 +24,7 @@ import art.arcane.iris.core.link.WorldEditLink;
import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.pack.PackDirectoryResolver; import art.arcane.iris.core.pack.PackDirectoryResolver;
import art.arcane.iris.core.runtime.ObjectStudioActivation; import art.arcane.iris.core.runtime.ObjectStudioActivation;
import art.arcane.iris.core.runtime.StudioOpenCoordinator;
import art.arcane.iris.core.runtime.WorldRuntimeControlService; import art.arcane.iris.core.runtime.WorldRuntimeControlService;
import art.arcane.iris.core.service.ObjectSVC; import art.arcane.iris.core.service.ObjectSVC;
import art.arcane.iris.core.service.StudioSVC; import art.arcane.iris.core.service.StudioSVC;
@@ -171,24 +172,29 @@ public class CommandObject implements DirectorExecutor {
IrisDimension finalHost = hostDimension; IrisDimension finalHost = hostDimension;
try { try {
Iris.service(StudioSVC.class).open(commandSender, seed, hostDimension.getLoadKey(), world -> { Iris.service(StudioSVC.class).open(
if (world == null) return; commandSender,
try { seed,
WorldRuntimeControlService.get().applyObjectStudioWorldRules(world); hostDimension.getLoadKey(),
} catch (Throwable e) { StudioOpenCoordinator.StudioOpenKind.OBJECT,
Iris.reportError("Failed to apply object studio world rules for " + world.getName(), e); world -> {
} if (world == null) return;
try {
WorldRuntimeControlService.get().applyObjectStudioWorldRules(world);
} catch (Throwable e) {
Iris.reportError("Failed to apply object studio world rules for " + world.getName(), e);
}
if (commandSender.isPlayer()) { if (commandSender.isPlayer()) {
Player p = commandSender.player(); Player p = commandSender.player();
if (p != null) { if (p != null) {
Location target = new Location(world, 0.5D, 66D, 0.5D); Location target = new Location(world, 0.5D, 66D, 0.5D);
J.runEntity(p, () -> { J.runEntity(p, () -> {
BukkitPlatform.teleportAsync(p, target).thenRun(() -> p.setGameMode(GameMode.CREATIVE)); BukkitPlatform.teleportAsync(p, target).thenRun(() -> p.setGameMode(GameMode.CREATIVE));
}); });
} }
} }
}); });
} catch (Throwable e) { } catch (Throwable e) {
Iris.reportError("Failed to open object studio world \"" + finalHost.getLoadKey() + "\".", e); Iris.reportError("Failed to open object studio world \"" + finalHost.getLoadKey() + "\".", e);
commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_OBJECT_FAILED_OPEN_OBJECT_STUDIO, MessageArgument.untrusted("value", String.valueOf(e.getMessage())))); commandSender.sendMessage(IrisLanguage.text(BukkitCommandMessages.COMMAND_OBJECT_FAILED_OPEN_OBJECT_STUDIO, MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
@@ -360,7 +366,7 @@ public class CommandObject implements DirectorExecutor {
o.write(o.getLoadFile()); o.write(o.getLoadFile());
} catch (IOException e) { } catch (IOException e) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_FAILED_SAVE_OBJECT, MessageArgument.untrusted("value", o.getLoadFile()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage())))); sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_OBJECT_FAILED_SAVE_OBJECT, MessageArgument.untrusted("value", o.getLoadFile()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage()))));
e.printStackTrace(); Iris.reportError("Failed to save object " + o.getLoadFile() + ".", e);
} }
} }
@@ -426,7 +432,7 @@ public class CommandObject implements DirectorExecutor {
try { try {
IrisConverter.convertSchematics(sender()); IrisConverter.convertSchematics(sender());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); Iris.reportError("Failed to convert schematics to Iris objects.", e);
} }
} }
@@ -89,7 +89,6 @@ public class CommandPregen implements DirectorExecutor {
} catch (Throwable e) { } catch (Throwable e) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_PREGEN_FAILED_START_PREGENERATION_SEE_CONSOLE_DETAILS)); sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_PREGEN_FAILED_START_PREGENERATION_SEE_CONSOLE_DETAILS));
Iris.reportError(e); Iris.reportError(e);
e.printStackTrace();
} }
} }
@@ -77,7 +77,6 @@ import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Chunk; import org.bukkit.Chunk;
import org.bukkit.FluidCollisionMode; import org.bukkit.FluidCollisionMode;
import org.bukkit.GameMode;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -573,7 +572,6 @@ public class CommandStudio implements DirectorExecutor {
IO.writeAll(report, fileText.toString("\n")); IO.writeAll(report, fileText.toString("\n"));
} catch (IOException e) { } catch (IOException e) {
Iris.reportError(e); Iris.reportError(e);
e.printStackTrace();
} }
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_DONE, MessageArgument.untrusted("value", report.getPath()))); sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_DONE, MessageArgument.untrusted("value", report.getPath())));
@@ -663,9 +661,6 @@ public class CommandStudio implements DirectorExecutor {
Iris.reportError("Studio teleport failed for player \"" + player.getName() + "\".", failure); Iris.reportError("Studio teleport failed for player \"" + player.getName() + "\".", failure);
return; return;
} }
if (Boolean.TRUE.equals(teleported)) {
J.runEntity(player, () -> player.setGameMode(GameMode.CREATIVE));
}
}); });
} }
@@ -831,7 +826,6 @@ public class CommandStudio implements DirectorExecutor {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_REPORTED, MessageArgument.untrusted("value", ff.getPath()))); sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_REPORTED, MessageArgument.untrusted("value", ff.getPath())));
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace();
Iris.reportError(e); Iris.reportError(e);
} }
} }
@@ -18,14 +18,17 @@
package art.arcane.iris.core.gui; package art.arcane.iris.core.gui;
import art.arcane.iris.platform.bukkit.BukkitPlatform; import art.arcane.iris.core.runtime.WorldRuntimeControlService;
import art.arcane.iris.platform.bukkit.BukkitWorldBinding;
import art.arcane.iris.engine.IrisComplex; import art.arcane.iris.engine.IrisComplex;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.render.RenderType; import art.arcane.iris.engine.framework.render.RenderType;
import art.arcane.iris.engine.object.IrisWorld; import art.arcane.iris.engine.object.IrisWorld;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.platform.bukkit.BukkitWorldBinding;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.util.common.scheduling.J; import art.arcane.iris.util.common.scheduling.J;
import art.arcane.volmlib.util.format.Form; import art.arcane.volmlib.util.format.Form;
import org.bukkit.Chunk;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
@@ -36,15 +39,21 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer; import java.util.function.Consumer;
import static art.arcane.iris.util.common.data.registry.Attributes.MAX_HEALTH; import static art.arcane.iris.util.common.data.registry.Attributes.MAX_HEALTH;
public final class BukkitVisionOverlay implements GuiOverlay { public final class BukkitVisionOverlay implements GuiOverlay {
private final Engine engine; private final Engine engine;
private final AtomicBoolean nativeTeleportActive = new AtomicBoolean();
private final AtomicBoolean playerRefreshQueued = new AtomicBoolean(); private final AtomicBoolean playerRefreshQueued = new AtomicBoolean();
private final AtomicLong teleportSequence = new AtomicLong();
private final AtomicReference<VisionTeleportRequest> latestTeleport = new AtomicReference<>();
private volatile List<GuiMarker> playerMarkers = List.of(); private volatile List<GuiMarker> playerMarkers = List.of();
public BukkitVisionOverlay(Engine engine) { public BukkitVisionOverlay(Engine engine) {
@@ -144,25 +153,198 @@ public final class BukkitVisionOverlay implements GuiOverlay {
@Override @Override
public void teleport(double worldX, double worldZ) { public void teleport(double worldX, double worldZ) {
IrisWorld target = engine.getWorld(); VisionTeleportRequest request = new VisionTeleportRequest(
if (!target.hasPlatformWorld()) { teleportSequence.incrementAndGet(),
VisionGUI.floorWorldCoordinate(worldX),
VisionGUI.floorWorldCoordinate(worldZ));
latestTeleport.set(request);
startTeleport(request);
}
private void startTeleport(VisionTeleportRequest request) {
if (!request.processing.compareAndSet(false, true)) {
return; return;
} }
J.runGlobal(() -> { boolean scheduled = J.runGlobal(() -> {
IrisWorld target = engine.getWorld();
if (!isCurrent(request, target)) {
finish(request);
return;
}
World world = BukkitWorldBinding.world(target);
if (world == null) {
finish(request);
return;
}
List<Player> players = BukkitWorldBinding.players(target); List<Player> players = BukkitWorldBinding.players(target);
if (players.isEmpty()) { if (players.isEmpty()) {
finish(request);
return; return;
} }
Player player = players.get(0); Player player = players.get(0);
World world = player.getWorld(); requestTeleportChunk(request, target, player, world);
int xx = (int) worldX;
int zz = (int) worldZ;
J.runRegion(world, xx >> 4, zz >> 4, () -> {
int yy = world.getHighestBlockYAt(xx, zz) + 1;
Location destination = new Location(world, xx, yy, zz);
J.runEntity(player, () -> BukkitPlatform.teleportAsync(player, destination));
});
}); });
if (!scheduled) {
finish(request);
}
}
private void requestTeleportChunk(
VisionTeleportRequest request,
IrisWorld target,
Player player,
World world
) {
int blockX = request.blockX;
int blockZ = request.blockZ;
int chunkX = blockX >> 4;
int chunkZ = blockZ >> 4;
CompletableFuture<Chunk> requested;
try {
requested = WorldRuntimeControlService.get().requestChunkAsync(
world,
chunkX,
chunkZ,
true,
true
);
} catch (Throwable failure) {
fail(request, target, world, failure);
return;
}
if (requested == null) {
fail(request, target, world, new IllegalStateException(
"Vision destination chunk request returned no future."));
return;
}
requested.whenComplete((chunk, failure) -> {
if (!isCurrent(request, target)) {
finish(request);
return;
}
if (failure != null) {
fail(request, target, world, failure);
return;
}
if (chunk == null || chunk.getWorld() != world) {
fail(request, target, world, new IllegalStateException(
"Vision destination chunk request returned no chunk."));
return;
}
boolean scheduled = J.runRegion(world, chunkX, chunkZ, () -> {
if (!isCurrent(request, target)) {
finish(request);
return;
}
int yy = world.getHighestBlockYAt(blockX, blockZ) + 1;
Location destination = new Location(world, blockX, yy, blockZ);
if (!J.runEntity(player, () -> delegateTeleport(
request,
target,
player,
world,
destination))) {
fail(request, target, world, new IllegalStateException(
"Failed to schedule the Vision teleport on the player entity."));
}
});
if (!scheduled) {
fail(request, target, world, new IllegalStateException(
"Failed to schedule the Vision surface lookup on its owning region."));
}
});
}
private void delegateTeleport(
VisionTeleportRequest request,
IrisWorld target,
Player player,
World world,
Location destination
) {
if (!isCurrent(request, target) || !player.isOnline() || player.getWorld() != world) {
finish(request);
return;
}
if (!nativeTeleportActive.compareAndSet(false, true)) {
finish(request);
return;
}
if (!isCurrent(request, target)) {
nativeTeleportActive.set(false);
finish(request);
restartLatest(request);
return;
}
CompletableFuture<Boolean> teleport;
try {
teleport = BukkitPlatform.teleportAsync(player, destination);
} catch (Throwable failure) {
nativeTeleportActive.set(false);
fail(request, target, world, failure);
restartLatest(request);
return;
}
if (teleport == null) {
nativeTeleportActive.set(false);
fail(request, target, world, new IllegalStateException(
"Vision teleport returned no completion future."));
restartLatest(request);
return;
}
teleport.whenComplete((success, failure) -> {
nativeTeleportActive.set(false);
finish(request);
if (isCurrent(request, target)) {
if (failure != null) {
reportTeleportFailure(world, request.blockX, request.blockZ, failure);
} else if (!Boolean.TRUE.equals(success)) {
reportTeleportFailure(world, request.blockX, request.blockZ, new IllegalStateException(
"Vision teleport did not complete successfully."));
}
}
restartLatest(request);
});
}
private boolean isCurrent(VisionTeleportRequest request, IrisWorld target) {
VisionTeleportRequest current = latestTeleport.get();
return current != null
&& current.sequence == request.sequence
&& target != null
&& engine.getWorld() == target
&& target.hasPlatformWorld()
&& !engine.isClosing()
&& !engine.isClosed();
}
private void fail(
VisionTeleportRequest request,
IrisWorld target,
World world,
Throwable failure
) {
finish(request);
if (isCurrent(request, target)) {
reportTeleportFailure(world, request.blockX, request.blockZ, failure);
}
}
private void finish(VisionTeleportRequest request) {
request.processing.set(false);
}
private void restartLatest(VisionTeleportRequest completed) {
VisionTeleportRequest current = latestTeleport.get();
if (current != null && current != completed) {
startTeleport(current);
}
}
private void reportTeleportFailure(World world, int blockX, int blockZ, Throwable failure) {
IrisLogging.reportError("Vision could not teleport to " + world.getName() + "@"
+ blockX + "," + blockZ + ".", failure);
} }
@Override @Override
@@ -179,4 +361,18 @@ public final class BukkitVisionOverlay implements GuiOverlay {
}; };
return file == null ? null : file.getName(); return file == null ? null : file.getName();
} }
private static final class VisionTeleportRequest {
private final long sequence;
private final int blockX;
private final int blockZ;
private final AtomicBoolean processing;
private VisionTeleportRequest(long sequence, int blockX, int blockZ) {
this.sequence = sequence;
this.blockX = blockX;
this.blockZ = blockZ;
processing = new AtomicBoolean(false);
}
}
} }
@@ -605,7 +605,6 @@ public final class IrisEngineSVC implements IrisService {
private static void reportFailure(String message, Throwable exception) { private static void reportFailure(String message, Throwable exception) {
IrisLogging.reportError(exception); IrisLogging.reportError(exception);
IrisLogging.error("EngineSVC: " + message); IrisLogging.error("EngineSVC: " + message);
exception.printStackTrace();
} }
private final class Registered { private final class Registered {
@@ -20,6 +20,7 @@ package art.arcane.iris.core.service;
import art.arcane.iris.Iris; import art.arcane.iris.Iris;
import art.arcane.iris.core.protocol.EngineResolver; import art.arcane.iris.core.protocol.EngineResolver;
import art.arcane.iris.core.protocol.IrisCursorRequestService;
import art.arcane.iris.core.protocol.IrisProtocolServer; import art.arcane.iris.core.protocol.IrisProtocolServer;
import art.arcane.iris.core.protocol.IrisServerTransport; import art.arcane.iris.core.protocol.IrisServerTransport;
import art.arcane.iris.core.protocol.IrisSession; import art.arcane.iris.core.protocol.IrisSession;
@@ -55,6 +56,7 @@ public class IrisProtocolService implements IrisService, PluginMessageListener,
private IrisSessionRegistry registry; private IrisSessionRegistry registry;
private IrisProtocolServer protocolServer; private IrisProtocolServer protocolServer;
private IrisCursorRequestService cursorService;
private IrisVisionRequestService visionService; private IrisVisionRequestService visionService;
@Override @Override
@@ -66,6 +68,8 @@ public class IrisProtocolService implements IrisService, PluginMessageListener,
protocolServer = new IrisProtocolServer(registry, SERVER_CAPABILITIES, brand(), true); protocolServer = new IrisProtocolServer(registry, SERVER_CAPABILITIES, brand(), true);
EngineResolver engineResolver = IrisProtocolService::resolveEngine; EngineResolver engineResolver = IrisProtocolService::resolveEngine;
protocolServer.setEngineResolver(engineResolver); protocolServer.setEngineResolver(engineResolver);
cursorService = IrisCursorRequestService.create(engineResolver, registry);
protocolServer.setCursorInfoHandler(cursorService);
visionService = IrisVisionRequestService.create(engineResolver, registry); visionService = IrisVisionRequestService.create(engineResolver, registry);
protocolServer.setVisionTileHandler(visionService); protocolServer.setVisionTileHandler(visionService);
IrisServices.register(IrisProtocolServer.class, protocolServer); IrisServices.register(IrisProtocolServer.class, protocolServer);
@@ -81,13 +85,22 @@ public class IrisProtocolService implements IrisService, PluginMessageListener,
messenger.unregisterIncomingPluginChannel(Iris.instance, IrisProtocol.CHANNEL, this); messenger.unregisterIncomingPluginChannel(Iris.instance, IrisProtocol.CHANNEL, this);
messenger.unregisterOutgoingPluginChannel(Iris.instance, IrisProtocol.CHANNEL); messenger.unregisterOutgoingPluginChannel(Iris.instance, IrisProtocol.CHANNEL);
IrisSessionRegistry current = registry; IrisSessionRegistry current = registry;
IrisCursorRequestService cursor = cursorService;
IrisVisionRequestService vision = visionService;
if (current != null) { if (current != null) {
for (IrisSession session : current.all()) { for (IrisSession session : current.all()) {
current.unregister(session.id()); current.unregister(session.id());
if (cursor != null) {
cursor.clearSession(session.id());
}
if (vision != null) {
vision.clearSession(session.id());
}
} }
} }
registry = null; registry = null;
protocolServer = null; protocolServer = null;
cursorService = null;
visionService = null; visionService = null;
} }
@@ -117,6 +130,10 @@ public class IrisProtocolService implements IrisService, PluginMessageListener,
} }
String sessionId = event.getPlayer().getUniqueId().toString(); String sessionId = event.getPlayer().getUniqueId().toString();
current.unregister(sessionId); current.unregister(sessionId);
IrisCursorRequestService cursor = cursorService;
if (cursor != null) {
cursor.clearSession(sessionId);
}
IrisVisionRequestService vision = visionService; IrisVisionRequestService vision = visionService;
if (vision != null) { if (vision != null) {
vision.clearSession(sessionId); vision.clearSession(sessionId);
@@ -51,6 +51,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.block.Action; import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemFlag;
@@ -61,18 +62,30 @@ import org.bukkit.util.BlockVector;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
import java.awt.Color; import java.awt.Color;
import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import static art.arcane.iris.util.common.data.registry.Particles.CRIT_MAGIC; import static art.arcane.iris.util.common.data.registry.Particles.CRIT_MAGIC;
import static art.arcane.iris.util.common.data.registry.Particles.REDSTONE; import static art.arcane.iris.util.common.data.registry.Particles.REDSTONE;
public class WandSVC implements IrisService { public class WandSVC implements IrisService {
private static final int MS_PER_TICK = Integer.parseInt(System.getProperty("iris.ms_per_tick", "30")); private static final int MS_PER_TICK = Integer.parseInt(System.getProperty("iris.ms_per_tick", "30"));
private static final int PLAYER_RESCAN_INTERVAL_TICKS = 100;
private static ItemStack dust; private static ItemStack dust;
private static ItemStack wand; private static ItemStack wand;
private final Map<UUID, Player> activePlayers = new ConcurrentHashMap<>();
private final AtomicBoolean playerRescanScheduled = new AtomicBoolean(false);
private volatile boolean enabled;
private int taskId = -1;
private int ticksUntilPlayerRescan = 0;
public static void pasteSchematic(IrisObject s, Location at) { public static void pasteSchematic(IrisObject s, Location at) {
s.place(at); s.place(at);
} }
@@ -174,7 +187,6 @@ public class WandSVC implements IrisService {
return s; return s;
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace();
Iris.reportError(e); Iris.reportError(e);
} }
@@ -196,7 +208,6 @@ public class WandSVC implements IrisService {
return WorldMatter.createMatter(p.getName(), f[0], f[1]); return WorldMatter.createMatter(p.getName(), f[0], f[1]);
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace();
Iris.reportError(e); Iris.reportError(e);
} }
@@ -307,8 +318,18 @@ public class WandSVC implements IrisService {
} }
public static Location[] getCuboidFromItem(ItemStack is) { public static Location[] getCuboidFromItem(ItemStack is) {
if (is == null) {
return new Location[]{null, null};
}
ItemMeta im = is.getItemMeta(); ItemMeta im = is.getItemMeta();
return new Location[]{stringToLocation(im.getLore().get(0)), stringToLocation(im.getLore().get(1))}; if (im == null) {
return new Location[]{null, null};
}
List<String> lore = im.getLore();
if (lore == null || lore.size() < 2) {
return new Location[]{null, null};
}
return new Location[]{stringToLocation(lore.get(0)), stringToLocation(lore.get(1))};
} }
public static Location[] getCuboid(Player p) { public static Location[] getCuboid(Player p) {
@@ -343,30 +364,47 @@ public class WandSVC implements IrisService {
* @return True if it is * @return True if it is
*/ */
public static boolean isWand(ItemStack is) { public static boolean isWand(ItemStack is) {
if (is == null || is.getItemMeta() == null) { if (is == null) {
return false; return false;
} }
Byte marker = is.getItemMeta().getPersistentDataContainer().get(wandKey(), PersistentDataType.BYTE); ItemMeta meta = is.getItemMeta();
if (marker != null && marker == (byte) 1) { if (meta == null) {
return false;
}
Byte marker = meta.getPersistentDataContainer().get(wandKey(), PersistentDataType.BYTE);
if (marker != null && marker.byteValue() == 1) {
return true; return true;
} }
return is.getType().equals(wand.getType()) && ItemStack template = wand;
is.getItemMeta().getDisplayName().equals(wand.getItemMeta().getDisplayName()) && if (template == null || !is.getType().equals(template.getType())) {
is.getItemMeta().getEnchants().equals(wand.getItemMeta().getEnchants()) && return false;
is.getItemMeta().getItemFlags().equals(wand.getItemMeta().getItemFlags()); }
ItemMeta templateMeta = template.getItemMeta();
return templateMeta != null
&& Objects.equals(meta.getDisplayName(), templateMeta.getDisplayName())
&& meta.getEnchants().equals(templateMeta.getEnchants())
&& meta.getItemFlags().equals(templateMeta.getItemFlags());
} }
@Override @Override
public void onEnable() { public void onEnable() {
wand = createWand(); wand = createWand();
dust = createDust(); dust = createDust();
enabled = true;
J.ar(this::tickAll, 0); activePlayers.clear();
ticksUntilPlayerRescan = 0;
taskId = J.ar(this::tickAll, 1);
} }
@Override @Override
public void onDisable() { public void onDisable() {
enabled = false;
if (taskId != -1) {
J.car(taskId);
taskId = -1;
}
activePlayers.clear();
playerRescanScheduled.set(false);
} }
/** /**
@@ -375,11 +413,16 @@ public class WandSVC implements IrisService {
*/ */
private void tickAll() { private void tickAll() {
try { try {
J.runGlobal(() -> { if (!enabled) {
for (Player p : Bukkit.getOnlinePlayers()) { return;
J.runEntity(p, () -> tick(p)); }
} if (ticksUntilPlayerRescan-- <= 0) {
}); ticksUntilPlayerRescan = PLAYER_RESCAN_INTERVAL_TICKS;
rescanPlayers();
}
for (Player player : activePlayers.values()) {
J.runEntity(player, () -> tick(player));
}
} catch (Throwable e) { } catch (Throwable e) {
Iris.reportError(e); Iris.reportError(e);
} }
@@ -387,20 +430,53 @@ public class WandSVC implements IrisService {
public void tick(Player p) { public void tick(Player p) {
try { try {
try { if (!p.isOnline()) {
if ((IrisSettings.get().getWorld().worldEditWandCUI && isHoldingWand(p)) || isWand(p.getInventory().getItemInMainHand())) { activePlayers.remove(p.getUniqueId(), p);
Location[] d = getCuboid(p); return;
if (d == null || d[0] == null || d[1] == null) return;
new WandSelection(new Cuboid(d[0], d[1]), p).draw();
}
} catch (Throwable e) {
Iris.reportError(e);
} }
Location[] selection = getCuboid(p);
if (!hasCompleteSelection(selection)) {
activePlayers.remove(p.getUniqueId(), p);
return;
}
new WandSelection(new Cuboid(selection[0], selection[1]), p).draw();
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); Iris.reportError(e);
} }
} }
private void rescanPlayers() {
if (!playerRescanScheduled.compareAndSet(false, true)) {
return;
}
if (!J.runGlobal(() -> {
try {
if (!enabled) {
return;
}
for (Player player : Bukkit.getOnlinePlayers()) {
J.runEntity(player, () -> refreshPlayer(player));
}
} finally {
playerRescanScheduled.set(false);
}
})) {
playerRescanScheduled.set(false);
}
}
private void refreshPlayer(Player player) {
if (enabled && player.isOnline() && hasCompleteSelection(getCuboid(player))) {
activePlayers.put(player.getUniqueId(), player);
return;
}
activePlayers.remove(player.getUniqueId(), player);
}
private static boolean hasCompleteSelection(Location[] selection) {
return selection != null && selection.length >= 2 && selection[0] != null && selection[1] != null;
}
/** /**
* Draw the outline of a selected region * Draw the outline of a selected region
* *
@@ -492,6 +568,7 @@ public class WandSVC implements IrisService {
return; return;
try { try {
if (isHoldingIrisWand(e.getPlayer())) { if (isHoldingIrisWand(e.getPlayer())) {
activePlayers.put(e.getPlayer().getUniqueId(), e.getPlayer());
if (e.getAction().equals(Action.LEFT_CLICK_BLOCK)) { if (e.getAction().equals(Action.LEFT_CLICK_BLOCK)) {
e.setCancelled(true); e.setCancelled(true);
e.getPlayer().getInventory().setItemInMainHand(update(true, Objects.requireNonNull(e.getClickedBlock()).getLocation(), e.getPlayer().getInventory().getItemInMainHand())); e.getPlayer().getInventory().setItemInMainHand(update(true, Objects.requireNonNull(e.getClickedBlock()).getLocation(), e.getPlayer().getInventory().getItemInMainHand()));
@@ -517,6 +594,11 @@ public class WandSVC implements IrisService {
} }
} }
@EventHandler
public void on(PlayerQuitEvent event) {
activePlayers.remove(event.getPlayer().getUniqueId());
}
/** /**
* Is the player holding Dust? * Is the player holding Dust?
* *
@@ -23,16 +23,18 @@ import art.arcane.volmlib.util.math.M;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Particle; import org.bukkit.Particle;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
import java.awt.Color; import java.awt.Color;
import static art.arcane.iris.util.common.data.registry.Particles.REDSTONE; import static art.arcane.iris.util.common.data.registry.Particles.REDSTONE;
public class WandSelection { public class WandSelection {
private static final double STEP = 0.10;
private static final double MAX_DISTANCE = 256D;
private static final double MAX_DISTANCE_SQUARED = MAX_DISTANCE * MAX_DISTANCE;
private final Cuboid c; private final Cuboid c;
private final Player p; private final Player p;
private static final double STEP = 0.10;
public WandSelection(Cuboid c, Player p) { public WandSelection(Cuboid c, Player p) {
this.c = c; this.c = c;
@@ -45,57 +47,91 @@ public class WandSelection {
return; return;
} }
double maxDistanceSquared = 256 * 256;
int particleCount = 0;
// cube! // cube!
Location[][] edges = { double minX = c.getLowerX();
{c.getLowerNE(), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ())}, double minY = c.getLowerY();
{c.getLowerNE(), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ())}, double minZ = c.getLowerZ();
{c.getLowerNE(), new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1)}, double maxX = c.getUpperX() + 1D;
{new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ())}, double maxY = c.getUpperY() + 1D;
{new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1)}, double maxZ = c.getUpperZ() + 1D;
{new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ())}, double playerX = playerLoc.getX();
{new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1)}, double playerY = playerLoc.getY();
{new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1)}, double playerZ = playerLoc.getZ();
{new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1)},
{new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getUpperZ() + 1)},
{new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getUpperZ() + 1)},
{new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getUpperZ() + 1)}
};
for (Location[] edge : edges) { drawX(minX, maxX, minY, minZ, playerX, playerY, playerZ);
Vector direction = edge[1].toVector().subtract(edge[0].toVector()); drawX(minX, maxX, maxY, minZ, playerX, playerY, playerZ);
double length = direction.length(); drawX(minX, maxX, minY, maxZ, playerX, playerY, playerZ);
direction.normalize(); drawX(minX, maxX, maxY, maxZ, playerX, playerY, playerZ);
drawY(minY, maxY, minX, minZ, playerX, playerY, playerZ);
drawY(minY, maxY, maxX, minZ, playerX, playerY, playerZ);
drawY(minY, maxY, minX, maxZ, playerX, playerY, playerZ);
drawY(minY, maxY, maxX, maxZ, playerX, playerY, playerZ);
drawZ(minZ, maxZ, minX, minY, playerX, playerY, playerZ);
drawZ(minZ, maxZ, maxX, minY, playerX, playerY, playerZ);
drawZ(minZ, maxZ, minX, maxY, playerX, playerY, playerZ);
drawZ(minZ, maxZ, maxX, maxY, playerX, playerY, playerZ);
}
for (double d = 0; d <= length; d += STEP) { private void drawX(double start, double end, double y, double z, double playerX, double playerY, double playerZ) {
Location particleLoc = edge[0].clone().add(direction.clone().multiply(d)); double fixedDistanceSquared = square(playerY - y) + square(playerZ - z);
drawAxis(start, end, playerX, fixedDistanceSquared, (double coordinate, double distanceSquared) ->
spawnParticle(coordinate, y, z, distanceSquared));
}
if (playerLoc.distanceSquared(particleLoc) > maxDistanceSquared) { private void drawY(double start, double end, double x, double z, double playerX, double playerY, double playerZ) {
continue; double fixedDistanceSquared = square(playerX - x) + square(playerZ - z);
} drawAxis(start, end, playerY, fixedDistanceSquared, (double coordinate, double distanceSquared) ->
spawnParticle(x, coordinate, z, distanceSquared));
}
spawnParticle(particleLoc, playerLoc); private void drawZ(double start, double end, double x, double y, double playerX, double playerY, double playerZ) {
particleCount++; double fixedDistanceSquared = square(playerX - x) + square(playerY - y);
} drawAxis(start, end, playerZ, fixedDistanceSquared, (double coordinate, double distanceSquared) ->
spawnParticle(x, y, coordinate, distanceSquared));
}
private void drawAxis(double start, double end, double playerCoordinate, double fixedDistanceSquared, AxisParticle particle) {
if (fixedDistanceSquared > MAX_DISTANCE_SQUARED) {
return;
}
double visibleRadius = Math.sqrt(MAX_DISTANCE_SQUARED - fixedDistanceSquared);
double visibleStart = Math.max(start, playerCoordinate - visibleRadius);
double visibleEnd = Math.min(end, playerCoordinate + visibleRadius);
if (visibleStart > visibleEnd) {
return;
}
int firstSample = (int) Math.max(0D, Math.ceil((visibleStart - start) / STEP));
int lastSample = (int) Math.floor((visibleEnd - start) / STEP);
for (int index = firstSample; index <= lastSample; index++) {
double coordinate = start + index * STEP;
double distanceSquared = fixedDistanceSquared + square(playerCoordinate - coordinate);
particle.spawn(coordinate, distanceSquared);
} }
} }
private void spawnParticle(Location particleLoc, Location playerLoc) { private void spawnParticle(double x, double y, double z, double distanceSquared) {
double accuracy = M.lerpInverse(0, 64 * 64, playerLoc.distanceSquared(particleLoc)); double accuracy = M.lerpInverse(0, 64 * 64, distanceSquared);
double dist = M.lerp(0.125, 3.5, accuracy); double dist = M.lerp(0.125, 3.5, accuracy);
if (M.r(Math.min(dist * 5, 0.9D) * 0.995)) { if (M.r(Math.min(dist * 5, 0.9D) * 0.995)) {
return; return;
} }
float hue = (float) (0.5f + (Math.sin((particleLoc.getX() + particleLoc.getY() + particleLoc.getZ() + (p.getTicksLived() / 2f)) / 20f) / 2)); float hue = (float) (0.5f + (Math.sin((x + y + z + (p.getTicksLived() / 2f)) / 20f) / 2));
Color color = Color.getHSBColor(hue, 1, 1); Color color = Color.getHSBColor(hue, 1, 1);
p.spawnParticle(REDSTONE, particleLoc, p.spawnParticle(REDSTONE, x, y, z,
0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
new Particle.DustOptions(org.bukkit.Color.fromRGB(color.getRed(), color.getGreen(), color.getBlue()), new Particle.DustOptions(org.bukkit.Color.fromRGB(color.getRed(), color.getGreen(), color.getBlue()),
(float) dist * 3f)); (float) dist * 3f));
} }
private static double square(double value) {
return value * value;
}
@FunctionalInterface
private interface AxisParticle {
void spawn(double coordinate, double distanceSquared);
}
} }
@@ -5,44 +5,31 @@ import org.junit.Test;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class CommandIrisFoliaCreateContractTest { public class CommandIrisFoliaCreateContractTest {
@Test @Test
public void ordinaryFoliaCreateRestartsOnlyAfterSuccessfulStagingAndFeedback() throws Exception { public void ordinaryFoliaCreateUsesTheSharedRuntimeCreationPath() throws Exception {
String source = Files.readString(Path.of(System.getProperty("iris.commandIrisSource"))); String source = Files.readString(Path.of(System.getProperty("iris.commandIrisSource")));
String foliaCreate = source.substring( String create = source.substring(
source.indexOf("if (J.isFolia()) {"), source.indexOf(" public void create("),
source.indexOf(" try {", source.indexOf("if (J.isFolia()) {")) source.indexOf(" @Director(", source.indexOf(" public void create("))
); );
int stage = foliaCreate.indexOf("stageFoliaWorldCreation(worldName, dimension, seed)"); assertTrue(create.contains("IrisToolbelt.createWorld()"));
int failureExit = foliaCreate.indexOf("if (!staged)"); assertTrue(create.contains(".studio(false)"));
int feedback = foliaCreate.indexOf("COMMAND_IRIS_WORLD_STAGING_COMPLETED_RESTARTING_SERVER_GENERATE_LOAD"); assertTrue(create.contains(".create();"));
int restart = foliaCreate.indexOf("ServerConfigurator.restart(\"Iris staged Folia world"); assertFalse(create.contains("J.isFolia()"));
assertFalse(create.contains("ServerConfigurator.restart("));
assertTrue(stage >= 0);
assertTrue(stage < failureExit);
assertTrue(failureExit < feedback);
assertTrue(feedback < restart);
} }
@Test @Test
public void foliaStagePublishesCurrentPaperDataBeforeRegisteringStartupAlias() throws Exception { public void obsoleteFoliaStagingSurfaceIsRemoved() throws Exception {
String source = Files.readString(Path.of(System.getProperty("iris.commandIrisSource"))); String source = Files.readString(Path.of(System.getProperty("iris.commandIrisSource")));
int methodStart = source.indexOf("private boolean stageFoliaWorldCreation(");
int methodEnd = source.indexOf("private boolean registerWorldInBukkitYml(", methodStart);
String staging = source.substring(methodStart, methodEnd);
int currentPaperData = staging.indexOf("INMS.get().writeCurrentPaperWorldData("); assertFalse(source.contains("stageFoliaWorldCreation"));
int pack = staging.indexOf("installIntoWorld("); assertFalse(source.contains("writeCurrentPaperWorldData"));
int publication = staging.indexOf("AtomicDirectoryPublisher.publishAbsent("); assertFalse(source.contains("COMMAND_IRIS_RUNTIME_WORLD_CREATION_IS_DISABLED_ON_FOLIA"));
int registration = staging.indexOf("registerWorldInBukkitYml(worldKey");
assertTrue(currentPaperData >= 0);
assertTrue(currentPaperData < pack);
assertTrue(pack < publication);
assertTrue(publication < registration);
assertTrue(source.contains("IrisWorldStorage.configuredWorldName("));
} }
} }
@@ -0,0 +1,255 @@
package art.arcane.iris.core.gui;
import art.arcane.iris.core.runtime.WorldRuntimeControlService;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisWorld;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.platform.bukkit.BukkitWorldBinding;
import art.arcane.iris.util.common.scheduling.J;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
public class BukkitVisionOverlayFoliaContractTest {
@Test
public void teleportLoadsTheDestinationChunkBeforeItsOwningRegionReadsTheSurface() throws Exception {
String source = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/gui/BukkitVisionOverlay.java"
)).replace("\r\n", "\n");
String request = method(source, "private void requestTeleportChunk(");
assertBefore(request, "requestChunkAsync(", "requested.whenComplete(");
assertBefore(request, "requested.whenComplete(", "J.runRegion(");
assertBefore(request, "J.runRegion(", "world.getHighestBlockYAt(");
assertBefore(request, "world.getHighestBlockYAt(", "J.runEntity(");
assertTrue(request.contains("chunkX,\n chunkZ,\n true,\n true"));
assertEquals(1, occurrences(request, "world.getHighestBlockYAt("));
}
@Test
public void teleportReportsAsyncAndSchedulingFailuresWithDestinationContext() throws Exception {
String source = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/gui/BukkitVisionOverlay.java"
)).replace("\r\n", "\n");
String request = method(source, "private void requestTeleportChunk(");
String reporter = method(source, "private void reportTeleportFailure(");
assertTrue(request.contains("if (requested == null)"));
assertTrue(request.contains("if (failure != null)"));
assertTrue(request.contains("if (chunk == null ||"));
assertTrue(request.contains("if (!J.runEntity("));
assertTrue(request.contains("if (!scheduled)"));
assertTrue(reporter.contains("IrisLogging.reportError("));
assertTrue(reporter.contains("world.getName()"));
assertTrue(reporter.contains("blockX + \",\" + blockZ"));
}
@Test
public void teleportObservesNativeCompletionAndRejectsFalseSettlement() throws Exception {
String source = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/gui/BukkitVisionOverlay.java"
)).replace("\r\n", "\n");
String delegate = method(source, "private void delegateTeleport(");
assertTrue(delegate.contains("teleport.whenComplete("));
assertTrue(delegate.contains("!Boolean.TRUE.equals(success)"));
assertTrue(delegate.contains("restartLatest(request)"));
}
@Test
public void staleChunkCompletionCannotTeleportOverTheLatestRequest() {
VisionHarness harness = new VisionHarness();
CompletableFuture<Chunk> firstChunk = new CompletableFuture<>();
CompletableFuture<Chunk> secondChunk = new CompletableFuture<>();
harness.stubChunk(0, 0, firstChunk);
harness.stubChunk(2, 2, secondChunk);
try (harness) {
harness.overlay.teleport(1.5D, 1.5D);
harness.overlay.teleport(33.5D, 33.5D);
firstChunk.complete(harness.chunk);
assertEquals(0, harness.destinations.size());
secondChunk.complete(harness.chunk);
assertEquals(1, harness.destinations.size());
assertEquals(33, harness.destinations.get(0).getBlockX());
assertEquals(33, harness.destinations.get(0).getBlockZ());
}
}
@Test
public void latestRequestRunsAfterAnOlderNativeTeleportSettles() {
VisionHarness harness = new VisionHarness();
harness.stubChunk(0, 0, CompletableFuture.completedFuture(harness.chunk));
harness.stubChunk(2, 2, CompletableFuture.completedFuture(harness.chunk));
CompletableFuture<Boolean> firstTeleport = new CompletableFuture<>();
CompletableFuture<Boolean> secondTeleport = new CompletableFuture<>();
harness.nativeTeleports.add(firstTeleport);
harness.nativeTeleports.add(secondTeleport);
try (harness) {
harness.overlay.teleport(1.5D, 1.5D);
harness.overlay.teleport(33.5D, 33.5D);
assertEquals(1, harness.destinations.size());
firstTeleport.complete(true);
assertEquals(2, harness.destinations.size());
assertEquals(33, harness.destinations.get(1).getBlockX());
assertEquals(33, harness.destinations.get(1).getBlockZ());
secondTeleport.complete(true);
}
}
private static void assertBefore(String source, String first, String second) {
int firstIndex = source.indexOf(first);
int secondIndex = source.indexOf(second);
assertTrue("Missing source contract token: " + first, firstIndex >= 0);
assertTrue("Missing source contract token: " + second, secondIndex >= 0);
assertTrue(first + " must occur before " + second, firstIndex < secondIndex);
}
private static int occurrences(String source, String match) {
int count = 0;
int offset = 0;
while ((offset = source.indexOf(match, offset)) >= 0) {
count++;
offset += match.length();
}
return count;
}
private static String method(String source, String signature) {
int start = source.indexOf(signature);
assertTrue("Missing source contract signature: " + signature, start >= 0);
int openBrace = source.indexOf('{', start);
assertTrue("Missing source contract method body: " + signature, openBrace >= 0);
int depth = 0;
for (int index = openBrace; index < source.length(); index++) {
char current = source.charAt(index);
if (current == '{') {
depth++;
} else if (current == '}') {
depth--;
if (depth == 0) {
return source.substring(start, index + 1);
}
}
}
throw new IllegalArgumentException("Unclosed source contract method: " + signature);
}
private static final class VisionHarness implements AutoCloseable {
private final Engine engine;
private final IrisWorld target;
private final World world;
private final Player player;
private final Chunk chunk;
private final WorldRuntimeControlService runtime;
private final MockedStatic<J> scheduling;
private final MockedStatic<BukkitWorldBinding> binding;
private final MockedStatic<WorldRuntimeControlService> runtimeAccess;
private final MockedStatic<BukkitPlatform> platform;
private final List<Location> destinations;
private final List<CompletableFuture<Boolean>> nativeTeleports;
private final AtomicInteger nativeTeleportIndex;
private final BukkitVisionOverlay overlay;
private VisionHarness() {
engine = mock(Engine.class);
target = mock(IrisWorld.class);
world = mock(World.class);
player = mock(Player.class);
chunk = mock(Chunk.class);
runtime = mock(WorldRuntimeControlService.class);
destinations = new ArrayList<>();
nativeTeleports = new ArrayList<>();
nativeTeleportIndex = new AtomicInteger();
when(engine.getWorld()).thenReturn(target);
when(target.hasPlatformWorld()).thenReturn(true);
when(player.isOnline()).thenReturn(true);
when(player.getWorld()).thenReturn(world);
when(chunk.getWorld()).thenReturn(world);
when(world.getHighestBlockYAt(anyInt(), anyInt())).thenReturn(70);
scheduling = mockStatic(J.class);
scheduling.when(() -> J.runGlobal(any(Runnable.class))).thenAnswer(invocation -> {
invocation.getArgument(0, Runnable.class).run();
return true;
});
scheduling.when(() -> J.runRegion(
same(world),
anyInt(),
anyInt(),
any(Runnable.class)))
.thenAnswer(invocation -> {
invocation.getArgument(3, Runnable.class).run();
return true;
});
scheduling.when(() -> J.runEntity(same(player), any(Runnable.class))).thenAnswer(invocation -> {
invocation.getArgument(1, Runnable.class).run();
return true;
});
binding = mockStatic(BukkitWorldBinding.class);
binding.when(() -> BukkitWorldBinding.world(target)).thenReturn(world);
binding.when(() -> BukkitWorldBinding.players(target)).thenReturn(List.of(player));
runtimeAccess = mockStatic(WorldRuntimeControlService.class);
runtimeAccess.when(WorldRuntimeControlService::get).thenReturn(runtime);
platform = mockStatic(BukkitPlatform.class);
platform.when(() -> BukkitPlatform.teleportAsync(same(player), any(Location.class)))
.thenAnswer(invocation -> {
destinations.add(invocation.getArgument(1, Location.class));
int index = nativeTeleportIndex.getAndIncrement();
return index < nativeTeleports.size()
? nativeTeleports.get(index)
: CompletableFuture.completedFuture(true);
});
overlay = new BukkitVisionOverlay(engine);
}
private void stubChunk(
int chunkX,
int chunkZ,
CompletableFuture<Chunk> requested
) {
when(runtime.requestChunkAsync(
same(world),
eq(chunkX),
eq(chunkZ),
eq(true),
eq(true))).thenReturn(requested);
}
@Override
public void close() {
platform.close();
runtimeAccess.close();
binding.close();
scheduling.close();
}
}
}
@@ -11,15 +11,14 @@ import static org.junit.Assert.assertTrue;
public class StudioPlayerModeContractTest { public class StudioPlayerModeContractTest {
@Test @Test
public void studioEntryKeepsPlayersEligibleForNaturalSpawning() throws IOException { public void autoStartedStudioUsesSpectatorMode() throws IOException {
String plugin = Files.readString(Path.of("src/main/java/art/arcane/iris/Iris.java")).replace("\r\n", "\n"); String plugin = Files.readString(Path.of("src/main/java/art/arcane/iris/Iris.java")).replace("\r\n", "\n");
String commands = Files.readString(Path.of( String commands = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/commands/CommandStudio.java")).replace("\r\n", "\n"); "src/main/java/art/arcane/iris/core/commands/CommandStudio.java")).replace("\r\n", "\n");
assertFalse(plugin.contains("GameMode.SPECTATOR")); assertFalse(plugin.contains("GameMode.CREATIVE"));
assertFalse(commands.contains("GameMode.SPECTATOR")); assertFalse(commands.contains("GameMode.CREATIVE"));
assertTrue(plugin.contains("GameMode.CREATIVE")); assertTrue(plugin.contains("GameMode.SPECTATOR"));
assertTrue(commands.contains("GameMode.CREATIVE"));
} }
@Test @Test
@@ -32,8 +31,23 @@ public class StudioPlayerModeContractTest {
assertTrue(method.contains("StudioSVC studioService = Iris.service(StudioSVC.class)")); assertTrue(method.contains("StudioSVC studioService = Iris.service(StudioSVC.class)"));
assertTrue(method.contains("studioService.teleportToActiveProject(player)")); assertTrue(method.contains("studioService.teleportToActiveProject(player)"));
assertFalse(method.contains("setGameMode("));
assertFalse(method.contains("getActiveProject()")); assertFalse(method.contains("getActiveProject()"));
assertFalse(method.contains("BukkitPlatform.teleportAsync")); assertFalse(method.contains("BukkitPlatform.teleportAsync"));
assertFalse(method.contains("BukkitWorldBinding.spawnLocation")); assertFalse(method.contains("BukkitWorldBinding.spawnLocation"));
} }
@Test
public void editingStudiosRemainCreative() throws IOException {
String objectCommands = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/commands/CommandObject.java")).replace("\r\n", "\n");
String jigsawCommands = Files.readString(Path.of(
"src/main/java/art/arcane/iris/core/commands/CommandJigsaw.java")).replace("\r\n", "\n");
assertTrue(objectCommands.contains("GameMode.CREATIVE"));
assertTrue(objectCommands.contains("StudioOpenCoordinator.StudioOpenKind.OBJECT"));
assertTrue(jigsawCommands.contains("GameMode.CREATIVE"));
assertFalse(objectCommands.contains("GameMode.SPECTATOR"));
assertFalse(jigsawCommands.contains("GameMode.SPECTATOR"));
}
} }
@@ -146,7 +146,7 @@ public final class IrisClient {
case IrisMessage.VisionTile visionTile -> TILES.onVisionTile(visionTile); case IrisMessage.VisionTile visionTile -> TILES.onVisionTile(visionTile);
case IrisMessage.VisionMarkers visionMarkers -> MARKERS.onMarkers(visionMarkers); case IrisMessage.VisionMarkers visionMarkers -> MARKERS.onMarkers(visionMarkers);
case IrisMessage.PregenRegionDelta delta -> REGIONS.onDelta(delta, PREGEN.activeJobId()); case IrisMessage.PregenRegionDelta delta -> REGIONS.onDelta(delta, PREGEN.activeJobId());
case IrisMessage.StudioHotload hotload -> TOASTS.enqueueHotload(hotload.packKey(), hotload.changedFiles(), hotload.failed(), hotload.message()); case IrisMessage.StudioHotload hotload -> onStudioHotload(hotload);
case IrisMessage.Toast toast -> TOASTS.enqueue(toast.kind(), toast.title(), toast.body()); case IrisMessage.Toast toast -> TOASTS.enqueue(toast.kind(), toast.title(), toast.body());
default -> { default -> {
} }
@@ -167,4 +167,29 @@ public final class IrisClient {
REGIONS.clear(); REGIONS.clear();
} }
} }
private static void onStudioHotload(IrisMessage.StudioHotload hotload) {
if (shouldInvalidateForHotload(DIMENSION.status(), hotload)) {
TILES.clear();
MARKERS.clear();
CURSOR.clear();
}
TOASTS.enqueueHotload(
hotload.packKey(),
hotload.changedFiles(),
hotload.failed(),
hotload.message()
);
}
static boolean shouldInvalidateForHotload(
IrisMessage.DimensionStatus status,
IrisMessage.StudioHotload hotload
) {
return status != null
&& status.irisWorld()
&& hotload != null
&& !hotload.failed()
&& status.packKey().equals(hotload.packKey());
}
} }
@@ -18,6 +18,7 @@
package art.arcane.iris.fabric.mixin; package art.arcane.iris.fabric.mixin;
import art.arcane.iris.modded.ModdedIrisLog;
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.RepositorySource;
@@ -26,7 +27,6 @@ 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.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.slf4j.LoggerFactory;
import java.util.Arrays; import java.util.Arrays;
@@ -42,7 +42,7 @@ public class PackRepositoryMixin {
} }
// Client resource-pack repositories legitimately have no ServerPacksSource; a missing server-data // Client resource-pack repositories legitimately have no ServerPacksSource; a missing server-data
// repository is reported once at boot by ModdedForcedDatapack.verifyInjected(). // repository is reported once at boot by ModdedForcedDatapack.verifyInjected().
LoggerFactory.getLogger("Iris").debug( ModdedIrisLog.debug(
"Iris forced datapack source not attached: no ServerPacksSource among {} source(s) {}", "Iris forced datapack source not attached: no ServerPacksSource among {} source(s) {}",
sources.length, Arrays.toString(sources)); sources.length, Arrays.toString(sources));
} }
@@ -82,8 +82,6 @@ import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureSet; import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.StructureStart; import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.util.Arrays; import java.util.Arrays;
@@ -98,7 +96,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.IntBinaryOperator; import java.util.function.IntBinaryOperator;
public final class IrisModdedChunkGenerator extends ChunkGenerator { public final class IrisModdedChunkGenerator extends ChunkGenerator {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
// Vanilla-shaped fallback for an unbound generator (matches IrisDimension defaults). getMinY, // Vanilla-shaped fallback for an unbound generator (matches IrisDimension defaults). getMinY,
// getSeaLevel and getGenDepth are called from world creation and client screens, so they must // getSeaLevel and getGenDepth are called from world creation and client screens, so they must
// answer without disk I/O and without throwing before a level is bound. // answer without disk I/O and without throwing before a level is bound.
@@ -368,7 +365,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
// Bind time: a feature-order cycle is reported here, once, and degrades to features-off. Non-waiting for // Bind time: a feature-order cycle is reported here, once, and degrades to features-off. Non-waiting for
// the same reason as repointAndBind: this method owns the generator monitor. // the same reason as repointAndBind: this method owns the generator monitor.
importedFeatures.prepareWithoutWaiting(bound); importedFeatures.prepareWithoutWaiting(bound);
LOGGER.info("Iris bound {}: chunk system {}", level.dimension().identifier(), ModdedGenPool.describeChunkSystem()); ModdedIrisLog.info("Iris bound {}: chunk system {}", level.dimension().identifier(), ModdedGenPool.describeChunkSystem());
} }
private Engine bindEngine(ServerLevel level) { private Engine bindEngine(ServerLevel level) {
@@ -601,7 +598,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
try { try {
heightMetadata = configuredPack().metadata(); heightMetadata = configuredPack().metadata();
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.warn("Iris generator '{}' could not pre-resolve pack heights for {}:{}: {}", ModdedIrisLog.warn("Iris generator '{}' could not pre-resolve pack heights for {}:{}: {}",
dimensionKey, activePack, activeDimensionKey, e.toString()); dimensionKey, activePack, activeDimensionKey, e.toString());
} }
} }
@@ -726,7 +723,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
Engine generationEngine = engine(); Engine generationEngine = engine();
ChunkPos pos = chunk.getPos(); ChunkPos pos = chunk.getPos();
lastChunkGenAt = System.currentTimeMillis(); lastChunkGenAt = System.currentTimeMillis();
LOGGER.debug("Iris generating chunk {},{}", pos.x(), pos.z()); ModdedIrisLog.debug("Iris generating chunk {},{}", pos.x(), pos.z());
PlatformBlockState air = IrisPlatforms.get().registries().air(); PlatformBlockState air = IrisPlatforms.get().registries().air();
@@ -744,7 +741,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
try (GenerationSessionLease lease = generationEngine.acquireGenerationLease("modded_chunk_pipeline"); try (GenerationSessionLease lease = generationEngine.acquireGenerationLease("modded_chunk_pipeline");
IrisContext.Scope ignored = IrisContext.open(generationEngine, lease.sessionId(), null)) { IrisContext.Scope ignored = IrisContext.open(generationEngine, lease.sessionId(), null)) {
if (announced.compareAndSet(false, true)) { if (announced.compareAndSet(false, true)) {
LOGGER.info("Iris generating {} through IrisModdedChunkGenerator (dim={} first chunk {},{})", ModdedIrisLog.info("Iris generating {} through IrisModdedChunkGenerator (dim={} first chunk {},{})",
dimensionKey, generationEngine.getDimension().getLoadKey(), pos.x(), pos.z()); dimensionKey, generationEngine.getDimension().getLoadKey(), pos.x(), pos.z());
} }
int dimMinY = generationEngine.getMinHeight(); int dimMinY = generationEngine.getMinHeight();
@@ -763,14 +760,14 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
return chunk; return chunk;
} catch (GenerationSessionException e) { } catch (GenerationSessionException e) {
if (generationEngine.isClosing() || e.isExpectedTeardown()) { if (generationEngine.isClosing() || e.isExpectedTeardown()) {
LOGGER.debug("Iris chunk {},{} skipped: engine sealed for hotload/teardown", pos.x(), pos.z()); ModdedIrisLog.debug("Iris chunk {},{} skipped: engine sealed for hotload/teardown", pos.x(), pos.z());
throw new IllegalStateException( throw new IllegalStateException(
"Iris chunk generation was rejected during an engine transition.", e); "Iris chunk generation was rejected during an engine transition.", e);
} }
LOGGER.error("Iris failed to generate chunk {},{}", pos.x(), pos.z(), e); ModdedIrisLog.error("Iris failed to generate chunk {},{}", pos.x(), pos.z(), e);
throw new IllegalStateException("Iris generation failed for chunk " + pos.x() + "," + pos.z(), e); throw new IllegalStateException("Iris generation failed for chunk " + pos.x() + "," + pos.z(), e);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris failed to generate chunk {},{}", pos.x(), pos.z(), e); ModdedIrisLog.error("Iris failed to generate chunk {},{}", pos.x(), pos.z(), e);
throw new IllegalStateException("Iris generation failed for chunk " + pos.x() + "," + pos.z(), e); throw new IllegalStateException("Iris generation failed for chunk " + pos.x() + "," + pos.z(), e);
} }
} }
@@ -18,8 +18,6 @@
package art.arcane.iris.modded; package art.arcane.iris.modded;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -32,7 +30,6 @@ import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public final class MainWorldService { public final class MainWorldService {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String MARKER_NAME = "mainworld.pending"; private static final String MARKER_NAME = "mainworld.pending";
private static final String PROPERTIES_NAME = "server.properties"; private static final String PROPERTIES_NAME = "server.properties";
/** /**
@@ -80,12 +77,12 @@ public final class MainWorldService {
if (!target.equals(currentType)) { if (!target.equals(currentType)) {
writeLevelProperties(properties, target, config.mainWorldSeed()); writeLevelProperties(properties, target, config.mainWorldSeed());
markPending(); markPending();
LOGGER.warn("Iris main world '{}' staged: {} level-type set to {}. Restart again to generate it (this boot still uses the previous overworld; player data is kept).", ModdedIrisLog.warn("Iris main world '{}' staged: {} level-type set to {}. Restart again to generate it (this boot still uses the previous overworld; player data is kept).",
pack, properties, target); pack, properties, target);
if (config.mainWorldAutoRestart()) { if (config.mainWorldAutoRestart()) {
LOGGER.warn("Iris mainWorldAutoRestart is enabled; stopping the JVM now with exit status {} so a restart wrapper brings the server back on the new main world.", ModdedIrisLog.warn("Iris mainWorldAutoRestart is enabled; stopping the JVM now with exit status {} so a restart wrapper brings the server back on the new main world.",
AUTO_RESTART_EXIT_STATUS); AUTO_RESTART_EXIT_STATUS);
LOGGER.warn("Configure the start script to restart the server on exit status {} (status 0 means a clean stop, so it must not be reused for this).", ModdedIrisLog.warn("Configure the start script to restart the server on exit status {} (status 0 means a clean stop, so it must not be reused for this).",
AUTO_RESTART_EXIT_STATUS); AUTO_RESTART_EXIT_STATUS);
System.exit(AUTO_RESTART_EXIT_STATUS); System.exit(AUTO_RESTART_EXIT_STATUS);
} }
@@ -102,16 +99,16 @@ public final class MainWorldService {
// bootstrap: there is no prior overworld to move aside, so this is nothing to quarantine, not a // bootstrap: there is no prior overworld to move aside, so this is nothing to quarantine, not a
// reason to refuse startup. // reason to refuse startup.
clearPending(); clearPending();
LOGGER.warn("Iris main world '{}' had nothing to quarantine: {} does not exist. Continuing boot; the overworld generates as {}.", ModdedIrisLog.warn("Iris main world '{}' had nothing to quarantine: {} does not exist. Continuing boot; the overworld generates as {}.",
pack, missing.path(), target); pack, missing.path(), target);
return; return;
} }
Path recovery = quarantineVanillaDimensions(worldRoot); Path recovery = quarantineVanillaDimensions(worldRoot);
clearPending(); clearPending();
LOGGER.warn("Iris main world '{}' generated fresh: moved the prior overworld/nether/end data from {} to {} so this boot regenerates them as {} (player data kept).", ModdedIrisLog.warn("Iris main world '{}' generated fresh: moved the prior overworld/nether/end data from {} to {} so this boot regenerates them as {} (player data kept).",
pack, worldRoot, recovery, target); pack, worldRoot, recovery, target);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris main world reconciliation failed", e); ModdedIrisLog.error("Iris main world reconciliation failed", e);
throw new IllegalStateException( throw new IllegalStateException(
"Iris refused startup after main-world reconciliation failed", e); "Iris refused startup after main-world reconciliation failed", e);
} }
@@ -119,7 +116,7 @@ public final class MainWorldService {
public static boolean stage(String packRef, long seed) { public static boolean stage(String packRef, long seed) {
if (ModdedEngineBootstrap.loader().clientEnvironment()) { if (ModdedEngineBootstrap.loader().clientEnvironment()) {
LOGGER.error("Iris main-world replacement is only available on dedicated servers; use the Create World generator selector in singleplayer"); ModdedIrisLog.error("Iris main-world replacement is only available on dedicated servers; use the Create World generator selector in singleplayer");
return false; return false;
} }
Path instanceRoot = verifiedInstanceRoot("stage the Iris main world"); Path instanceRoot = verifiedInstanceRoot("stage the Iris main world");
@@ -131,7 +128,7 @@ public final class MainWorldService {
markPending(); markPending();
return true; return true;
} catch (IOException e) { } catch (IOException e) {
LOGGER.error("Iris failed to stage the main world in server.properties", e); ModdedIrisLog.error("Iris failed to stage the main world in server.properties", e);
return false; return false;
} }
} }
@@ -140,7 +137,7 @@ public final class MainWorldService {
try { try {
clearPending(); clearPending();
} catch (IOException e) { } catch (IOException e) {
LOGGER.error("Iris failed to clear the pending main world marker", e); ModdedIrisLog.error("Iris failed to clear the pending main world marker", e);
} }
} }
@@ -156,8 +153,8 @@ public final class MainWorldService {
if (Files.isRegularFile(workingDirectory.resolve(PROPERTIES_NAME))) { if (Files.isRegularFile(workingDirectory.resolve(PROPERTIES_NAME))) {
return workingDirectory; return workingDirectory;
} }
LOGGER.error("Iris refuses to {}: no {} in the server working directory {}", operation, PROPERTIES_NAME, workingDirectory); ModdedIrisLog.error("Iris refuses to {}: no {} in the server working directory {}", operation, PROPERTIES_NAME, workingDirectory);
LOGGER.error("Iris only edits main-world properties in the directory the dedicated server reads {} from, and it moves no world data outside it. Start the server from its instance directory, or clear mainWorldPack in irisworldgen/modded.json.", PROPERTIES_NAME); ModdedIrisLog.error("Iris only edits main-world properties in the directory the dedicated server reads {} from, and it moves no world data outside it. Start the server from its instance directory, or clear mainWorldPack in irisworldgen/modded.json.", PROPERTIES_NAME);
return null; return null;
} }
@@ -305,7 +302,7 @@ public final class MainWorldService {
return List.of(arguments.get()); return List.of(arguments.get());
} }
} catch (RuntimeException unavailable) { } catch (RuntimeException unavailable) {
LOGGER.debug("Iris could not read the process arguments", unavailable); ModdedIrisLog.debug("Iris could not read the process arguments", unavailable);
} }
// Whitespace split only: sun.java.command is a flattened string with no quoting information, so a // Whitespace split only: sun.java.command is a flattened string with no quoting information, so a
// --universe or --world value containing spaces cannot be recovered from it. Deliberately not parsed // --universe or --world value containing spaces cannot be recovered from it. Deliberately not parsed
@@ -29,8 +29,6 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier; import net.minecraft.resources.Identifier;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -39,7 +37,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier; import java.util.function.Supplier;
public final class ModdedBiomeWriter implements PlatformBiomeWriter { public final class ModdedBiomeWriter implements PlatformBiomeWriter {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String VANILLA_FALLBACK_KEY = "minecraft:plains"; private static final String VANILLA_FALLBACK_KEY = "minecraft:plains";
private static final int MAX_CACHED_IDS = 4096; private static final int MAX_CACHED_IDS = 4096;
/** NUL cannot occur in a pack or registry key, so the composite cache key stays unambiguous. */ /** NUL cannot occur in a pack or registry key, so the composite cache key stays unambiguous. */
@@ -61,7 +58,7 @@ public final class ModdedBiomeWriter implements PlatformBiomeWriter {
return 0; return 0;
} }
if (key == null) { if (key == null) {
LOGGER.warn("Iris biome writer got a null biome key; falling back to {}", VANILLA_FALLBACK_KEY); ModdedIrisLog.warn("Iris biome writer got a null biome key; falling back to {}", VANILLA_FALLBACK_KEY);
return fallbackId(registry); return fallbackId(registry);
} }
@@ -217,7 +214,7 @@ public final class ModdedBiomeWriter implements PlatformBiomeWriter {
private void reportMissingServer(String operation, String fallback) { private void reportMissingServer(String operation, String fallback) {
if (serverMissingReported.compareAndSet(false, true)) { if (serverMissingReported.compareAndSet(false, true)) {
LOGGER.warn("Iris cannot {} before the Minecraft server is available; {}", operation, fallback); ModdedIrisLog.warn("Iris cannot {} before the Minecraft server is available; {}", operation, fallback);
} }
} }
@@ -43,14 +43,11 @@ import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public final class ModdedBlockBreakHandler { public final class ModdedBlockBreakHandler {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final ConcurrentHashMap<BreakKey, PendingBreak> PENDING = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<BreakKey, PendingBreak> PENDING = new ConcurrentHashMap<>();
private ModdedBlockBreakHandler() { private ModdedBlockBreakHandler() {
@@ -72,7 +69,7 @@ public final class ModdedBlockBreakHandler {
if (scheduler == null) { if (scheduler == null) {
// finishPending is the only thing that evicts an unconsumed entry. With no scheduler there is no // finishPending is the only thing that evicts an unconsumed entry. With no scheduler there is no
// sweep, so an entry inserted here would leak for the rest of the server uptime. // sweep, so an entry inserted here would leak for the rest of the server uptime.
LOGGER.debug("Iris skipped block-break provenance at {},{},{}: scheduler unavailable", ModdedIrisLog.debug("Iris skipped block-break provenance at {},{},{}: scheduler unavailable",
position.getX(), position.getY(), position.getZ()); position.getX(), position.getY(), position.getZ());
return; return;
} }
@@ -150,7 +147,7 @@ public final class ModdedBlockBreakHandler {
try { try {
return evaluate(level, position, pending); return evaluate(level, position, pending);
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris block-break processing failed at {},{},{} in {}", position.getX(), position.getY(), position.getZ(), ModdedIrisLog.error("Iris block-break processing failed at {},{},{} in {}", position.getX(), position.getY(), position.getZ(),
level.dimension().identifier(), error); level.dimension().identifier(), error);
return Result.empty(); return Result.empty();
} }
@@ -182,7 +179,7 @@ public final class ModdedBlockBreakHandler {
try { try {
return evaluateDrops(level, position, brokenState, engine); return evaluateDrops(level, position, brokenState, engine);
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris managed block-drop processing failed at {},{},{} in {}", position.getX(), position.getY(), position.getZ(), ModdedIrisLog.error("Iris managed block-drop processing failed at {},{},{} in {}", position.getX(), position.getY(), position.getZ(),
level.dimension().identifier(), error); level.dimension().identifier(), error);
return Result.empty(); return Result.empty();
} }
@@ -315,7 +312,7 @@ public final class ModdedBlockBreakHandler {
try { try {
return irisGenerator.commandEngine(); return irisGenerator.commandEngine();
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris could not resolve the engine for a block break in {}", level.dimension().identifier(), error); ModdedIrisLog.error("Iris could not resolve the engine for a block break in {}", level.dimension().identifier(), error);
return null; return null;
} }
} }
@@ -46,8 +46,6 @@ import net.minecraft.world.level.storage.DerivedLevelData;
import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.LevelStorageSource;
import net.minecraft.world.level.storage.ServerLevelData; import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.level.storage.WorldData; import net.minecraft.world.level.storage.WorldData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@@ -60,7 +58,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public final class ModdedDimensionManager { public final class ModdedDimensionManager {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Object LOCK = new Object(); private static final Object LOCK = new Object();
private static final ConcurrentHashMap<String, Handle> HANDLES = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, Handle> HANDLES = new ConcurrentHashMap<>();
private static final TicketType TELEPORT_WARM_TICKET = new TicketType(TicketType.NO_TIMEOUT, private static final TicketType TELEPORT_WARM_TICKET = new TicketType(TicketType.NO_TIMEOUT,
@@ -137,7 +134,7 @@ public final class ModdedDimensionManager {
if (present == null || !(present.getChunkSource().getGenerator() instanceof IrisModdedChunkGenerator generator)) { if (present == null || !(present.getChunkSource().getGenerator() instanceof IrisModdedChunkGenerator generator)) {
throw new IllegalStateException("Iris cannot inject dimension '" + dimensionId + "': a non-Iris level with that id is already loaded"); throw new IllegalStateException("Iris cannot inject dimension '" + dimensionId + "': a non-Iris level with that id is already loaded");
} }
LOGGER.warn("Iris dimension '{}' is already present in the running server; reusing it", dimensionId); ModdedIrisLog.warn("Iris dimension '{}' is already present in the running server; reusing it", dimensionId);
generator.repointAndBind(present, pack, packDimensionKey, seed); generator.repointAndBind(present, pack, packDimensionKey, seed);
Handle handle = new Handle(dimensionId, pack, packDimensionKey, seed, present, generator); Handle handle = new Handle(dimensionId, pack, packDimensionKey, seed, present, generator);
HANDLES.put(dimensionId, handle); HANDLES.put(dimensionId, handle);
@@ -147,10 +144,10 @@ public final class ModdedDimensionManager {
try { try {
Handle handle = inject(server, serverAccess, dimensionId, key, pack, packDimensionKey, seed); Handle handle = inject(server, serverAccess, dimensionId, key, pack, packDimensionKey, seed);
HANDLES.put(dimensionId, handle); HANDLES.put(dimensionId, handle);
LOGGER.info("Iris injected runtime dimension '{}' (pack={} dim={} seed={})", dimensionId, pack, packDimensionKey, seed); ModdedIrisLog.info("Iris injected runtime dimension '{}' (pack={} dim={} seed={})", dimensionId, pack, packDimensionKey, seed);
return handle; return handle;
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris failed to inject runtime dimension '{}' (pack={} dim={} seed={})", dimensionId, pack, packDimensionKey, seed, e); ModdedIrisLog.error("Iris failed to inject runtime dimension '{}' (pack={} dim={} seed={})", dimensionId, pack, packDimensionKey, seed, e);
throw new IllegalStateException("Iris runtime dimension injection failed for " + dimensionId, e); throw new IllegalStateException("Iris runtime dimension injection failed for " + dimensionId, e);
} }
} }
@@ -233,11 +230,11 @@ public final class ModdedDimensionManager {
if (wipeStorage) { if (wipeStorage) {
ModdedDimensionStorage.wipe(server, key); ModdedDimensionStorage.wipe(server, key);
} }
LOGGER.info("Iris removed runtime dimension '{}'", dimensionId); ModdedIrisLog.info("Iris removed runtime dimension '{}'", dimensionId);
return true; return true;
} catch (Throwable e) { } catch (Throwable e) {
rollbackRemoval(server, serverAccess, key, level, generator, generatorUnbound, e); rollbackRemoval(server, serverAccess, key, level, generator, generatorUnbound, e);
LOGGER.error("Iris failed to remove runtime dimension '{}'", dimensionId, e); ModdedIrisLog.error("Iris failed to remove runtime dimension '{}'", dimensionId, e);
throw new IllegalStateException("Iris runtime dimension removal failed for " + dimensionId, e); throw new IllegalStateException("Iris runtime dimension removal failed for " + dimensionId, e);
} }
} }
@@ -256,7 +253,7 @@ public final class ModdedDimensionManager {
if (rollbackFailure != failure) { if (rollbackFailure != failure) {
failure.addSuppressed(rollbackFailure); failure.addSuppressed(rollbackFailure);
} }
LOGGER.error("Iris failed to restore the engine for retained runtime dimension '{}'", ModdedIrisLog.error("Iris failed to restore the engine for retained runtime dimension '{}'",
key.identifier(), rollbackFailure); key.identifier(), rollbackFailure);
} }
} }
@@ -282,7 +279,7 @@ public final class ModdedDimensionManager {
.whenComplete((Object result, Throwable error) -> server.execute(() -> { .whenComplete((Object result, Throwable error) -> server.execute(() -> {
level.getChunkSource().removeTicketWithRadius(TELEPORT_WARM_TICKET, chunkPos, 1); level.getChunkSource().removeTicketWithRadius(TELEPORT_WARM_TICKET, chunkPos, 1);
if (error != null) { if (error != null) {
LOGGER.warn("Iris chunk warm for teleport into '{}' at {},{} failed: {}", dimensionId, chunkPos.x(), chunkPos.z(), error.toString()); ModdedIrisLog.warn("Iris chunk warm for teleport into '{}' at {},{} failed: {}", dimensionId, chunkPos.x(), chunkPos.z(), error.toString());
} }
ServerPlayer target = server.getPlayerList().getPlayer(playerId); ServerPlayer target = server.getPlayerList().getPlayer(playerId);
if (target == null) { if (target == null) {
@@ -325,7 +322,7 @@ public final class ModdedDimensionManager {
} }
return dimension; return dimension;
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris could not load pack '{}' dimension '{}' for dimension type resolution", ModdedIrisLog.error("Iris could not load pack '{}' dimension '{}' for dimension type resolution",
pack, packDimensionKey, e); pack, packDimensionKey, e);
if (e instanceof Error fatalError) { if (e instanceof Error fatalError) {
throw fatalError; throw fatalError;
@@ -401,19 +398,19 @@ public final class ModdedDimensionManager {
} }
} catch (Throwable cleanupError) { } catch (Throwable cleanupError) {
failure.addSuppressed(cleanupError); failure.addSuppressed(cleanupError);
LOGGER.error("Iris failed to remove a partially injected level for {}", key.identifier(), cleanupError); ModdedIrisLog.error("Iris failed to remove a partially injected level for {}", key.identifier(), cleanupError);
} }
try { try {
generator.unbindEngine(level); generator.unbindEngine(level);
} catch (Throwable cleanupError) { } catch (Throwable cleanupError) {
failure.addSuppressed(cleanupError); failure.addSuppressed(cleanupError);
LOGGER.error("Iris failed to close a partially bound engine for {}", key.identifier(), cleanupError); ModdedIrisLog.error("Iris failed to close a partially bound engine for {}", key.identifier(), cleanupError);
} }
try { try {
level.close(); level.close();
} catch (Throwable cleanupError) { } catch (Throwable cleanupError) {
failure.addSuppressed(cleanupError); failure.addSuppressed(cleanupError);
LOGGER.error("Iris failed to close a partially injected level for {}", key.identifier(), cleanupError); ModdedIrisLog.error("Iris failed to close a partially injected level for {}", key.identifier(), cleanupError);
} }
} }
@@ -22,8 +22,6 @@ import art.arcane.volmlib.util.json.JSONArray;
import art.arcane.volmlib.util.json.JSONObject; import art.arcane.volmlib.util.json.JSONObject;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.storage.LevelResource; import net.minecraft.world.level.storage.LevelResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
@@ -41,7 +39,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public final class ModdedDimensionRegistryStore { public final class ModdedDimensionRegistryStore {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String FILE_NAME = "iris-dimensions.json"; private static final String FILE_NAME = "iris-dimensions.json";
private static final Pattern ID_FIELD = Pattern.compile("\"id\"\\s*:\\s*\"([^\"]+)\""); private static final Pattern ID_FIELD = Pattern.compile("\"id\"\\s*:\\s*\"([^\"]+)\"");
@@ -68,13 +65,13 @@ public final class ModdedDimensionRegistryStore {
try { try {
return load(file); return load(file);
} catch (RuntimeException corrupt) { } catch (RuntimeException corrupt) {
LOGGER.error("Iris persistent dimension registry at {} is corrupt; quarantining it and continuing boot", ModdedIrisLog.error("Iris persistent dimension registry at {} is corrupt; quarantining it and continuing boot",
file, corrupt); file, corrupt);
List<String> lostIds = salvageIds(file); List<String> lostIds = salvageIds(file);
if (lostIds.isEmpty()) { if (lostIds.isEmpty()) {
LOGGER.error("Iris could not recover any dimension ids from the corrupt registry; re-create the worlds with /iris world create"); ModdedIrisLog.error("Iris could not recover any dimension ids from the corrupt registry; re-create the worlds with /iris world create");
} else { } else {
LOGGER.error("Iris lost {} persistent dimension(s) from the corrupt registry: {}", ModdedIrisLog.error("Iris lost {} persistent dimension(s) from the corrupt registry: {}",
lostIds.size(), String.join(", ", lostIds)); lostIds.size(), String.join(", ", lostIds));
} }
quarantine(file); quarantine(file);
@@ -93,7 +90,7 @@ public final class ModdedDimensionRegistryStore {
} }
} }
} catch (IOException | RuntimeException unreadable) { } catch (IOException | RuntimeException unreadable) {
LOGGER.warn("Iris could not scan the corrupt persistent dimension registry at {} for lost ids", file, unreadable); ModdedIrisLog.warn("Iris could not scan the corrupt persistent dimension registry at {} for lost ids", file, unreadable);
} }
return ids; return ids;
} }
@@ -102,9 +99,9 @@ public final class ModdedDimensionRegistryStore {
Path broken = file.resolveSibling(FILE_NAME + ".broken-" + System.currentTimeMillis()); Path broken = file.resolveSibling(FILE_NAME + ".broken-" + System.currentTimeMillis());
try { try {
Files.move(file, broken, StandardCopyOption.REPLACE_EXISTING); Files.move(file, broken, StandardCopyOption.REPLACE_EXISTING);
LOGGER.error("Iris moved the corrupt persistent dimension registry to {}", broken); ModdedIrisLog.error("Iris moved the corrupt persistent dimension registry to {}", broken);
} catch (IOException failure) { } catch (IOException failure) {
LOGGER.error("Iris could not quarantine the corrupt persistent dimension registry at {}; delete it by hand", ModdedIrisLog.error("Iris could not quarantine the corrupt persistent dimension registry at {}; delete it by hand",
file, failure); file, failure);
} }
} }
@@ -134,14 +131,14 @@ public final class ModdedDimensionRegistryStore {
PersistentDimension previous = deduplicated.putIfAbsent( PersistentDimension previous = deduplicated.putIfAbsent(
id, new PersistentDimension(id, pack, dimension, entry.getLong("seed"))); id, new PersistentDimension(id, pack, dimension, entry.getLong("seed")));
if (previous != null) { if (previous != null) {
LOGGER.warn("Iris persistent dimension registry entry {} in {} duplicates id '{}'; keeping the first", ModdedIrisLog.warn("Iris persistent dimension registry entry {} in {} duplicates id '{}'; keeping the first",
index, file, id); index, file, id);
} }
} catch (RuntimeException invalidEntry) { } catch (RuntimeException invalidEntry) {
if (raw != null) { if (raw != null) {
unparsed.add(raw); unparsed.add(raw);
} }
LOGGER.warn("Iris persistent dimension registry entry {} in {} is invalid ({}); kept verbatim: {}", ModdedIrisLog.warn("Iris persistent dimension registry entry {} in {} is invalid ({}); kept verbatim: {}",
index, file, invalidEntry.getMessage(), raw); index, file, invalidEntry.getMessage(), raw);
} }
} }
@@ -23,8 +23,6 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.storage.LevelResource; import net.minecraft.world.level.storage.LevelResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -35,7 +33,6 @@ import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
public final class ModdedDimensionStorage { public final class ModdedDimensionStorage {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final List<String> CHUNK_DATA_FOLDERS = List.of("region", "entities", "poi", "mantle"); private static final List<String> CHUNK_DATA_FOLDERS = List.of("region", "entities", "poi", "mantle");
private ModdedDimensionStorage() { private ModdedDimensionStorage() {
@@ -56,7 +53,7 @@ public final class ModdedDimensionStorage {
"Iris failed to completely wipe dimension storage at " "Iris failed to completely wipe dimension storage at "
+ storageFolder.getAbsolutePath(), e); + storageFolder.getAbsolutePath(), e);
} }
LOGGER.info("Iris wiped dimension storage at {}", storageFolder.getAbsolutePath()); ModdedIrisLog.info("Iris wiped dimension storage at {}", storageFolder.getAbsolutePath());
} }
private static void deleteRecursively(Path root) throws IOException { private static void deleteRecursively(Path root) throws IOException {
@@ -55,13 +55,10 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.storage.LevelData; import net.minecraft.world.level.storage.LevelData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayDeque; import java.util.ArrayDeque;
public final class ModdedEngineBootstrap { public final class ModdedEngineBootstrap {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String[] CORE_SELF_TEST_CLASSES = { private static final String[] CORE_SELF_TEST_CLASSES = {
"art.arcane.iris.engine.IrisEngine", "art.arcane.iris.engine.IrisEngine",
"art.arcane.iris.util.common.data.B", "art.arcane.iris.util.common.data.B",
@@ -156,7 +153,7 @@ public final class ModdedEngineBootstrap {
try { try {
generator.unbindEngine(level); generator.unbindEngine(level);
} catch (Throwable exception) { } catch (Throwable exception) {
LOGGER.error("Iris engine unload failed for {}", level.dimension().identifier(), exception); ModdedIrisLog.error("Iris engine unload failed for {}", level.dimension().identifier(), exception);
if (exception instanceof RuntimeException runtimeException) { if (exception instanceof RuntimeException runtimeException) {
throw runtimeException; throw runtimeException;
} }
@@ -207,7 +204,7 @@ public final class ModdedEngineBootstrap {
if (failure != null) { if (failure != null) {
// The shutdown path must not propagate: propagating aborts the remaining loader stop handlers and // The shutdown path must not propagate: propagating aborts the remaining loader stop handlers and
// can leave the level unsaved. Every stage already logged its own failure. // can leave the level unsaved. Every stage already logged its own failure.
LOGGER.error("Iris modded shutdown completed with failures", failure); ModdedIrisLog.error("Iris modded shutdown completed with failures", failure);
} }
} }
@@ -216,7 +213,7 @@ public final class ModdedEngineBootstrap {
action.run(); action.run();
return failure; return failure;
} catch (Throwable stageFailure) { } catch (Throwable stageFailure) {
LOGGER.error("Iris modded shutdown stage '{}' failed", stage, stageFailure); ModdedIrisLog.error("Iris modded shutdown stage '{}' failed", stage, stageFailure);
if (failure == null) { if (failure == null) {
return stageFailure; return stageFailure;
} }
@@ -255,7 +252,7 @@ public final class ModdedEngineBootstrap {
BlockPos position = reconciledSpawnPosition(surfaceY, level.getMinY(), level.getHeight()); BlockPos position = reconciledSpawnPosition(surfaceY, level.getMinY(), level.getHeight());
server.setRespawnData(LevelData.RespawnData.of( server.setRespawnData(LevelData.RespawnData.of(
level.dimension(), position, current.yaw(), current.pitch())); level.dimension(), position, current.yaw(), current.pitch()));
LOGGER.info("Iris spawn reconciled for {} at {},{},{}", dimensionId, ModdedIrisLog.info("Iris spawn reconciled for {} at {},{},{}", dimensionId,
position.getX(), position.getY(), position.getZ()); position.getX(), position.getY(), position.getZ());
} }
@@ -321,7 +318,7 @@ public final class ModdedEngineBootstrap {
Class.forName(className, true, classLoader); Class.forName(className, true, classLoader);
loadedClasses++; loadedClasses++;
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris core self-test failed to initialize {}", className, error); ModdedIrisLog.error("Iris core self-test failed to initialize {}", className, error);
} }
} }
@@ -404,7 +401,7 @@ public final class ModdedEngineBootstrap {
ModdedIrisSplash.print(boundLoader); ModdedIrisSplash.print(boundLoader);
} catch (Throwable splashFailure) { } catch (Throwable splashFailure) {
// A cosmetic banner must never roll back the platform bind. // A cosmetic banner must never roll back the platform bind.
LOGGER.warn("Iris splash could not be printed", splashFailure); ModdedIrisLog.warn("Iris splash could not be printed", splashFailure);
} }
createdServices.enableAll(); createdServices.enableAll();
runtime = new BoundRuntime(created, createdServices); runtime = new BoundRuntime(created, createdServices);
@@ -413,7 +410,7 @@ public final class ModdedEngineBootstrap {
} catch (Throwable failure) { } catch (Throwable failure) {
createdServices.rollback(failure); createdServices.rollback(failure);
rollback.restore(failure); rollback.restore(failure);
LOGGER.error("Iris modded platform binding failed", failure); ModdedIrisLog.error("Iris modded platform binding failed", failure);
if (failure instanceof RuntimeException runtimeException) { if (failure instanceof RuntimeException runtimeException) {
throw runtimeException; throw runtimeException;
} }
@@ -39,8 +39,6 @@ import net.minecraft.server.packs.PathPackResources;
import net.minecraft.server.packs.repository.Pack; import net.minecraft.server.packs.repository.Pack;
import net.minecraft.server.packs.repository.PackSource; import net.minecraft.server.packs.repository.PackSource;
import net.minecraft.server.packs.repository.RepositorySource; import net.minecraft.server.packs.repository.RepositorySource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -67,7 +65,6 @@ import java.util.function.Consumer;
import java.util.stream.Stream; import java.util.stream.Stream;
public final class ModdedForcedDatapack { public final class ModdedForcedDatapack {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String PACK_ID = "iris_worldgen"; private static final String PACK_ID = "iris_worldgen";
private static final String PACK_FOLDER = "iris"; private static final String PACK_FOLDER = "iris";
private static final String HASH_FILE_NAME = "packs.hash"; private static final String HASH_FILE_NAME = "packs.hash";
@@ -111,7 +108,7 @@ public final class ModdedForcedDatapack {
return requireReadablePack(current.directory()); return requireReadablePack(current.directory());
} catch (RuntimeException unreadable) { } catch (RuntimeException unreadable) {
published = null; published = null;
LOGGER.error("Iris could not read the published forced datapack at {}; regenerating", ModdedIrisLog.error("Iris could not read the published forced datapack at {}; regenerating",
current.directory(), unreadable); current.directory(), unreadable);
} }
} }
@@ -125,19 +122,19 @@ public final class ModdedForcedDatapack {
reason = "stale cache (hash changed)"; reason = "stale cache (hash changed)";
} else { } else {
if (hash.isEmpty() && STALE_SERVE_LOGGED.compareAndSet(false, true)) { if (hash.isEmpty() && STALE_SERVE_LOGGED.compareAndSet(false, true)) {
LOGGER.warn("Iris cannot hash the installed packs; serving the last generated forced datapack from {} unverified", ModdedIrisLog.warn("Iris cannot hash the installed packs; serving the last generated forced datapack from {} unverified",
state.directory()); state.directory());
} }
try { try {
return requireReadablePack(state.directory()); return requireReadablePack(state.directory());
} catch (RuntimeException unreadable) { } catch (RuntimeException unreadable) {
published = null; published = null;
LOGGER.error("Iris could not read the published forced datapack at {}; regenerating", ModdedIrisLog.error("Iris could not read the published forced datapack at {}; regenerating",
state.directory(), unreadable); state.directory(), unreadable);
} }
reason = "unreadable published pack"; reason = "unreadable published pack";
} }
LOGGER.info("Iris forced datapack cache is unusable ({}); generating it once now", reason); ModdedIrisLog.info("Iris forced datapack cache is unusable ({}); generating it once now", reason);
return buildPack(); return buildPack();
} }
} }
@@ -151,11 +148,11 @@ public final class ModdedForcedDatapack {
if (packs.isEmpty()) { if (packs.isEmpty()) {
return; return;
} }
LOGGER.error("==============================================================="); ModdedIrisLog.error("===============================================================");
LOGGER.error("Iris forced datapack '{}' was never loaded by this server.", PACK_ID); ModdedIrisLog.error("Iris forced datapack '{}' was never loaded by this server.", PACK_ID);
LOGGER.error("{} installed pack(s) at {} contributed no dimension types or custom biomes.", packs.size(), packsRoot); ModdedIrisLog.error("{} installed pack(s) at {} contributed no dimension types or custom biomes.", packs.size(), packsRoot);
LOGGER.error("Datapack source injection failed for this loader (mixin/event not applied), so world creation will fail and restarting will not fix it."); ModdedIrisLog.error("Datapack source injection failed for this loader (mixin/event not applied), so world creation will fail and restarting will not fix it.");
LOGGER.error("==============================================================="); ModdedIrisLog.error("===============================================================");
} }
public static Path datapackRoot() { public static Path datapackRoot() {
@@ -172,7 +169,7 @@ public final class ModdedForcedDatapack {
} catch (RuntimeException | Error generationFailure) { } catch (RuntimeException | Error generationFailure) {
Path lastKnownGood = packDirectory(); Path lastKnownGood = packDirectory();
if (Files.isRegularFile(lastKnownGood.resolve("pack.mcmeta"))) { if (Files.isRegularFile(lastKnownGood.resolve("pack.mcmeta"))) {
LOGGER.error("Iris kept the last known-good generated datapack after regeneration failed", ModdedIrisLog.error("Iris kept the last known-good generated datapack after regeneration failed",
generationFailure); generationFailure);
return requireReadablePack(lastKnownGood); return requireReadablePack(lastKnownGood);
} }
@@ -201,7 +198,7 @@ public final class ModdedForcedDatapack {
try { try {
return write(); return write();
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris failed to generate the forced startup datapack", e); ModdedIrisLog.error("Iris failed to generate the forced startup datapack", e);
if (e instanceof RuntimeException runtimeException) { if (e instanceof RuntimeException runtimeException) {
throw runtimeException; throw runtimeException;
} }
@@ -222,10 +219,10 @@ public final class ModdedForcedDatapack {
String currentHash = packsHashOrEmpty(); String currentHash = packsHashOrEmpty();
PublishedState state = publishedState(); PublishedState state = publishedState();
if (state != null && !currentHash.isEmpty() && state.packsHash().equals(currentHash)) { if (state != null && !currentHash.isEmpty() && state.packsHash().equals(currentHash)) {
LOGGER.debug("Iris forced datapack is current ({}); skipping regeneration", reason); ModdedIrisLog.debug("Iris forced datapack is current ({}); skipping regeneration", reason);
return false; return false;
} }
LOGGER.info("Iris regenerating the forced datapack ({})", reason); ModdedIrisLog.info("Iris regenerating the forced datapack ({})", reason);
regenerate(); regenerate();
return true; return true;
} }
@@ -240,7 +237,7 @@ public final class ModdedForcedDatapack {
try { try {
regenerateIfStale(reason); regenerateIfStale(reason);
} catch (Throwable failure) { } catch (Throwable failure) {
LOGGER.error("Iris forced datapack regeneration failed ({})", reason, failure); ModdedIrisLog.error("Iris forced datapack regeneration failed ({})", reason, failure);
} }
}; };
ModdedScheduler scheduler = ModdedEngineBootstrap.schedulerOrNull(); ModdedScheduler scheduler = ModdedEngineBootstrap.schedulerOrNull();
@@ -301,7 +298,7 @@ public final class ModdedForcedDatapack {
try { try {
return Files.readString(hashFile, StandardCharsets.UTF_8).trim(); return Files.readString(hashFile, StandardCharsets.UTF_8).trim();
} catch (IOException unreadable) { } catch (IOException unreadable) {
LOGGER.warn("Iris could not read the forced datapack hash at {}", hashFile, unreadable); ModdedIrisLog.warn("Iris could not read the forced datapack hash at {}", hashFile, unreadable);
return ""; return "";
} }
} }
@@ -332,7 +329,7 @@ public final class ModdedForcedDatapack {
try { try {
hash = packsHash(); hash = packsHash();
} catch (IOException | RuntimeException failure) { } catch (IOException | RuntimeException failure) {
LOGGER.warn("Iris could not hash the installed packs directory", failure); ModdedIrisLog.warn("Iris could not hash the installed packs directory", failure);
hash = ""; hash = "";
} }
packsHashMemo = new HashMemo(hash, now); packsHashMemo = new HashMemo(hash, now);
@@ -412,9 +409,9 @@ public final class ModdedForcedDatapack {
if (!presetIds.isEmpty()) { if (!presetIds.isEmpty()) {
writeWorldPresetTag(stagingDirectory, presetIds); writeWorldPresetTag(stagingDirectory, presetIds);
} }
LOGGER.info("Iris forced startup datapack staged: {} pack(s), {} world preset(s), {} custom biome(s) at {}", packCount, presetIds.size(), countBiomes(seenBiomes), stagingDirectory); ModdedIrisLog.info("Iris forced startup datapack staged: {} pack(s), {} world preset(s), {} custom biome(s) at {}", packCount, presetIds.size(), countBiomes(seenBiomes), stagingDirectory);
if (packCount == 0) { if (packCount == 0) {
LOGGER.warn("Iris installed NO worldgen packs into the forced datapack - custom biomes and their colors will NOT generate. Install a pack with /iris download pack=overworld, /iris download pack=underworld, or /iris download link=<zip-url>, then restart before creating an Iris world."); ModdedIrisLog.warn("Iris installed NO worldgen packs into the forced datapack - custom biomes and their colors will NOT generate. Install a pack with /iris download pack=overworld, /iris download pack=underworld, or /iris download link=<zip-url>, then restart before creating an Iris world.");
} }
} }
@@ -425,13 +422,13 @@ public final class ModdedForcedDatapack {
try { try {
validation = PackValidator.validateForDatapackBootstrap(sourcePack); validation = PackValidator.validateForDatapackBootstrap(sourcePack);
} catch (Throwable validationFailure) { } catch (Throwable validationFailure) {
LOGGER.error("Iris excluded pack '{}' from Create World because validation failed", ModdedIrisLog.error("Iris excluded pack '{}' from Create World because validation failed",
sourcePack.getName(), validationFailure); sourcePack.getName(), validationFailure);
rethrowIfUnrecoverable(validationFailure); rethrowIfUnrecoverable(validationFailure);
return false; return false;
} }
if (!validation.isLoadable()) { if (!validation.isLoadable()) {
LOGGER.error("Iris excluded pack '{}' from Create World: {} blocking validation error(s); first error: {}", ModdedIrisLog.error("Iris excluded pack '{}' from Create World: {} blocking validation error(s); first error: {}",
sourcePack.getName(), validation.getBlockingErrors().size(), sourcePack.getName(), validation.getBlockingErrors().size(),
validation.getBlockingErrors().getFirst()); validation.getBlockingErrors().getFirst());
return false; return false;
@@ -447,7 +444,7 @@ public final class ModdedForcedDatapack {
try { try {
installed = installPack(sourcePack, fixer, packFolders, packBiomes, packPresetIds); installed = installPack(sourcePack, fixer, packFolders, packBiomes, packPresetIds);
} catch (Throwable installationFailure) { } catch (Throwable installationFailure) {
LOGGER.error("Iris excluded pack '{}' from Create World because datapack serialization failed", ModdedIrisLog.error("Iris excluded pack '{}' from Create World because datapack serialization failed",
sourcePack.getName(), installationFailure); sourcePack.getName(), installationFailure);
rethrowIfUnrecoverable(installationFailure); rethrowIfUnrecoverable(installationFailure);
installed = false; installed = false;
@@ -465,7 +462,7 @@ public final class ModdedForcedDatapack {
try { try {
clean(packStagingDirectory); clean(packStagingDirectory);
} catch (Throwable cleanupFailure) { } catch (Throwable cleanupFailure) {
LOGGER.warn("Iris could not remove temporary datapack staging for pack '{}'", ModdedIrisLog.warn("Iris could not remove temporary datapack staging for pack '{}'",
sourcePack.getName(), cleanupFailure); sourcePack.getName(), cleanupFailure);
} }
} }
@@ -728,7 +725,7 @@ public final class ModdedForcedDatapack {
try { try {
clean(backupDirectory); clean(backupDirectory);
} catch (Throwable cleanupError) { } catch (Throwable cleanupError) {
LOGGER.warn("Iris published the forced datapack but could not remove backup {}", ModdedIrisLog.warn("Iris published the forced datapack but could not remove backup {}",
backupDirectory, cleanupError); backupDirectory, cleanupError);
} }
} }
@@ -18,8 +18,6 @@
package art.arcane.iris.modded; package art.arcane.iris.modded;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@@ -42,7 +40,6 @@ import java.util.concurrent.atomic.AtomicReference;
* missing hop serializes generation on the loader's chunk threads. * missing hop serializes generation on the loader's chunk threads.
*/ */
public final class ModdedGenPool { public final class ModdedGenPool {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final long SHUTDOWN_DRAIN_MILLIS = 2_000L; private static final long SHUTDOWN_DRAIN_MILLIS = 2_000L;
private static final String[] C2ME_MARKERS = { private static final String[] C2ME_MARKERS = {
"com.ishland.c2me.base.ModProperties", "com.ishland.c2me.base.ModProperties",
@@ -116,7 +113,7 @@ public final class ModdedGenPool {
if (pool.awaitTermination(SHUTDOWN_DRAIN_MILLIS, TimeUnit.MILLISECONDS)) { if (pool.awaitTermination(SHUTDOWN_DRAIN_MILLIS, TimeUnit.MILLISECONDS)) {
return; return;
} }
LOGGER.debug("Iris gen pool did not drain in {}ms, forcing shutdown", SHUTDOWN_DRAIN_MILLIS); ModdedIrisLog.debug("Iris gen pool did not drain in {}ms, forcing shutdown", SHUTDOWN_DRAIN_MILLIS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
@@ -131,7 +128,7 @@ public final class ModdedGenPool {
if (detected == null) { if (detected == null) {
detected = new ChunkSystem(false, "vanilla"); detected = new ChunkSystem(false, "vanilla");
} }
LOGGER.info("Iris chunk system: {} (parallel={}, generation on {})", ModdedIrisLog.info("Iris chunk system: {} (parallel={}, generation on {})",
detected.description(), detected.description(),
detected.parallel() ? "yes" : "no", detected.parallel() ? "yes" : "no",
detected.parallel() ? "loader threads" : "Iris gen pool"); detected.parallel() ? "loader threads" : "Iris gen pool");
@@ -182,7 +179,7 @@ public final class ModdedGenPool {
try { try {
value = field.get(null); value = field.get(null);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not read {}.{}: {}", configClass.getName(), section, e.toString()); ModdedIrisLog.debug("Iris chunk system probe could not read {}.{}: {}", configClass.getName(), section, e.toString());
continue; continue;
} }
if (value instanceof Boolean flag) { if (value instanceof Boolean flag) {
@@ -209,7 +206,7 @@ public final class ModdedGenPool {
return flag; return flag;
} }
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not read {}.{}: {}", type.getName(), name, e.toString()); ModdedIrisLog.debug("Iris chunk system probe could not read {}.{}: {}", type.getName(), name, e.toString());
} }
} }
Method method = declaredMethodOrNull(type, name); Method method = declaredMethodOrNull(type, name);
@@ -219,7 +216,7 @@ public final class ModdedGenPool {
return flag; return flag;
} }
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not call {}.{}(): {}", type.getName(), name, e.toString()); ModdedIrisLog.debug("Iris chunk system probe could not call {}.{}(): {}", type.getName(), name, e.toString());
} }
} }
} }
@@ -241,7 +238,7 @@ public final class ModdedGenPool {
return number.intValue(); return number.intValue();
} }
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not read {}.{}: {}", marker.getName(), name, e.toString()); ModdedIrisLog.debug("Iris chunk system probe could not read {}.{}: {}", marker.getName(), name, e.toString());
} }
} }
} }
@@ -254,7 +251,7 @@ public final class ModdedGenPool {
try { try {
pool = field.get(null); pool = field.get(null);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not read {}.{}: {}", marker.getName(), poolName, e.toString()); ModdedIrisLog.debug("Iris chunk system probe could not read {}.{}: {}", marker.getName(), poolName, e.toString());
continue; continue;
} }
if (pool == null) { if (pool == null) {
@@ -281,7 +278,7 @@ public final class ModdedGenPool {
return number.intValue(); return number.intValue();
} }
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not call {}.{}(): {}", current.getName(), name, e.toString()); ModdedIrisLog.debug("Iris chunk system probe could not call {}.{}(): {}", current.getName(), name, e.toString());
} }
} }
return null; return null;
@@ -305,7 +302,7 @@ public final class ModdedGenPool {
} catch (NoSuchFieldException e) { } catch (NoSuchFieldException e) {
return null; return null;
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not access field {}.{}: {}", type.getName(), name, e.toString()); ModdedIrisLog.debug("Iris chunk system probe could not access field {}.{}: {}", type.getName(), name, e.toString());
return null; return null;
} }
} }
@@ -318,7 +315,7 @@ public final class ModdedGenPool {
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
return null; return null;
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.debug("Iris chunk system probe could not access method {}.{}(): {}", type.getName(), name, e.toString()); ModdedIrisLog.debug("Iris chunk system probe could not access method {}.{}(): {}", type.getName(), name, e.toString());
return null; return null;
} }
} }
@@ -336,7 +333,7 @@ public final class ModdedGenPool {
try { try {
return Class.forName(name, false, ModdedGenPool.class.getClassLoader()); return Class.forName(name, false, ModdedGenPool.class.getClassLoader());
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.debug("Iris chunk system probe: {} absent ({})", name, e.getClass().getSimpleName()); ModdedIrisLog.debug("Iris chunk system probe: {} absent ({})", name, e.getClass().getSimpleName());
return null; return null;
} }
} }
@@ -46,8 +46,6 @@ import net.minecraft.world.level.levelgen.RandomSupport;
import net.minecraft.world.level.levelgen.WorldgenRandom; import net.minecraft.world.level.levelgen.WorldgenRandom;
import net.minecraft.world.level.levelgen.XoroshiroRandomSource; import net.minecraft.world.level.levelgen.XoroshiroRandomSource;
import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -76,7 +74,6 @@ import java.util.concurrent.locks.ReentrantLock;
* {@link #generationSettings} answers exactly what vanilla's default getter answers. * {@link #generationSettings} answers exactly what vanilla's default getter answers.
*/ */
final class ModdedImportedFeatureStage { final class ModdedImportedFeatureStage {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String CYCLE_MARKER = "Feature order cycle found"; private static final String CYCLE_MARKER = "Feature order cycle found";
private static final long NO_GENERATION = Long.MIN_VALUE; private static final long NO_GENERATION = Long.MIN_VALUE;
@@ -179,7 +176,7 @@ final class ModdedImportedFeatureStage {
try { try {
control = NativeFeatureGenerationPolicy.control(engine); control = NativeFeatureGenerationPolicy.control(engine);
} catch (RuntimeException error) { } catch (RuntimeException error) {
LOGGER.error("Iris could not read importedFeatures for this dimension; features off: {}", ModdedIrisLog.error("Iris could not read importedFeatures for this dimension; features off: {}",
error.toString()); error.toString());
markInert(generation); markInert(generation);
return; return;
@@ -193,7 +190,7 @@ final class ModdedImportedFeatureStage {
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) { IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
built = buildTable(engine, control, generation); built = buildTable(engine, control, generation);
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris importedFeatures is off for {}: feature table construction failed: {}", ModdedIrisLog.error("Iris importedFeatures is off for {}: feature table construction failed: {}",
dimensionKey(engine), error.toString()); dimensionKey(engine), error.toString());
markInert(generation); markInert(generation);
return; return;
@@ -207,7 +204,7 @@ final class ModdedImportedFeatureStage {
// Arm the worldcheck log watch here, before any chunk decorates: arming from the first pass instead // Arm the worldcheck log watch here, before any chunk decorates: arming from the first pass instead
// missed every far-chunk write the first chunk made. No-op unless -Diris.worldcheck is set. // missed every far-chunk write the first chunk made. No-op unless -Diris.worldcheck is set.
WorldCheckFeaturePlacement.arm(); WorldCheckFeaturePlacement.arm();
LOGGER.info("Iris importedFeatures on for {}: {} biomes, {} steps, {} custom-biome derivative maps", ModdedIrisLog.info("Iris importedFeatures on for {}: {} biomes, {} steps, {} custom-biome derivative maps",
dimensionKey(engine), built.biomes().size(), built.steps().size(), dimensionKey(engine), built.biomes().size(), built.steps().size(),
built.derivatives().size()); built.derivatives().size());
} }
@@ -222,7 +219,7 @@ final class ModdedImportedFeatureStage {
// detection depend on JVM hash order and turns a real cycle into an intermittent one. // detection depend on JVM hash order and turns a real cycle into an intermittent one.
List<Holder<Biome>> biomes = biomeSource.orderedPossibleBiomes(); List<Holder<Biome>> biomes = biomeSource.orderedPossibleBiomes();
if (biomes.isEmpty()) { if (biomes.isEmpty()) {
LOGGER.error("Iris importedFeatures is on but {} exposes no biomes; features off", ModdedIrisLog.error("Iris importedFeatures is on but {} exposes no biomes; features off",
dimensionKey(engine)); dimensionKey(engine));
return null; return null;
} }
@@ -245,7 +242,7 @@ final class ModdedImportedFeatureStage {
if (message == null || !message.contains(CYCLE_MARKER)) { if (message == null || !message.contains(CYCLE_MARKER)) {
throw error; throw error;
} }
LOGGER.error("Iris importedFeatures is off for {}: the registered placed features cannot be ordered." ModdedIrisLog.error("Iris importedFeatures is off for {}: the registered placed features cannot be ordered."
+ " {}. Remove or reorder the conflicting content, or leave" + " {}. Remove or reorder the conflicting content, or leave"
+ " importedFeatures.enabled false.", + " importedFeatures.enabled false.",
dimensionKey(engine), message); dimensionKey(engine), message);
@@ -276,7 +273,7 @@ final class ModdedImportedFeatureStage {
derivative = biomeSource.registeredBiome(derivativeKey); derivative = biomeSource.registeredBiome(derivativeKey);
} }
if (derivative == null) { if (derivative == null) {
LOGGER.warn("Iris importedFeatures: vanilla derivative {} of biome {} is not registered;" ModdedIrisLog.warn("Iris importedFeatures: vanilla derivative {} of biome {} is not registered;"
+ " its custom biomes generate no imported features", + " its custom biomes generate no imported features",
derivativeKey, irisBiome.getLoadKey()); derivativeKey, irisBiome.getLoadKey());
continue; continue;
@@ -54,18 +54,55 @@ public final class ModdedIrisLog {
LOGGER.info("[Iris/DEBUG] " + clean(message)); LOGGER.info("[Iris/DEBUG] " + clean(message));
} }
public static void debug(String format, Object... arguments) {
RenderedLog rendered = render(format, arguments);
if (rendered.error() == null) {
debug(rendered.message());
return;
}
if (!debugEnabled()) {
LOGGER.debug(clean(rendered.message()), rendered.error());
return;
}
LOGGER.info("[Iris/DEBUG] " + clean(rendered.message()), rendered.error());
}
public static void info(String message) { public static void info(String message) {
LOGGER.info(clean(message)); LOGGER.info(clean(message));
} }
public static void info(String format, Object... arguments) {
RenderedLog rendered = render(format, arguments);
if (rendered.error() != null) {
LOGGER.info(clean(rendered.message()), rendered.error());
return;
}
info(rendered.message());
}
public static void warn(String message) { public static void warn(String message) {
LOGGER.warn(clean(message)); LOGGER.warn(clean(message));
} }
public static void warn(String format, Object... arguments) {
RenderedLog rendered = render(format, arguments);
if (rendered.error() != null) {
LOGGER.warn(clean(rendered.message()), rendered.error());
return;
}
warn(rendered.message());
}
public static void error(String message) { public static void error(String message) {
LOGGER.error(clean(message)); LOGGER.error(clean(message));
} }
public static void error(String format, Object... arguments) {
RenderedLog rendered = render(format, arguments);
error(rendered.message(), rendered.error());
}
public static void error(String message, Throwable error) { public static void error(String message, Throwable error) {
if (error == null) { if (error == null) {
error(message); error(message);
@@ -79,6 +116,34 @@ public final class ModdedIrisLog {
return IrisLogging.clean(message); return IrisLogging.clean(message);
} }
static RenderedLog render(String format, Object... arguments) {
String source = format == null ? "null" : format;
if (arguments == null || arguments.length == 0) {
return new RenderedLog(source, null);
}
int argumentCount = arguments.length;
Throwable error = arguments[argumentCount - 1] instanceof Throwable throwable ? throwable : null;
if (error != null) {
argumentCount--;
}
StringBuilder output = new StringBuilder(source.length() + argumentCount * 8);
int cursor = 0;
int argumentIndex = 0;
while (argumentIndex < argumentCount) {
int placeholder = source.indexOf("{}", cursor);
if (placeholder < 0) {
break;
}
output.append(source, cursor, placeholder);
output.append(String.valueOf(arguments[argumentIndex++]));
cursor = placeholder + 2;
}
output.append(source, cursor, source.length());
return new RenderedLog(output.toString(), error);
}
private static boolean debugEnabled() { private static boolean debugEnabled() {
try { try {
IrisSettings settings = IrisSettings.settings != null ? IrisSettings.settings : IrisSettings.get(); IrisSettings settings = IrisSettings.settings != null ? IrisSettings.settings : IrisSettings.get();
@@ -97,4 +162,7 @@ public final class ModdedIrisLog {
DEBUG_SETTING_WARNING_LOGGED = true; DEBUG_SETTING_WARNING_LOGGED = true;
LOGGER.warn("Iris debug logging setting could not be read", error); LOGGER.warn("Iris debug logging setting could not be read", error);
} }
record RenderedLog(String message, Throwable error) {
}
} }
@@ -18,8 +18,6 @@
package art.arcane.iris.modded; package art.arcane.iris.modded;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
@@ -40,7 +38,6 @@ import java.util.function.BooleanSupplier;
* references and is safe on a dedicated server. * references and is safe on a dedicated server.
*/ */
public final class ModdedMixinAudit { public final class ModdedMixinAudit {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final AtomicBoolean AUDITED = new AtomicBoolean(false); private static final AtomicBoolean AUDITED = new AtomicBoolean(false);
private static final List<ExpectedMixin> EXPECTED = List.of( private static final List<ExpectedMixin> EXPECTED = List.of(
@@ -95,20 +92,20 @@ public final class ModdedMixinAudit {
} }
} }
if (missing.isEmpty()) { if (missing.isEmpty()) {
LOGGER.info("Iris mixin audit ok on {} ({} dist): {}", platform, ModdedIrisLog.info("Iris mixin audit ok on {} ({} dist): {}", platform,
clientEnvironment ? "client" : "server", String.join(", ", applied)); clientEnvironment ? "client" : "server", String.join(", ", applied));
return; return;
} }
LOGGER.error("==============================================================="); ModdedIrisLog.error("===============================================================");
LOGGER.error("Iris mixin audit FAILED on {} ({} dist): {} of {} expected mixin(s) were not applied.", ModdedIrisLog.error("Iris mixin audit FAILED on {} ({} dist): {} of {} expected mixin(s) were not applied.",
platform, clientEnvironment ? "client" : "server", missing.size(), platform, clientEnvironment ? "client" : "server", missing.size(),
missing.size() + applied.size()); missing.size() + applied.size());
for (String entry : missing) { for (String entry : missing) {
LOGGER.error(" missing: {}", entry); ModdedIrisLog.error(" missing: {}", entry);
} }
LOGGER.error("The mixin config was not registered for this loader (fabric.mod.json mixins, neoforge.mods.toml [[mixins]], forge MixinConfigs manifest attribute)."); ModdedIrisLog.error("The mixin config was not registered for this loader (fabric.mod.json mixins, neoforge.mods.toml [[mixins]], forge MixinConfigs manifest attribute).");
LOGGER.error("Entity persistence, custom mob loot, parallel structure safety, or Iris world-type labels are disabled until this is fixed."); ModdedIrisLog.error("Entity persistence, custom mob loot, parallel structure safety, or Iris world-type labels are disabled until this is fixed.");
LOGGER.error("==============================================================="); ModdedIrisLog.error("===============================================================");
} }
private static boolean isApplied(ExpectedMixin expected) { private static boolean isApplied(ExpectedMixin expected) {
@@ -126,7 +123,7 @@ public final class ModdedMixinAudit {
} }
return false; return false;
} catch (ClassNotFoundException | LinkageError unavailable) { } catch (ClassNotFoundException | LinkageError unavailable) {
LOGGER.warn("Iris mixin audit could not inspect {}", expected.targetClass(), unavailable); ModdedIrisLog.warn("Iris mixin audit could not inspect {}", expected.targetClass(), unavailable);
return true; return true;
} }
} }
@@ -24,11 +24,8 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets; 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class ModdedModConfig { public final class ModdedModConfig {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Object LOCK = new Object(); private static final Object LOCK = new Object();
private static volatile ModdedModConfig instance; private static volatile ModdedModConfig instance;
@@ -128,7 +125,7 @@ public final class ModdedModConfig {
json.optLong("mainWorldSeed", defaults.mainWorldSeed), json.optLong("mainWorldSeed", defaults.mainWorldSeed),
json.optBoolean("mainWorldAutoRestart", defaults.mainWorldAutoRestart)); json.optBoolean("mainWorldAutoRestart", defaults.mainWorldAutoRestart));
} catch (RuntimeException | IOException e) { } catch (RuntimeException | IOException e) {
LOGGER.error("Iris modded config at {} is invalid; using defaults", file, e); ModdedIrisLog.error("Iris modded config at {} is invalid; using defaults", file, e);
return defaults; return defaults;
} }
} }
@@ -145,7 +142,7 @@ public final class ModdedModConfig {
Files.createDirectories(file.getParent()); Files.createDirectories(file.getParent());
Files.writeString(file, json.toString(4), StandardCharsets.UTF_8); Files.writeString(file, json.toString(4), StandardCharsets.UTF_8);
} catch (IOException e) { } catch (IOException e) {
LOGGER.error("Iris failed to write modded config at {}", file, e); ModdedIrisLog.error("Iris failed to write modded config at {}", file, e);
} }
} }
} }
@@ -29,8 +29,6 @@ import art.arcane.iris.spi.PlatformBiome;
import art.arcane.iris.spi.PlatformBlockState; import art.arcane.iris.spi.PlatformBlockState;
import art.arcane.iris.util.project.hunk.Hunk; import art.arcane.iris.util.project.hunk.Hunk;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -48,7 +46,6 @@ import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
public final class ModdedParityProbe { public final class ModdedParityProbe {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String DIMENSION_KEY = "overworld"; private static final String DIMENSION_KEY = "overworld";
private static final long SEED = 1337L; private static final long SEED = 1337L;
private static final int BIOME_STEP = 4; private static final int BIOME_STEP = 4;
@@ -82,7 +79,7 @@ public final class ModdedParityProbe {
} }
if (server == null) { if (server == null) {
LOGGER.error("[parity] server did not become ready within 10 minutes"); ModdedIrisLog.error("[parity] server did not become ready within 10 minutes");
return; return;
} }
@@ -90,10 +87,10 @@ public final class ModdedParityProbe {
try { try {
match = run(server, config); match = run(server, config);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("[parity] probe failed", e); ModdedIrisLog.error("[parity] probe failed", e);
} }
LOGGER.info("[parity] shutting down dev server (result={})", match ? "MATCH" : "MISMATCH"); ModdedIrisLog.info("[parity] shutting down dev server (result={})", match ? "MATCH" : "MISMATCH");
server.halt(false); server.halt(false);
} }
@@ -112,7 +109,7 @@ public final class ModdedParityProbe {
File packSource = new File(packPath); File packSource = new File(packPath);
if (!packSource.isDirectory()) { if (!packSource.isDirectory()) {
LOGGER.error("[parity] pack folder not found: {}", packSource.getAbsolutePath()); ModdedIrisLog.error("[parity] pack folder not found: {}", packSource.getAbsolutePath());
return false; return false;
} }
@@ -121,14 +118,14 @@ public final class ModdedParityProbe {
File workRoot = Files.createTempDirectory("iris-parity").toFile(); File workRoot = Files.createTempDirectory("iris-parity").toFile();
File pack = clonePack(packSource, workRoot); File pack = clonePack(packSource, workRoot);
LOGGER.info("[parity] pack: {}", packSource.getAbsolutePath()); ModdedIrisLog.info("[parity] pack: {}", packSource.getAbsolutePath());
LOGGER.info("[parity] work copy: {}", pack.getAbsolutePath()); ModdedIrisLog.info("[parity] work copy: {}", pack.getAbsolutePath());
LOGGER.info("[parity] radius: {} ({} chunks)", radius, (2 * radius + 1) * (2 * radius + 1)); ModdedIrisLog.info("[parity] radius: {} ({} chunks)", radius, (2 * radius + 1) * (2 * radius + 1));
IrisData data = IrisData.get(pack); IrisData data = IrisData.get(pack);
IrisDimension dimension = data.getDimensionLoader().load(DIMENSION_KEY); IrisDimension dimension = data.getDimensionLoader().load(DIMENSION_KEY);
if (dimension == null) { if (dimension == null) {
LOGGER.error("[parity] dimension '{}' did not load from {}", DIMENSION_KEY, pack.getAbsolutePath()); ModdedIrisLog.error("[parity] dimension '{}' did not load from {}", DIMENSION_KEY, pack.getAbsolutePath());
return false; return false;
} }
@@ -147,7 +144,7 @@ public final class ModdedParityProbe {
int minY = dimension.getMinHeight(); int minY = dimension.getMinHeight();
int maxY = dimension.getMaxHeight(); int maxY = dimension.getMaxHeight();
int height = maxY - minY; int height = maxY - minY;
LOGGER.info("[parity] engine up: dim={} seed={} minY={} maxY={}", engine.getDimension().getLoadKey(), engine.getSeedManager().getSeed(), minY, maxY); ModdedIrisLog.info("[parity] engine up: dim={} seed={} minY={} maxY={}", engine.getDimension().getLoadKey(), engine.getSeedManager().getSeed(), minY, maxY);
Map<String, String> goldenChunks = new HashMap<>(); Map<String, String> goldenChunks = new HashMap<>();
String goldenCombined = null; String goldenCombined = null;
@@ -173,7 +170,7 @@ public final class ModdedParityProbe {
goldenChunks.put(line.substring(0, second), line); goldenChunks.put(line.substring(0, second), line);
} }
} }
LOGGER.info("[parity] golden: {} ({} chunks, combined={})", goldenPath, goldenChunks.size(), goldenCombined); ModdedIrisLog.info("[parity] golden: {} ({} chunks, combined={})", goldenPath, goldenChunks.size(), goldenCombined);
} }
PlatformBlockState airState = IrisPlatforms.get().registries().air(); PlatformBlockState airState = IrisPlatforms.get().registries().air();
@@ -198,9 +195,9 @@ public final class ModdedParityProbe {
if (!failures.isEmpty()) { if (!failures.isEmpty()) {
failed++; failed++;
LOGGER.error("[parity] chunk {},{} FAILED ({} error(s))", cx, cz, failures.size()); ModdedIrisLog.error("[parity] chunk {},{} FAILED ({} error(s))", cx, cz, failures.size());
for (Throwable failure : failures) { for (Throwable failure : failures) {
LOGGER.error("[parity] chunk {},{} error", cx, cz, failure); ModdedIrisLog.error("[parity] chunk {},{} error", cx, cz, failure);
} }
continue; continue;
} }
@@ -212,9 +209,9 @@ public final class ModdedParityProbe {
String golden = goldenChunks.get(key); String golden = goldenChunks.get(key);
if (golden != null && !golden.equals(line)) { if (golden != null && !golden.equals(line)) {
mismatches.add(key); mismatches.add(key);
LOGGER.warn("[parity] chunk {} MISMATCH", key); ModdedIrisLog.warn("[parity] chunk {} MISMATCH", key);
LOGGER.warn("[parity] golden: {}", golden); ModdedIrisLog.warn("[parity] golden: {}", golden);
LOGGER.warn("[parity] actual: {}", line); ModdedIrisLog.warn("[parity] actual: {}", line);
if (mismatches.size() == 1) { if (mismatches.size() == 1) {
diffDeep(cx, cz, blocks, height, minY); diffDeep(cx, cz, blocks, height, minY);
} }
@@ -231,9 +228,9 @@ public final class ModdedParityProbe {
boolean match = goldenChunks.isEmpty() ? combinedMatch : (chunkMatch && (radius != 8 || combinedMatch)); boolean match = goldenChunks.isEmpty() ? combinedMatch : (chunkMatch && (radius != 8 || combinedMatch));
if (!goldenChunks.isEmpty()) { if (!goldenChunks.isEmpty()) {
LOGGER.info("[parity] per-chunk: {}/{} matched golden ({} failed)", body.size() - mismatches.size(), body.size(), failed); ModdedIrisLog.info("[parity] per-chunk: {}/{} matched golden ({} failed)", body.size() - mismatches.size(), body.size(), failed);
} }
LOGGER.info("[parity] combined={} expected={} {} ({}/{})", ModdedIrisLog.info("[parity] combined={} expected={} {} ({}/{})",
combined.substring(0, 12), expected, match ? "MATCH" : "MISMATCH", body.size() - mismatches.size(), targets.size()); combined.substring(0, 12), expected, match ? "MATCH" : "MISMATCH", body.size() - mismatches.size(), targets.size());
return match; return match;
} }
@@ -246,7 +243,7 @@ public final class ModdedParityProbe {
try { try {
Path goldenDump = Path.of(deepDir, cx + "_" + cz + ".txt"); Path goldenDump = Path.of(deepDir, cx + "_" + cz + ".txt");
if (!Files.exists(goldenDump)) { if (!Files.exists(goldenDump)) {
LOGGER.warn("[parity] no deep dump for chunk {},{} at {}", cx, cz, goldenDump); ModdedIrisLog.warn("[parity] no deep dump for chunk {},{} at {}", cx, cz, goldenDump);
return; return;
} }
List<String> golden = Files.readAllLines(goldenDump, StandardCharsets.UTF_8); List<String> golden = Files.readAllLines(goldenDump, StandardCharsets.UTF_8);
@@ -267,15 +264,15 @@ public final class ModdedParityProbe {
String g = i < golden.size() ? golden.get(i) : "<missing>"; String g = i < golden.size() ? golden.get(i) : "<missing>";
String a = i < actual.size() ? actual.get(i) : "<missing>"; String a = i < actual.size() ? actual.get(i) : "<missing>";
if (!g.equals(a)) { if (!g.equals(a)) {
LOGGER.warn("[parity] deep diff line {}: golden='{}' actual='{}'", i, g, a); ModdedIrisLog.warn("[parity] deep diff line {}: golden='{}' actual='{}'", i, g, a);
shown++; shown++;
} }
} }
File out = new File(IrisPlatforms.get().dataFolder("parity"), "deep-" + cx + "_" + cz + ".txt"); File out = new File(IrisPlatforms.get().dataFolder("parity"), "deep-" + cx + "_" + cz + ".txt");
Files.write(out.toPath(), actual, StandardCharsets.UTF_8); Files.write(out.toPath(), actual, StandardCharsets.UTF_8);
LOGGER.warn("[parity] full actual dump: {}", out.getAbsolutePath()); ModdedIrisLog.warn("[parity] full actual dump: {}", out.getAbsolutePath());
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.warn("[parity] deep diff failed", e); ModdedIrisLog.warn("[parity] deep diff failed", e);
} }
} }
@@ -367,7 +364,7 @@ public final class ModdedParityProbe {
} }
} else { } else {
for (Throwable error : batch) { for (Throwable error : batch) {
LOGGER.warn("[parity] engine-init reported error (non-fatal)", error); ModdedIrisLog.warn("[parity] engine-init reported error (non-fatal)", error);
} }
quietSince = System.currentTimeMillis(); quietSince = System.currentTimeMillis();
} }
@@ -21,8 +21,6 @@ package art.arcane.iris.modded;
import net.minecraft.server.MinecraftServer; 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -31,7 +29,6 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public final class ModdedPrimaryWorldRouter { public final class ModdedPrimaryWorldRouter {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int TICK_INTERVAL = 20; private static final int TICK_INTERVAL = 20;
private static final Set<UUID> routed = ConcurrentHashMap.newKeySet(); private static final Set<UUID> routed = ConcurrentHashMap.newKeySet();
@@ -96,7 +93,7 @@ public final class ModdedPrimaryWorldRouter {
ModdedDimensionManager.teleport(player, server, primary, player.getX(), Double.MIN_VALUE, player.getZ()); ModdedDimensionManager.teleport(player, server, primary, player.getX(), Double.MIN_VALUE, player.getZ());
routed.add(id); routed.add(id);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris failed to route player {} to primary world '{}'", id, primary, e); ModdedIrisLog.error("Iris failed to route player {} to primary world '{}'", id, primary, e);
} }
} }
} }
@@ -19,6 +19,7 @@
package art.arcane.iris.modded; package art.arcane.iris.modded;
import art.arcane.iris.core.protocol.EngineResolver; import art.arcane.iris.core.protocol.EngineResolver;
import art.arcane.iris.core.protocol.IrisCursorRequestService;
import art.arcane.iris.core.protocol.IrisProtocolServer; import art.arcane.iris.core.protocol.IrisProtocolServer;
import art.arcane.iris.core.protocol.IrisSession; import art.arcane.iris.core.protocol.IrisSession;
import art.arcane.iris.core.protocol.IrisSessionRegistry; import art.arcane.iris.core.protocol.IrisSessionRegistry;
@@ -30,8 +31,6 @@ 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.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
@@ -43,7 +42,6 @@ public final class ModdedProtocolHandler {
| IrisProtocol.CAPABILITY_CURSOR | IrisProtocol.CAPABILITY_CURSOR
| IrisProtocol.CAPABILITY_STUDIO; | IrisProtocol.CAPABILITY_STUDIO;
private static final int DIMENSION_SYNC_INTERVAL_TICKS = 5; private static final int DIMENSION_SYNC_INTERVAL_TICKS = 5;
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final ConcurrentHashMap<String, Engine> SESSION_ENGINES = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, Engine> SESSION_ENGINES = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, String> SESSION_LEVELS = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, String> SESSION_LEVELS = new ConcurrentHashMap<>();
@@ -53,6 +51,7 @@ public final class ModdedProtocolHandler {
private static volatile IrisSessionRegistry registry; private static volatile IrisSessionRegistry registry;
private static volatile IrisProtocolServer protocolServer; private static volatile IrisProtocolServer protocolServer;
private static volatile ModdedProtocolTransport transport; private static volatile ModdedProtocolTransport transport;
private static volatile IrisCursorRequestService cursorRequests;
private static volatile IrisVisionRequestService visionRequests; private static volatile IrisVisionRequestService visionRequests;
private static int dimensionSyncTicks; private static int dimensionSyncTicks;
@@ -80,11 +79,14 @@ public final class ModdedProtocolHandler {
return engine == null || engine.isClosed() ? null : engine; return engine == null || engine.isClosed() ? null : engine;
}; };
protocol.setEngineResolver(engineResolver); protocol.setEngineResolver(engineResolver);
IrisCursorRequestService cursorService = IrisCursorRequestService.create(engineResolver, sessionRegistry);
protocol.setCursorInfoHandler(cursorService);
IrisVisionRequestService visionService = IrisVisionRequestService.create(engineResolver, sessionRegistry); IrisVisionRequestService visionService = IrisVisionRequestService.create(engineResolver, sessionRegistry);
protocol.setVisionTileHandler(visionService); protocol.setVisionTileHandler(visionService);
registry = sessionRegistry; registry = sessionRegistry;
transport = serverTransport; transport = serverTransport;
protocolServer = protocol; protocolServer = protocol;
cursorRequests = cursorService;
visionRequests = visionService; visionRequests = visionService;
IrisServices.register(IrisProtocolServer.class, protocol); IrisServices.register(IrisProtocolServer.class, protocol);
if (server.getPlayerList() == null) { if (server.getPlayerList() == null) {
@@ -98,10 +100,14 @@ public final class ModdedProtocolHandler {
public static void stop() { public static void stop() {
IrisServices.remove(IrisProtocolServer.class); IrisServices.remove(IrisProtocolServer.class);
IrisSessionRegistry current = registry; IrisSessionRegistry current = registry;
IrisCursorRequestService cursor = cursorRequests;
IrisVisionRequestService vision = visionRequests; IrisVisionRequestService vision = visionRequests;
if (current != null) { if (current != null) {
for (IrisSession session : current.all()) { for (IrisSession session : current.all()) {
current.unregister(session.id()); current.unregister(session.id());
if (cursor != null) {
cursor.clearSession(session.id());
}
if (vision != null) { if (vision != null) {
vision.clearSession(session.id()); vision.clearSession(session.id());
} }
@@ -114,6 +120,7 @@ public final class ModdedProtocolHandler {
registry = null; registry = null;
protocolServer = null; protocolServer = null;
transport = null; transport = null;
cursorRequests = null;
visionRequests = null; visionRequests = null;
} }
@@ -147,6 +154,10 @@ public final class ModdedProtocolHandler {
if (current != null) { if (current != null) {
current.unregister(sessionId); current.unregister(sessionId);
} }
IrisCursorRequestService cursor = cursorRequests;
if (cursor != null) {
cursor.clearSession(sessionId);
}
IrisVisionRequestService vision = visionRequests; IrisVisionRequestService vision = visionRequests;
if (vision != null) { if (vision != null) {
vision.clearSession(sessionId); vision.clearSession(sessionId);
@@ -229,7 +240,7 @@ public final class ModdedProtocolHandler {
Engine engine = generator.engineIfBound(); Engine engine = generator.engineIfBound();
return engine == null || engine.isClosed() ? null : engine; return engine == null || engine.isClosed() ? null : engine;
} catch (Throwable failure) { } catch (Throwable failure) {
LOGGER.error("Iris dimension status engine lookup failed for {}", level.dimension().identifier(), failure); ModdedIrisLog.error("Iris dimension status engine lookup failed for {}", level.dimension().identifier(), failure);
return null; return null;
} }
} }
@@ -21,8 +21,6 @@ package art.arcane.iris.modded;
import art.arcane.iris.spi.PlatformScheduler; import art.arcane.iris.spi.PlatformScheduler;
import art.arcane.iris.spi.PlatformWorld; import art.arcane.iris.spi.PlatformWorld;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -41,7 +39,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
public final class ModdedScheduler implements PlatformScheduler { public final class ModdedScheduler implements PlatformScheduler {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int ASYNC_MAX_THREADS = Math.max(4, Runtime.getRuntime().availableProcessors()); private static final int ASYNC_MAX_THREADS = Math.max(4, Runtime.getRuntime().availableProcessors());
private static final long ASYNC_KEEP_ALIVE_SECONDS = 30L; private static final long ASYNC_KEEP_ALIVE_SECONDS = 30L;
private static final int ASYNC_BACKLOG_WARN = 8192; private static final int ASYNC_BACKLOG_WARN = 8192;
@@ -85,10 +82,10 @@ public final class ModdedScheduler implements PlatformScheduler {
rejectionAwareTask.reject(); rejectionAwareTask.reject();
} }
if (executor.isShutdown()) { if (executor.isShutdown()) {
LOGGER.debug("Iris async task dropped: scheduler is shut down"); ModdedIrisLog.debug("Iris async task dropped: scheduler is shut down");
return; return;
} }
LOGGER.error("Iris async task rejected by the executor (queued={} active={})", ModdedIrisLog.error("Iris async task rejected by the executor (queued={} active={})",
executor.getQueue().size(), executor.getActiveCount()); executor.getQueue().size(), executor.getActiveCount());
}; };
} }
@@ -217,7 +214,7 @@ public final class ModdedScheduler implements PlatformScheduler {
if (now - last < ASYNC_BACKLOG_WARN_INTERVAL_MILLIS || !lastBacklogWarnAt.compareAndSet(last, now)) { if (now - last < ASYNC_BACKLOG_WARN_INTERVAL_MILLIS || !lastBacklogWarnAt.compareAndSet(last, now)) {
return; return;
} }
LOGGER.warn("Iris async backlog {} tasks (threads={}); async work is falling behind", queued, executor.getPoolSize()); ModdedIrisLog.warn("Iris async backlog {} tasks (threads={}); async work is falling behind", queued, executor.getPoolSize());
} }
private void drain() { private void drain() {
@@ -262,7 +259,7 @@ public final class ModdedScheduler implements PlatformScheduler {
try { try {
task.run(); task.run();
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris scheduled task failed", error); ModdedIrisLog.error("Iris scheduled task failed", error);
} }
} }
@@ -23,8 +23,6 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.LevelStorageSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ConcurrentModificationException; import java.util.ConcurrentModificationException;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -35,7 +33,6 @@ import java.util.concurrent.Executor;
import java.util.function.Consumer; import java.util.function.Consumer;
public final class ModdedServerLevels implements ModdedServerAccess { public final class ModdedServerLevels implements ModdedServerAccess {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int CAPTURE_ATTEMPTS = 16; private static final int CAPTURE_ATTEMPTS = 16;
private static volatile Snapshot snapshot; private static volatile Snapshot snapshot;
@@ -125,7 +122,7 @@ public final class ModdedServerLevels implements ModdedServerAccess {
Thread.onSpinWait(); Thread.onSpinWait();
} }
} }
LOGGER.error("Iris could not snapshot the level map after {} attempts; readers will see the previous snapshot", CAPTURE_ATTEMPTS); ModdedIrisLog.error("Iris could not snapshot the level map after {} attempts; readers will see the previous snapshot", CAPTURE_ATTEMPTS);
Snapshot current = snapshot; Snapshot current = snapshot;
return current != null && current.server() == server ? current : null; return current != null && current.server() == server ? current : null;
} }
@@ -21,14 +21,11 @@ package art.arcane.iris.modded;
import art.arcane.iris.modded.service.ModdedService; import art.arcane.iris.modded.service.ModdedService;
import art.arcane.iris.modded.service.ModdedTickableService; import art.arcane.iris.modded.service.ModdedTickableService;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
public final class ModdedServiceManager { public final class ModdedServiceManager {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private final Map<Class<? extends ModdedService>, ModdedService> services = new LinkedHashMap<>(); private final Map<Class<? extends ModdedService>, ModdedService> services = new LinkedHashMap<>();
private boolean enabled = false; private boolean enabled = false;
@@ -103,7 +100,7 @@ public final class ModdedServiceManager {
service.onDisable(); service.onDisable();
} catch (Throwable serviceFailure) { } catch (Throwable serviceFailure) {
failed++; failed++;
LOGGER.error("Iris service onDisable failed for {}", service.getClass().getName(), serviceFailure); ModdedIrisLog.error("Iris service onDisable failed for {}", service.getClass().getName(), serviceFailure);
if (failure == null) { if (failure == null) {
failure = serviceFailure; failure = serviceFailure;
} else if (serviceFailure != failure) { } else if (serviceFailure != failure) {
@@ -113,7 +110,7 @@ public final class ModdedServiceManager {
} }
enabled = false; enabled = false;
if (failure != null) { if (failure != null) {
LOGGER.error("Iris disabled all services with {} failure(s)", failed, failure); ModdedIrisLog.error("Iris disabled all services with {} failure(s)", failed, failure);
} }
} }
@@ -132,7 +129,7 @@ public final class ModdedServiceManager {
try { try {
service.onServerTick(server); service.onServerTick(server);
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris service tick failed for {}", service.getClass().getName(), error); ModdedIrisLog.error("Iris service tick failed for {}", service.getClass().getName(), error);
} }
} }
@@ -143,12 +140,12 @@ public final class ModdedServiceManager {
if (cleanupError != failure) { if (cleanupError != failure) {
failure.addSuppressed(cleanupError); failure.addSuppressed(cleanupError);
} }
LOGGER.error("Iris service rollback failed for {}", service.getClass().getName(), cleanupError); ModdedIrisLog.error("Iris service rollback failed for {}", service.getClass().getName(), cleanupError);
} }
} }
private RuntimeException serviceFailure(ModdedService service, Throwable failure) { private RuntimeException serviceFailure(ModdedService service, Throwable failure) {
LOGGER.error("Iris service onEnable failed for {}", service.getClass().getName(), failure); ModdedIrisLog.error("Iris service onEnable failed for {}", service.getClass().getName(), failure);
if (failure instanceof RuntimeException runtimeException) { if (failure instanceof RuntimeException runtimeException) {
return runtimeException; return runtimeException;
} }
@@ -29,8 +29,6 @@ import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -43,7 +41,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream; import java.util.stream.Stream;
public final class ModdedStartup { public final class ModdedStartup {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final AtomicBoolean PREPARED = new AtomicBoolean(false); private static final AtomicBoolean PREPARED = new AtomicBoolean(false);
private static final AtomicBoolean STARTED = new AtomicBoolean(false); private static final AtomicBoolean STARTED = new AtomicBoolean(false);
@@ -76,7 +73,7 @@ public final class ModdedStartup {
} }
if (!PackDirectoryResolver.listVisiblePackDirectories(legacy).isEmpty()) { if (!PackDirectoryResolver.listVisiblePackDirectories(legacy).isEmpty()) {
File real = art.arcane.iris.spi.IrisPlatforms.get().packsFolderNoCreate(); File real = art.arcane.iris.spi.IrisPlatforms.get().packsFolderNoCreate();
LOGGER.warn("Iris found packs under the legacy directory {} - modded packs load from {} only. Move them there.", ModdedIrisLog.warn("Iris found packs under the legacy directory {} - modded packs load from {} only. Move them there.",
legacy.getAbsolutePath(), real.getAbsolutePath()); legacy.getAbsolutePath(), real.getAbsolutePath());
return; return;
} }
@@ -85,7 +82,7 @@ public final class ModdedStartup {
legacy.delete(); legacy.delete();
} }
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.debug("Iris legacy packs directory check failed", e); ModdedIrisLog.debug("Iris legacy packs directory check failed", e);
} }
} }
@@ -104,7 +101,7 @@ public final class ModdedStartup {
try { try {
ModdedForcedDatapack.regenerateIfStale("boot"); ModdedForcedDatapack.regenerateIfStale("boot");
} catch (Throwable failure) { } catch (Throwable failure) {
LOGGER.error("Iris could not refresh the forced datapack at boot", failure); ModdedIrisLog.error("Iris could not refresh the forced datapack at boot", failure);
} }
} }
@@ -133,7 +130,7 @@ public final class ModdedStartup {
List<File> packDirs = PackDirectoryResolver.listVisiblePackDirectories(packsRoot); List<File> packDirs = PackDirectoryResolver.listVisiblePackDirectories(packsRoot);
PackValidationRegistry.clear(); PackValidationRegistry.clear();
if (packDirs.isEmpty()) { if (packDirs.isEmpty()) {
LOGGER.info("Iris found no packs to validate under {}; install one with /iris download pack=overworld, /iris download pack=underworld, or /iris download link=<zip-url>", ModdedIrisLog.info("Iris found no packs to validate under {}; install one with /iris download pack=overworld, /iris download pack=underworld, or /iris download link=<zip-url>",
packsRoot.getAbsolutePath()); packsRoot.getAbsolutePath());
return; return;
} }
@@ -142,19 +139,19 @@ 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 with {} blocking error(s); world/studio creation will be refused. First error: {}", ModdedIrisLog.error("Iris pack '{}' FAILED validation with {} blocking error(s); world/studio creation will be refused. First error: {}",
result.getPackName(), result.getBlockingErrors().size(), result.getPackName(), result.getBlockingErrors().size(),
result.getBlockingErrors().getFirst()); 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()); ModdedIrisLog.info("Iris pack '{}' validated ({} warning(s)).", result.getPackName(), result.getWarnings().size());
for (String warning : result.getWarnings()) { for (String warning : result.getWarnings()) {
LOGGER.warn(" [{}] {}", result.getPackName(), warning); ModdedIrisLog.warn(" [{}] {}", result.getPackName(), warning);
} }
} else { } else {
LOGGER.info("Iris pack '{}' validated.", result.getPackName()); ModdedIrisLog.info("Iris pack '{}' validated.", result.getPackName());
} }
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris pack validation failed for '{}'", packDir.getName(), e); ModdedIrisLog.error("Iris pack validation failed for '{}'", packDir.getName(), e);
String detail = e.getMessage(); String detail = e.getMessage();
if (detail == null || detail.isBlank()) { if (detail == null || detail.isBlank()) {
detail = e.getClass().getSimpleName(); detail = e.getClass().getSimpleName();
@@ -190,7 +187,7 @@ public final class ModdedStartup {
} catch (BrokenPackException e) { } catch (BrokenPackException e) {
throw e; throw e;
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris required world-creation validation failed for '{}'", pack, e); ModdedIrisLog.error("Iris required world-creation validation failed for '{}'", pack, e);
String detail = e.getMessage(); String detail = e.getMessage();
if (detail == null || detail.isBlank()) { if (detail == null || detail.isBlank()) {
detail = e.getClass().getSimpleName(); detail = e.getClass().getSimpleName();
@@ -220,17 +217,17 @@ public final class ModdedStartup {
try { try {
ModdedDimensionManager.create(server, dimension.id(), dimension.pack(), dimension.dimension(), dimension.seed()); ModdedDimensionManager.create(server, dimension.id(), dimension.pack(), dimension.dimension(), dimension.seed());
injected++; injected++;
LOGGER.info("Iris re-injected {}/{} '{}' (pack={} dim={}) in {}ms", ModdedIrisLog.info("Iris re-injected {}/{} '{}' (pack={} dim={}) in {}ms",
index, dimensions.size(), dimension.id(), dimension.pack(), dimension.dimension(), index, dimensions.size(), dimension.id(), dimension.pack(), dimension.dimension(),
System.currentTimeMillis() - dimensionStartedAt); System.currentTimeMillis() - dimensionStartedAt);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris failed to re-inject persistent dimension '{}' (pack={} dim={} seed={})", dimension.id(), dimension.pack(), dimension.dimension(), dimension.seed(), e); ModdedIrisLog.error("Iris failed to re-inject persistent dimension '{}' (pack={} dim={} seed={})", dimension.id(), dimension.pack(), dimension.dimension(), dimension.seed(), e);
if (e instanceof OutOfMemoryError outOfMemory) { if (e instanceof OutOfMemoryError outOfMemory) {
throw outOfMemory; throw outOfMemory;
} }
} }
} }
LOGGER.info("Iris re-injected {}/{} persistent dimension(s) at startup in {}ms", ModdedIrisLog.info("Iris re-injected {}/{} persistent dimension(s) at startup in {}ms",
injected, dimensions.size(), System.currentTimeMillis() - startedAt); injected, dimensions.size(), System.currentTimeMillis() - startedAt);
} }
@@ -245,7 +242,7 @@ public final class ModdedStartup {
} }
} }
} catch (IOException | RuntimeException unreadable) { } catch (IOException | RuntimeException unreadable) {
LOGGER.debug("Iris could not stat {} for validation reuse; revalidating", root, unreadable); ModdedIrisLog.debug("Iris could not stat {} for validation reuse; revalidating", root, unreadable);
return Long.MAX_VALUE; return Long.MAX_VALUE;
} }
return newest; return newest;
@@ -32,8 +32,6 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.Heightmap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.MessageDigest; import java.security.MessageDigest;
@@ -53,7 +51,6 @@ public final class ModdedWorldCheck {
private static final long SERVER_WAIT_TIMEOUT_MILLIS = 600000L; private static final long SERVER_WAIT_TIMEOUT_MILLIS = 600000L;
private static final long SERVER_WAIT_INTERVAL_MILLIS = 250L; private static final long SERVER_WAIT_INTERVAL_MILLIS = 250L;
private static final long SERVER_TASK_TIMEOUT_MILLIS = 900000L; private static final long SERVER_TASK_TIMEOUT_MILLIS = 900000L;
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
// halt, not exit: awaitStopAndExit already waited for MinecraftServer.halt(true), and exit() would run the // halt, not exit: awaitStopAndExit already waited for MinecraftServer.halt(true), and exit() would run the
// shutdown hooks and block behind the server thread it just stopped, so a finished check could hang forever. // shutdown hooks and block behind the server thread it just stopped, so a finished check could hang forever.
private static final ProcessExit PROCESS_EXIT = Runtime.getRuntime()::halt; private static final ProcessExit PROCESS_EXIT = Runtime.getRuntime()::halt;
@@ -100,7 +97,7 @@ public final class ModdedWorldCheck {
} }
if (server == null) { if (server == null) {
LOGGER.error("[worldcheck] server did not finish starting within 10 minutes"); ModdedIrisLog.error("[worldcheck] server did not finish starting within 10 minutes");
return; return;
} }
@@ -115,15 +112,15 @@ public final class ModdedWorldCheck {
} }
)).get(SERVER_TASK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); )).get(SERVER_TASK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOGGER.error("[worldcheck] coordinator interrupted", e); ModdedIrisLog.error("[worldcheck] coordinator interrupted", e);
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} catch (TimeoutException e) { } catch (TimeoutException e) {
LOGGER.error("[worldcheck] server task did not finish within {}ms", SERVER_TASK_TIMEOUT_MILLIS); ModdedIrisLog.error("[worldcheck] server task did not finish within {}ms", SERVER_TASK_TIMEOUT_MILLIS);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("[worldcheck] check failed", e); ModdedIrisLog.error("[worldcheck] check failed", e);
} finally { } finally {
int resultCode = exitCode; int resultCode = exitCode;
LOGGER.info("[worldcheck] shutting down dev server (result={})", resultCode == EXIT_PASS ? "PASS" : "FAIL"); ModdedIrisLog.info("[worldcheck] shutting down dev server (result={})", resultCode == EXIT_PASS ? "PASS" : "FAIL");
MinecraftServer serverRef = server; MinecraftServer serverRef = server;
if (serverRef != null && stopRequested.get()) { if (serverRef != null && stopRequested.get()) {
awaitStopAndExit(() -> serverRef.halt(true), resultCode, processExit); awaitStopAndExit(() -> serverRef.halt(true), resultCode, processExit);
@@ -138,12 +135,12 @@ public final class ModdedWorldCheck {
try { try {
exitCode = check.getAsBoolean() ? EXIT_PASS : EXIT_FAILURE; exitCode = check.getAsBoolean() ? EXIT_PASS : EXIT_FAILURE;
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("[worldcheck] check failed", e); ModdedIrisLog.error("[worldcheck] check failed", e);
} }
try { try {
requestStop.run(); requestStop.run();
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("[worldcheck] server stop request failed", e); ModdedIrisLog.error("[worldcheck] server stop request failed", e);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
return exitCode; return exitCode;
@@ -159,7 +156,7 @@ public final class ModdedWorldCheck {
awaitStop.run(); awaitStop.run();
} catch (Throwable e) { } catch (Throwable e) {
exitCode = EXIT_FAILURE; exitCode = EXIT_FAILURE;
LOGGER.error("[worldcheck] waiting for server shutdown failed", e); ModdedIrisLog.error("[worldcheck] waiting for server shutdown failed", e);
} finally { } finally {
if (interrupted) { if (interrupted) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
@@ -171,25 +168,25 @@ public final class ModdedWorldCheck {
private static WorldCheckPreparation run(MinecraftServer server) { private static WorldCheckPreparation run(MinecraftServer server) {
ServerLevel level = targetLevel(server); ServerLevel level = targetLevel(server);
if (level == null) { if (level == null) {
LOGGER.error("[worldcheck] no Iris dimension is loaded"); ModdedIrisLog.error("[worldcheck] no Iris dimension is loaded");
return new WorldCheckPreparation(false, false, false, false, return new WorldCheckPreparation(false, false, false, false,
new NativeStructureGate(false, 0, false, null)); new NativeStructureGate(false, 0, false, null));
} }
String levelId = level.dimension().identifier().toString(); String levelId = level.dimension().identifier().toString();
String generatorClass = level.getChunkSource().getGenerator().getClass().getName(); String generatorClass = level.getChunkSource().getGenerator().getClass().getName();
LOGGER.info("[worldcheck] {} generator: {}", levelId, generatorClass); ModdedIrisLog.info("[worldcheck] {} generator: {}", levelId, generatorClass);
IrisModdedChunkGenerator generator = level.getChunkSource().getGenerator() instanceof IrisModdedChunkGenerator iris IrisModdedChunkGenerator generator = level.getChunkSource().getGenerator() instanceof IrisModdedChunkGenerator iris
? iris : null; ? iris : null;
boolean irisGenerator = generator != null; boolean irisGenerator = generator != null;
if (!irisGenerator) { if (!irisGenerator) {
LOGGER.error("[worldcheck] {} is NOT using IrisModdedChunkGenerator", levelId); ModdedIrisLog.error("[worldcheck] {} is NOT using IrisModdedChunkGenerator", levelId);
} }
boolean dimensionTypeOk = generator != null boolean dimensionTypeOk = generator != null
&& WorldCheckDimensionContract.checkDimensionType(level, generator); && WorldCheckDimensionContract.checkDimensionType(level, generator);
BlockPos spawn = level.getRespawnData().pos(); BlockPos spawn = level.getRespawnData().pos();
LOGGER.info("[worldcheck] spawn: {} {} {} (minY={} height={})", spawn.getX(), spawn.getY(), spawn.getZ(), level.getMinY(), level.getHeight()); ModdedIrisLog.info("[worldcheck] spawn: {} {} {} (minY={} height={})", spawn.getX(), spawn.getY(), spawn.getZ(), level.getMinY(), level.getHeight());
MessageDigest digest = WorldCheckPredicates.sha256(); MessageDigest digest = WorldCheckPredicates.sha256();
List<String> samples = new ArrayList<>(); List<String> samples = new ArrayList<>();
@@ -210,9 +207,9 @@ public final class ModdedWorldCheck {
} }
for (int i = 0; i < Math.min(6, samples.size()); i++) { for (int i = 0; i < Math.min(6, samples.size()); i++) {
LOGGER.info("[worldcheck] surface sample: {}", samples.get(i)); ModdedIrisLog.info("[worldcheck] surface sample: {}", samples.get(i));
} }
LOGGER.info("[worldcheck] surface digest: {} ({} columns, {} distinct surface blocks: {})", ModdedIrisLog.info("[worldcheck] surface digest: {} ({} columns, {} distinct surface blocks: {})",
HexFormat.of().formatHex(digest.digest()).substring(0, 12), samples.size(), surfaceKeys.size(), surfaceKeys); HexFormat.of().formatHex(digest.digest()).substring(0, 12), samples.size(), surfaceKeys.size(), surfaceKeys);
ChunkAccess zeroChunk = level.getChunk(0, 0); ChunkAccess zeroChunk = level.getChunk(0, 0);
@@ -230,16 +227,16 @@ public final class ModdedWorldCheck {
columnKeys.add(BuiltInRegistries.BLOCK.getKey(state.getBlock()).toString()); columnKeys.add(BuiltInRegistries.BLOCK.getKey(state.getBlock()).toString());
} }
} }
LOGGER.info("[worldcheck] chunk 0,0: {} non-empty sections of {}; column blocks at (8,*,8): {}", ModdedIrisLog.info("[worldcheck] chunk 0,0: {} non-empty sections of {}; column blocks at (8,*,8): {}",
nonEmptySections, zeroChunk.getSections().length, columnKeys); nonEmptySections, zeroChunk.getSections().length, columnKeys);
boolean sectionsOk = nonEmptySections >= 4; boolean sectionsOk = nonEmptySections >= 4;
boolean varietyOk = columnKeys.size() >= 2 || surfaceKeys.size() >= 2; boolean varietyOk = columnKeys.size() >= 2 || surfaceKeys.size() >= 2;
if (!sectionsOk) { if (!sectionsOk) {
LOGGER.error("[worldcheck] chunk 0,0 looks empty/vanilla-flat ({} non-empty sections)", nonEmptySections); ModdedIrisLog.error("[worldcheck] chunk 0,0 looks empty/vanilla-flat ({} non-empty sections)", nonEmptySections);
} }
if (!varietyOk) { if (!varietyOk) {
LOGGER.error("[worldcheck] generated terrain has no block variety (flat-world signature)"); ModdedIrisLog.error("[worldcheck] generated terrain has no block variety (flat-world signature)");
} }
boolean entityMixinsOk = WorldCheckDimensionContract.checkEntityMixins(level); boolean entityMixinsOk = WorldCheckDimensionContract.checkEntityMixins(level);
@@ -262,7 +259,7 @@ public final class ModdedWorldCheck {
WorldCheckPredicates.qaEvent("village_poi_metric", "village", poiOk, WorldCheckPredicates.qaEvent("village_poi_metric", "village", poiOk,
"inBounds=" + poi.inBounds() + ",outOfBounds=" + poi.outOfBounds()); "inBounds=" + poi.inBounds() + ",outOfBounds=" + poi.outOfBounds());
if (!poiOk) { if (!poiOk) {
LOGGER.error("[worldcheck] village POI audit failed: inBounds={} outOfBounds={}", ModdedIrisLog.error("[worldcheck] village POI audit failed: inBounds={} outOfBounds={}",
poi.inBounds(), poi.outOfBounds()); poi.inBounds(), poi.outOfBounds());
} }
} else { } else {
@@ -271,12 +268,12 @@ public final class ModdedWorldCheck {
int passed = structureGate.nonVillagePassed() int passed = structureGate.nonVillagePassed()
+ (structureGate.villagePassBeforePoi() && poiOk ? 1 : 0); + (structureGate.villagePassBeforePoi() && poiOk ? 1 : 0);
boolean structurePass = structureGate.passBeforePoi() && poiOk; boolean structurePass = structureGate.passBeforePoi() && poiOk;
LOGGER.info("[worldcheck] native structure gate: {}/{} passed", passed, ModdedIrisLog.info("[worldcheck] native structure gate: {}/{} passed", passed,
WorldCheckStructureAudit.STRUCTURE_CHECKS.size()); WorldCheckStructureAudit.STRUCTURE_CHECKS.size());
WorldCheckPredicates.qaEvent("structure_aggregate", "all", structurePass, WorldCheckPredicates.qaEvent("structure_aggregate", "all", structurePass,
"passed=" + passed + ",total=" + WorldCheckStructureAudit.STRUCTURE_CHECKS.size()); "passed=" + passed + ",total=" + WorldCheckStructureAudit.STRUCTURE_CHECKS.size());
boolean pass = preparation.nonStructurePass() && structurePass; boolean pass = preparation.nonStructurePass() && structurePass;
LOGGER.info("[worldcheck] {}", pass ? "PASS" : "FAIL"); ModdedIrisLog.info("[worldcheck] {}", pass ? "PASS" : "FAIL");
WorldCheckPredicates.qaEvent("worldcheck_final", "all", pass, WorldCheckPredicates.qaEvent("worldcheck_final", "all", pass,
"structures=" + WorldCheckStructureAudit.STRUCTURE_CHECKS.size() "structures=" + WorldCheckStructureAudit.STRUCTURE_CHECKS.size()
+ ",terrain=" + preparation.terrainOk() + ",terrain=" + preparation.terrainOk()
@@ -295,7 +292,7 @@ public final class ModdedWorldCheck {
if (requested != null) { if (requested != null) {
return requested; return requested;
} }
LOGGER.error("[worldcheck] requested dimension '{}' is not loaded", target); ModdedIrisLog.error("[worldcheck] requested dimension '{}' is not loaded", target);
return null; return null;
} }
@@ -31,8 +31,6 @@ import art.arcane.iris.modded.command.ModdedGuiHost;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.storage.LevelResource; import net.minecraft.world.level.storage.LevelResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@@ -42,7 +40,6 @@ import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public final class ModdedWorldEngines { public final class ModdedWorldEngines {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final ConcurrentHashMap<ServerLevel, Engine> ENGINES = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<ServerLevel, Engine> ENGINES = new ConcurrentHashMap<>();
private ModdedWorldEngines() { private ModdedWorldEngines() {
@@ -64,7 +61,7 @@ public final class ModdedWorldEngines {
try { try {
evictOrThrow(level); evictOrThrow(level);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris engine evict close failed for {}", level.dimension().identifier(), e); ModdedIrisLog.error("Iris engine evict close failed for {}", level.dimension().identifier(), e);
} }
} }
@@ -80,7 +77,7 @@ public final class ModdedWorldEngines {
} }
// The GUI host holds strong Engine/ServerLevel references with no other remove path. // The GUI host holds strong Engine/ServerLevel references with no other remove path.
ModdedGuiHost.unbind(removed[0]); ModdedGuiHost.unbind(removed[0]);
LOGGER.info("Iris engine evicted for {}", level.dimension().identifier()); ModdedIrisLog.info("Iris engine evicted for {}", level.dimension().identifier());
} }
static Engine prepareReplacement(ServerLevel level, String pack, String dimensionKey, long seedOverride) { static Engine prepareReplacement(ServerLevel level, String pack, String dimensionKey, long seedOverride) {
@@ -111,7 +108,7 @@ public final class ModdedWorldEngines {
IrisData data = IrisData.openRuntime(packDir); IrisData data = IrisData.openRuntime(packDir);
IrisDimension dimension = data.getDimensionLoader().load(dimensionKey); IrisDimension dimension = data.getDimensionLoader().load(dimensionKey);
if (dimension == null) { if (dimension == null) {
LOGGER.error("Iris pack '{}' at {} does not contain dimension '{}' (expected dimensions/{}.json). Install a matching Iris pack and restart.", ModdedIrisLog.error("Iris pack '{}' at {} does not contain dimension '{}' (expected dimensions/{}.json). Install a matching Iris pack and restart.",
pack, packDir.getAbsolutePath(), dimensionKey, dimensionKey); pack, packDir.getAbsolutePath(), dimensionKey, dimensionKey);
throw new IllegalStateException("Iris dimension '" + dimensionKey + "' missing from pack " + packDir.getAbsolutePath()); throw new IllegalStateException("Iris dimension '" + dimensionKey + "' missing from pack " + packDir.getAbsolutePath());
} }
@@ -142,7 +139,7 @@ public final class ModdedWorldEngines {
throw failure; throw failure;
} }
LOGGER.info("Iris engine up for {}: pack={} dim={} seed={} height={}..{}", ModdedIrisLog.info("Iris engine up for {}: pack={} dim={} seed={} height={}..{}",
level.dimension().identifier(), packDir.getAbsolutePath(), dimension.getLoadKey(), seed, dimension.getMinHeight(), dimension.getMaxHeight()); level.dimension().identifier(), packDir.getAbsolutePath(), dimension.getLoadKey(), seed, dimension.getMinHeight(), dimension.getMaxHeight());
return engine; return engine;
} }
@@ -182,11 +179,11 @@ public final class ModdedWorldEngines {
return packDir; return packDir;
} }
LOGGER.error("==============================================================="); ModdedIrisLog.error("===============================================================");
LOGGER.error("Iris pack '{}' is not installed.", pack); ModdedIrisLog.error("Iris pack '{}' is not installed.", pack);
LOGGER.error("Expected a pack folder at: {}", packDir.getAbsolutePath()); ModdedIrisLog.error("Expected a pack folder at: {}", packDir.getAbsolutePath());
LOGGER.error("Install an Iris pack there (the folder must contain dimensions/{}.json) and restart the server.", dimensionKey); ModdedIrisLog.error("Install an Iris pack there (the folder must contain dimensions/{}.json) and restart the server.", dimensionKey);
LOGGER.error("==============================================================="); ModdedIrisLog.error("===============================================================");
throw new IllegalStateException("Iris pack not installed: " + packDir.getAbsolutePath()); throw new IllegalStateException("Iris pack not installed: " + packDir.getAbsolutePath());
} }
@@ -215,9 +212,9 @@ public final class ModdedWorldEngines {
+ level.dimension().identifier()); + level.dimension().identifier());
} }
} }
LOGGER.info("Iris engine closed for {}", level.dimension().identifier()); ModdedIrisLog.info("Iris engine closed for {}", level.dimension().identifier());
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris engine close failed for {}", level.dimension().identifier(), e); ModdedIrisLog.error("Iris engine close failed for {}", level.dimension().identifier(), e);
if (failure == null) { if (failure == null) {
failure = e; failure = e;
} else if (e != failure) { } else if (e != failure) {
@@ -25,11 +25,8 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.Items; import net.minecraft.world.item.Items;
import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.dimension.DimensionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class WorldCheckDimensionContract { final class WorldCheckDimensionContract {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private WorldCheckDimensionContract() { private WorldCheckDimensionContract() {
} }
@@ -44,13 +41,13 @@ final class WorldCheckDimensionContract {
+ ",levelMinY=" + level.getMinY() + ",levelHeight=" + level.getHeight(); + ",levelMinY=" + level.getMinY() + ",levelHeight=" + level.getHeight();
WorldCheckPredicates.qaEvent("dimension_type", dimension.getLoadKey(), pass, detail); WorldCheckPredicates.qaEvent("dimension_type", dimension.getLoadKey(), pass, detail);
if (!pass) { if (!pass) {
LOGGER.error("[worldcheck] dimension type mismatch for {}: {}", dimension.getLoadKey(), detail); ModdedIrisLog.error("[worldcheck] dimension type mismatch for {}: {}", dimension.getLoadKey(), detail);
} else { } else {
LOGGER.info("[worldcheck] dimension type contract: {}", detail); ModdedIrisLog.info("[worldcheck] dimension type contract: {}", detail);
} }
return pass; return pass;
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("[worldcheck] could not validate the Iris dimension type contract", error); ModdedIrisLog.error("[worldcheck] could not validate the Iris dimension type contract", error);
WorldCheckPredicates.qaEvent("dimension_type", generator.activeDimensionKey(), false, WorldCheckPredicates.qaEvent("dimension_type", generator.activeDimensionKey(), false,
"validationError=" + error.getClass().getSimpleName() + ":" + error.getMessage()); "validationError=" + error.getClass().getSimpleName() + ":" + error.getMessage());
return false; return false;
@@ -102,7 +99,7 @@ final class WorldCheckDimensionContract {
WorldCheckPredicates.qaEvent("entity_mixin", "persistence", pass, WorldCheckPredicates.qaEvent("entity_mixin", "persistence", pass,
"vanilla=" + vanillaSave + ",suppressed=" + suppressed + ",restored=" + restored); "vanilla=" + vanillaSave + ",suppressed=" + suppressed + ",restored=" + restored);
if (!pass) { if (!pass) {
LOGGER.error("[worldcheck] shared entity mixins are not active on this loader"); ModdedIrisLog.error("[worldcheck] shared entity mixins are not active on this loader");
} }
return pass; return pass;
} }
@@ -20,14 +20,11 @@ package art.arcane.iris.modded;
import art.arcane.iris.engine.framework.StructureVerticalBounds; import art.arcane.iris.engine.framework.StructureVerticalBounds;
import art.arcane.iris.modded.WorldCheckStructureAudit.StructureCheck; import art.arcane.iris.modded.WorldCheckStructureAudit.StructureCheck;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
final class WorldCheckPredicates { final class WorldCheckPredicates {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private WorldCheckPredicates() { private WorldCheckPredicates() {
} }
@@ -39,7 +36,7 @@ final class WorldCheckPredicates {
} }
static void qaEvent(String event, String structure, boolean pass, String detail) { static void qaEvent(String event, String structure, boolean pass, String detail) {
LOGGER.info(qaEventJson(event, structure, pass, detail)); ModdedIrisLog.info(qaEventJson(event, structure, pass, detail));
} }
static String qaEventJson(String event, String structure, boolean pass, String detail) { static String qaEventJson(String event, String structure, boolean pass, String detail) {
@@ -48,8 +48,6 @@ import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement; import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement; import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement; import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -77,7 +75,6 @@ final class WorldCheckStructureAudit {
private static final int MAX_FOOTPRINT_CHUNKS = 96; private static final int MAX_FOOTPRINT_CHUNKS = 96;
private static final int MAX_START_REFERENCE_CHUNKS = 16; private static final int MAX_START_REFERENCE_CHUNKS = 16;
private static final int MAX_STRUCTURE_CANDIDATES = 1024; private static final int MAX_STRUCTURE_CANDIDATES = 1024;
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private WorldCheckStructureAudit() { private WorldCheckStructureAudit() {
} }
@@ -123,13 +120,13 @@ final class WorldCheckStructureAudit {
} }
} }
boolean registryOk = registered.size() == check.registryKeys().size(); boolean registryOk = registered.size() == check.registryKeys().size();
LOGGER.info("[worldcheck] {} registry: {}/{} resolved {}", check.label(), registered.size(), ModdedIrisLog.info("[worldcheck] {} registry: {}/{} resolved {}", check.label(), registered.size(),
check.registryKeys().size(), registeredKeys); check.registryKeys().size(), registeredKeys);
WorldCheckPredicates.qaEvent("structure_registry", check.label(), registryOk, WorldCheckPredicates.qaEvent("structure_registry", check.label(), registryOk,
"resolved=" + registered.size() + ",expected=" + check.registryKeys().size() "resolved=" + registered.size() + ",expected=" + check.registryKeys().size()
+ ",keys=" + String.join("|", registeredKeys)); + ",keys=" + String.join("|", registeredKeys));
if (!registryOk) { if (!registryOk) {
LOGGER.error("[worldcheck] {} registry resolution failed; expected {}", check.label(), check.registryKeys()); ModdedIrisLog.error("[worldcheck] {} registry resolution failed; expected {}", check.label(), check.registryKeys());
WorldCheckPredicates.emitSkipped(check, "registry", "structure_reachability", "structure_locate", WorldCheckPredicates.emitSkipped(check, "registry", "structure_reachability", "structure_locate",
"structure_start_reference", "structure_footprint", "structure_material", "structure_start_reference", "structure_footprint", "structure_material",
"structure_block_entity"); "structure_block_entity");
@@ -149,12 +146,12 @@ final class WorldCheckStructureAudit {
} }
} }
boolean reachableOk = !reachable.isEmpty(); boolean reachableOk = !reachable.isEmpty();
LOGGER.info("[worldcheck] {} biome-reachable through Iris: {}", check.label(), reachableKeys); ModdedIrisLog.info("[worldcheck] {} biome-reachable through Iris: {}", check.label(), reachableKeys);
WorldCheckPredicates.qaEvent("structure_reachability", check.label(), reachableOk, WorldCheckPredicates.qaEvent("structure_reachability", check.label(), reachableOk,
"reachable=" + reachable.size() + ",registered=" + registered.size() "reachable=" + reachable.size() + ",registered=" + registered.size()
+ ",keys=" + String.join("|", reachableKeys)); + ",keys=" + String.join("|", reachableKeys));
if (!reachableOk) { if (!reachableOk) {
LOGGER.error("[worldcheck] {} cannot generate in any biome produced by this Iris pack", check.label()); ModdedIrisLog.error("[worldcheck] {} cannot generate in any biome produced by this Iris pack", check.label());
WorldCheckPredicates.emitSkipped(check, "reachability", "structure_locate", "structure_start_reference", WorldCheckPredicates.emitSkipped(check, "reachability", "structure_locate", "structure_start_reference",
"structure_footprint", "structure_material", "structure_block_entity"); "structure_footprint", "structure_material", "structure_block_entity");
return new StructureCheckResult(false, null); return new StructureCheckResult(false, null);
@@ -170,7 +167,7 @@ final class WorldCheckStructureAudit {
"method=placement_candidates,millis=" + locateMillis + ",radius=" + check.locateRadius() "method=placement_candidates,millis=" + locateMillis + ",radius=" + check.locateRadius()
+ ",result=" + (foundKey == null ? "none" : foundKey)); + ",result=" + (foundKey == null ? "none" : foundKey));
if (found == null) { if (found == null) {
LOGGER.error("[worldcheck] {} native placement candidates produced no valid start within {} rings after {}ms", ModdedIrisLog.error("[worldcheck] {} native placement candidates produced no valid start within {} rings after {}ms",
check.label(), check.locateRadius(), locateMillis); check.label(), check.locateRadius(), locateMillis);
WorldCheckPredicates.emitSkipped(check, "locate", "structure_start_reference", "structure_footprint", WorldCheckPredicates.emitSkipped(check, "locate", "structure_start_reference", "structure_footprint",
"structure_material", "structure_block_entity"); "structure_material", "structure_block_entity");
@@ -178,11 +175,11 @@ final class WorldCheckStructureAudit {
} }
BlockPos position = found.getFirst(); BlockPos position = found.getFirst();
LOGGER.info("[worldcheck] {} generated candidate: {} {} {} in {}ms (radius={}, result={})", ModdedIrisLog.info("[worldcheck] {} generated candidate: {} {} {} in {}ms (radius={}, result={})",
check.label(), position.getX(), position.getY(), position.getZ(), locateMillis, check.label(), position.getX(), position.getY(), position.getZ(), locateMillis,
check.locateRadius(), foundKey); check.locateRadius(), foundKey);
if (!locateOk) { if (!locateOk) {
LOGGER.error("[worldcheck] {} candidate scan returned unexpected structure {}", check.label(), foundKey); ModdedIrisLog.error("[worldcheck] {} candidate scan returned unexpected structure {}", check.label(), foundKey);
WorldCheckPredicates.emitSkipped(check, "locate", "structure_start_reference", "structure_footprint", WorldCheckPredicates.emitSkipped(check, "locate", "structure_start_reference", "structure_footprint",
"structure_material", "structure_block_entity"); "structure_material", "structure_block_entity");
return new StructureCheckResult(false, null); return new StructureCheckResult(false, null);
@@ -196,13 +193,13 @@ final class WorldCheckStructureAudit {
boolean validStart = start != null && start.isValid(); boolean validStart = start != null && start.isValid();
int references = targetChunk.getReferencesForStructure(structure).size(); int references = targetChunk.getReferencesForStructure(structure).size();
boolean startReferenceOk = WorldCheckPredicates.hasNativeStructureEvidence(validStart, references); boolean startReferenceOk = WorldCheckPredicates.hasNativeStructureEvidence(validStart, references);
LOGGER.info("[worldcheck] {} target chunk {},{}: valid start={}, references={}", ModdedIrisLog.info("[worldcheck] {} target chunk {},{}: valid start={}, references={}",
check.label(), chunkX, chunkZ, validStart, references); check.label(), chunkX, chunkZ, validStart, references);
WorldCheckPredicates.qaEvent("structure_start_reference", check.label(), startReferenceOk, WorldCheckPredicates.qaEvent("structure_start_reference", check.label(), startReferenceOk,
"chunk=" + chunkX + "," + chunkZ + ",validStart=" + validStart "chunk=" + chunkX + "," + chunkZ + ",validStart=" + validStart
+ ",references=" + references); + ",references=" + references);
if (!startReferenceOk || !validStart) { if (!startReferenceOk || !validStart) {
LOGGER.error("[worldcheck] {} located at chunk {},{} but no resolvable valid start was generated", ModdedIrisLog.error("[worldcheck] {} located at chunk {},{} but no resolvable valid start was generated",
check.label(), chunkX, chunkZ); check.label(), chunkX, chunkZ);
WorldCheckPredicates.emitSkipped(check, "start_reference", "structure_footprint", "structure_material", WorldCheckPredicates.emitSkipped(check, "start_reference", "structure_footprint", "structure_material",
"structure_block_entity"); "structure_block_entity");
@@ -221,7 +218,7 @@ final class WorldCheckStructureAudit {
"configured=" + decision.yShift() + ",applied=" "configured=" + decision.yShift() + ",applied="
+ (appliedShift == null ? "unrecorded" : appliedShift)); + (appliedShift == null ? "unrecorded" : appliedShift));
if (!verticalShiftOk) { if (!verticalShiftOk) {
LOGGER.error("[worldcheck] {} expected vertical shift {} but generation recorded {}", ModdedIrisLog.error("[worldcheck] {} expected vertical shift {} but generation recorded {}",
check.label(), decision.yShift(), appliedShift); check.label(), decision.yShift(), appliedShift);
} }
@@ -229,7 +226,7 @@ final class WorldCheckStructureAudit {
boolean footprintOk = footprint.inspectedChunks() > 0 boolean footprintOk = footprint.inspectedChunks() > 0
&& footprint.evidenceChunks() == footprint.inspectedChunks() && footprint.evidenceChunks() == footprint.inspectedChunks()
&& footprint.coveredPieces() == footprint.totalPieces(); && footprint.coveredPieces() == footprint.totalPieces();
LOGGER.info("[worldcheck] {} footprint: chunks={}/{} evidence={} pieces={}/{}", ModdedIrisLog.info("[worldcheck] {} footprint: chunks={}/{} evidence={} pieces={}/{}",
check.label(), footprint.inspectedChunks(), footprint.availableChunks(), check.label(), footprint.inspectedChunks(), footprint.availableChunks(),
footprint.evidenceChunks(), footprint.coveredPieces(), footprint.totalPieces()); footprint.evidenceChunks(), footprint.coveredPieces(), footprint.totalPieces());
WorldCheckPredicates.qaEvent("structure_footprint", check.label(), footprintOk, WorldCheckPredicates.qaEvent("structure_footprint", check.label(), footprintOk,
@@ -239,7 +236,7 @@ final class WorldCheckStructureAudit {
boolean materialOk = WorldCheckPredicates.hasCharacteristicMaterialEvidence(footprint.characteristicBlocks(), boolean materialOk = WorldCheckPredicates.hasCharacteristicMaterialEvidence(footprint.characteristicBlocks(),
footprint.characteristicChunks(), footprint.materialScannedChunks()); footprint.characteristicChunks(), footprint.materialScannedChunks());
LOGGER.info("[worldcheck] {} material: blocks={} chunks={}/{}", ModdedIrisLog.info("[worldcheck] {} material: blocks={} chunks={}/{}",
check.label(), footprint.characteristicBlocks(), footprint.characteristicChunks(), check.label(), footprint.characteristicBlocks(), footprint.characteristicChunks(),
footprint.materialScannedChunks()); footprint.materialScannedChunks());
WorldCheckPredicates.qaEvent("structure_material", check.label(), materialOk, WorldCheckPredicates.qaEvent("structure_material", check.label(), materialOk,
@@ -250,7 +247,7 @@ final class WorldCheckStructureAudit {
if (check.label().equals("mansion")) { if (check.label().equals("mansion")) {
boolean overlap = footprint.vegetationBlocks() > 0; boolean overlap = footprint.vegetationBlocks() > 0;
vegetationOk = WorldCheckPredicates.mansionVegetationPass(footprint.vegetationBlocks()); vegetationOk = WorldCheckPredicates.mansionVegetationPass(footprint.vegetationBlocks());
LOGGER.info("[worldcheck] mansion vegetation metric: remaining log/leaf blocks={} columns={} overlap={}", ModdedIrisLog.info("[worldcheck] mansion vegetation metric: remaining log/leaf blocks={} columns={} overlap={}",
footprint.vegetationBlocks(), footprint.vegetationColumns(), overlap); footprint.vegetationBlocks(), footprint.vegetationColumns(), overlap);
WorldCheckPredicates.qaEvent("mansion_vegetation_metric", check.label(), vegetationOk, WorldCheckPredicates.qaEvent("mansion_vegetation_metric", check.label(), vegetationOk,
"remainingLogsOrLeaves=" + footprint.vegetationBlocks() + ",columns=" "remainingLogsOrLeaves=" + footprint.vegetationBlocks() + ",columns="
@@ -261,7 +258,7 @@ final class WorldCheckStructureAudit {
PendingVillagePoi pendingPoi = null; PendingVillagePoi pendingPoi = null;
if (check.label().equals("village")) { if (check.label().equals("village")) {
foundationOk = WorldCheckPredicates.villageFoundationPass(footprint.foundationGapColumns()); foundationOk = WorldCheckPredicates.villageFoundationPass(footprint.foundationGapColumns());
LOGGER.info("[worldcheck] village foundation metric: bases={} cobblestone={} columns={} unsupported={}", ModdedIrisLog.info("[worldcheck] village foundation metric: bases={} cobblestone={} columns={} unsupported={}",
footprint.foundationBaseColumns(), footprint.foundationBlocks(), footprint.foundationBaseColumns(), footprint.foundationBlocks(),
footprint.foundationColumns(), footprint.foundationGapColumns()); footprint.foundationColumns(), footprint.foundationGapColumns());
WorldCheckPredicates.qaEvent("village_foundation_metric", check.label(), foundationOk, WorldCheckPredicates.qaEvent("village_foundation_metric", check.label(), foundationOk,
@@ -272,26 +269,26 @@ final class WorldCheckStructureAudit {
} }
boolean blockEntityOk = footprint.blockEntityStates() == footprint.blockEntitiesPresent(); boolean blockEntityOk = footprint.blockEntityStates() == footprint.blockEntitiesPresent();
LOGGER.info("[worldcheck] {} block entities: state blocks={}, present={}, missing={}", ModdedIrisLog.info("[worldcheck] {} block entities: state blocks={}, present={}, missing={}",
check.label(), footprint.blockEntityStates(), footprint.blockEntitiesPresent(), check.label(), footprint.blockEntityStates(), footprint.blockEntitiesPresent(),
footprint.blockEntityStates() - footprint.blockEntitiesPresent()); footprint.blockEntityStates() - footprint.blockEntitiesPresent());
WorldCheckPredicates.qaEvent("structure_block_entity", check.label(), blockEntityOk, WorldCheckPredicates.qaEvent("structure_block_entity", check.label(), blockEntityOk,
"states=" + footprint.blockEntityStates() + ",present=" + footprint.blockEntitiesPresent() "states=" + footprint.blockEntityStates() + ",present=" + footprint.blockEntitiesPresent()
+ ",missing=" + (footprint.blockEntityStates() - footprint.blockEntitiesPresent())); + ",missing=" + (footprint.blockEntityStates() - footprint.blockEntitiesPresent()));
if (!footprintOk) { if (!footprintOk) {
LOGGER.error("[worldcheck] {} structure footprint is incomplete", check.label()); ModdedIrisLog.error("[worldcheck] {} structure footprint is incomplete", check.label());
} }
if (!materialOk) { if (!materialOk) {
LOGGER.error("[worldcheck] {} has no distributed characteristic structure material", check.label()); ModdedIrisLog.error("[worldcheck] {} has no distributed characteristic structure material", check.label());
} }
if (!blockEntityOk) { if (!blockEntityOk) {
LOGGER.error("[worldcheck] {} generated block-entity states without matching block entities", check.label()); ModdedIrisLog.error("[worldcheck] {} generated block-entity states without matching block entities", check.label());
} }
if (!vegetationOk) { if (!vegetationOk) {
LOGGER.error("[worldcheck] mansion vegetation still intersects the generated structure footprint"); ModdedIrisLog.error("[worldcheck] mansion vegetation still intersects the generated structure footprint");
} }
if (!foundationOk) { if (!foundationOk) {
LOGGER.error("[worldcheck] village has unsupported foundation columns after stilt placement"); ModdedIrisLog.error("[worldcheck] village has unsupported foundation columns after stilt placement");
} }
boolean pass = verticalShiftOk && footprintOk && materialOk && blockEntityOk boolean pass = verticalShiftOk && footprintOk && materialOk && blockEntityOk
&& vegetationOk && foundationOk; && vegetationOk && foundationOk;
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.api; package art.arcane.iris.modded.api;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.modded.ModdedBlockResolution; import art.arcane.iris.modded.ModdedBlockResolution;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@@ -25,8 +26,6 @@ import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -57,7 +56,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
* next provider. * next provider.
*/ */
public final class ModdedCustomContentRegistry { public final class ModdedCustomContentRegistry {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final List<ModdedDataProvider> PROVIDERS = new CopyOnWriteArrayList<>(); private static final List<ModdedDataProvider> PROVIDERS = new CopyOnWriteArrayList<>();
private static final Map<String, BlockState> CUSTOM_BLOCKS = new ConcurrentHashMap<>(); private static final Map<String, BlockState> CUSTOM_BLOCKS = new ConcurrentHashMap<>();
private static volatile boolean scanned = false; private static volatile boolean scanned = false;
@@ -77,14 +75,14 @@ public final class ModdedCustomContentRegistry {
} }
Identifier identifier = Identifier.tryParse(namespace + ":" + key); Identifier identifier = Identifier.tryParse(namespace + ":" + key);
if (identifier == null) { if (identifier == null) {
LOGGER.warn("Iris custom block data registration rejected invalid id {}:{}", namespace, key); ModdedIrisLog.warn("Iris custom block data registration rejected invalid id {}:{}", namespace, key);
return; return;
} }
BlockState parsed; BlockState parsed;
try { try {
parsed = ModdedBlockResolution.strictParse(state).handle(); parsed = ModdedBlockResolution.strictParse(state).handle();
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris custom block data '{}:{}' has unparseable state '{}'", namespace, key, state, error); ModdedIrisLog.error("Iris custom block data '{}:{}' has unparseable state '{}'", namespace, key, state, error);
return; return;
} }
DiscoveryBatch activeBatch = discoveryBatch; DiscoveryBatch activeBatch = discoveryBatch;
@@ -93,7 +91,7 @@ public final class ModdedCustomContentRegistry {
} else { } else {
activeBatch.customBlocks.put(identifier.toString(), parsed); activeBatch.customBlocks.put(identifier.toString(), parsed);
} }
LOGGER.info("Iris registered custom block data {}:{} -> {}", namespace, key, state); ModdedIrisLog.info("Iris registered custom block data {}:{} -> {}", namespace, key, state);
} }
/** /**
@@ -112,7 +110,7 @@ public final class ModdedCustomContentRegistry {
} }
for (ModdedDataProvider existing : PROVIDERS) { for (ModdedDataProvider existing : PROVIDERS) {
if (existing.modId().equals(provider.modId())) { if (existing.modId().equals(provider.modId())) {
LOGGER.warn("Iris custom content provider for '{}' already registered; ignoring duplicate", provider.modId()); ModdedIrisLog.warn("Iris custom content provider for '{}' already registered; ignoring duplicate", provider.modId());
return; return;
} }
} }
@@ -120,9 +118,9 @@ public final class ModdedCustomContentRegistry {
try { try {
provider.init(); provider.init();
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris custom content provider '{}' failed to initialize", provider.modId(), error); ModdedIrisLog.error("Iris custom content provider '{}' failed to initialize", provider.modId(), error);
} }
LOGGER.info("Iris registered custom content provider '{}'", provider.modId()); ModdedIrisLog.info("Iris registered custom content provider '{}'", provider.modId());
} }
/** /**
@@ -159,7 +157,7 @@ public final class ModdedCustomContentRegistry {
CUSTOM_BLOCKS.putAll(batch.customBlocks); CUSTOM_BLOCKS.putAll(batch.customBlocks);
scanned = true; scanned = true;
for (ModdedDataProvider provider : batch.additions) { for (ModdedDataProvider provider : batch.additions) {
LOGGER.info("Iris registered custom content provider '{}'", provider.modId()); ModdedIrisLog.info("Iris registered custom content provider '{}'", provider.modId());
} }
return new Discovery(previousProviders, previousCustomBlocks, return new Discovery(previousProviders, previousCustomBlocks,
previousDiscoveryComplete, true); previousDiscoveryComplete, true);
@@ -171,7 +169,7 @@ public final class ModdedCustomContentRegistry {
failure.addSuppressed(rollbackFailure); failure.addSuppressed(rollbackFailure);
} }
} }
LOGGER.warn("Iris custom content provider discovery failed at {}", ModdedIrisLog.warn("Iris custom content provider discovery failed at {}",
providerIdentity(failingProvider), failure); providerIdentity(failingProvider), failure);
if (failure instanceof RuntimeException runtimeException) { if (failure instanceof RuntimeException runtimeException) {
throw runtimeException; throw runtimeException;
@@ -242,7 +240,7 @@ public final class ModdedCustomContentRegistry {
try { try {
types = provider.getTypes(type); types = provider.getTypes(type);
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris custom content provider '{}' failed listing {} types", provider.modId(), type, error); ModdedIrisLog.error("Iris custom content provider '{}' failed listing {} types", provider.modId(), type, error);
continue; continue;
} }
if (types == null) { if (types == null) {
@@ -288,7 +286,7 @@ public final class ModdedCustomContentRegistry {
return resolved; return resolved;
} }
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris custom content provider '{}' failed resolving block {}", provider.modId(), key, error); ModdedIrisLog.error("Iris custom content provider '{}' failed resolving block {}", provider.modId(), key, error);
} }
} }
return null; return null;
@@ -302,7 +300,7 @@ public final class ModdedCustomContentRegistry {
public static void processBlockPlacement(Engine engine, ServerLevel level, BlockPos position, String key) { public static void processBlockPlacement(Engine engine, ServerLevel level, BlockPos position, String key) {
Identifier base = parseIdentifier(key); Identifier base = parseIdentifier(key);
if (base == null) { if (base == null) {
LOGGER.warn("Iris deferred custom block placement rejected invalid id {}", key); ModdedIrisLog.warn("Iris deferred custom block placement rejected invalid id {}", key);
return; return;
} }
Map<String, String> state = parseState(key); Map<String, String> state = parseState(key);
@@ -314,11 +312,11 @@ public final class ModdedCustomContentRegistry {
provider.processBlockPlacement(new ModdedBlockPlacementContext( provider.processBlockPlacement(new ModdedBlockPlacementContext(
engine, level, position.immutable(), base, state, level.getBlockState(position))); engine, level, position.immutable(), base, state, level.getBlockState(position)));
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris custom content provider '{}' failed post-placement for {} at {}", provider.modId(), key, position, error); ModdedIrisLog.error("Iris custom content provider '{}' failed post-placement for {} at {}", provider.modId(), key, position, error);
} }
return; return;
} }
LOGGER.warn("Iris deferred custom block placement has no provider for {}", key); ModdedIrisLog.warn("Iris deferred custom block placement has no provider for {}", key);
} }
/** /**
@@ -343,7 +341,7 @@ public final class ModdedCustomContentRegistry {
return entity; return entity;
} }
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris custom content provider '{}' failed spawning mob {}", provider.modId(), key, error); ModdedIrisLog.error("Iris custom content provider '{}' failed spawning mob {}", provider.modId(), key, error);
} }
} }
return null; return null;
@@ -439,7 +437,7 @@ public final class ModdedCustomContentRegistry {
"Iris custom content provider returned a null mod id"); "Iris custom content provider returned a null mod id");
for (ModdedDataProvider existing : providers) { for (ModdedDataProvider existing : providers) {
if (modId.equals(existing.modId())) { if (modId.equals(existing.modId())) {
LOGGER.warn("Iris custom content provider for '{}' already registered; ignoring duplicate", modId); ModdedIrisLog.warn("Iris custom content provider for '{}' already registered; ignoring duplicate", modId);
return; return;
} }
} }
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.localization.IrisLanguage; import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.IrisMessages; import art.arcane.iris.core.localization.IrisMessages;
@@ -51,8 +52,6 @@ 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.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.ChunkGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -67,7 +66,6 @@ import java.util.concurrent.TimeUnit;
import art.arcane.volmlib.util.localization.MessageArgument; import art.arcane.volmlib.util.localization.MessageArgument;
public final class IrisModdedCommands { public final class IrisModdedCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final long DOWNLOAD_SHUTDOWN_POLL_SECONDS = 15L; private static final long DOWNLOAD_SHUTDOWN_POLL_SECONDS = 15L;
private static final Object DOWNLOAD_MONITOR = new Object(); private static final Object DOWNLOAD_MONITOR = new Object();
@@ -82,7 +80,7 @@ public final class IrisModdedCommands {
LiteralCommandNode<CommandSourceStack> root = dispatcher.register(ModdedCommandTree.rootTree()); LiteralCommandNode<CommandSourceStack> root = dispatcher.register(ModdedCommandTree.rootTree());
dispatcher.register(Commands.literal("ir").redirect(root)); dispatcher.register(Commands.literal("ir").redirect(root));
dispatcher.register(Commands.literal("irs").redirect(root)); dispatcher.register(Commands.literal("irs").redirect(root));
IrisLogging.info("Iris /iris command tree registered"); IrisLogging.debug("Iris /iris command tree registered");
} }
public static void openDownloadAdmission() { public static void openDownloadAdmission() {
@@ -108,7 +106,7 @@ public final class IrisModdedCommands {
try { try {
if (!execution.await(DOWNLOAD_SHUTDOWN_POLL_SECONDS, TimeUnit.SECONDS) && !warned) { if (!execution.await(DOWNLOAD_SHUTDOWN_POLL_SECONDS, TimeUnit.SECONDS) && !warned) {
warned = true; warned = true;
LOGGER.warn(execution.isPublishing() ModdedIrisLog.warn(execution.isPublishing()
? "Waiting for atomic pack publication to finish before Iris shutdown." ? "Waiting for atomic pack publication to finish before Iris shutdown."
: "Waiting for the active pack download to cancel before Iris shutdown."); : "Waiting for the active pack download to cancel before Iris shutdown.");
} }
@@ -354,7 +352,7 @@ public final class IrisModdedCommands {
accepted = scheduler.asyncIfRunning(execution, execution::cancel); accepted = scheduler.asyncIfRunning(execution, execution::cancel);
} catch (Throwable error) { } catch (Throwable error) {
execution.cancel(); execution.cancel();
LOGGER.error("Iris pack download dispatch failed for {}", target, error); ModdedIrisLog.error("Iris pack download dispatch failed for {}", target, error);
fail(source, IrisLanguage.plain( fail(source, IrisLanguage.plain(
ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE, ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE,
MessageArgument.untrusted("pack", target), MessageArgument.untrusted("pack", target),
@@ -363,7 +361,7 @@ public final class IrisModdedCommands {
} }
if (!accepted) { if (!accepted) {
execution.cancel(); execution.cancel();
LOGGER.error("Iris pack download dispatch rejected for {} because the scheduler is shut down", target); ModdedIrisLog.error("Iris pack download dispatch rejected for {} because the scheduler is shut down", target);
fail(source, IrisLanguage.plain( fail(source, IrisLanguage.plain(
ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE, ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE,
MessageArgument.untrusted("pack", target), MessageArgument.untrusted("pack", target),
@@ -416,7 +414,7 @@ public final class IrisModdedCommands {
dispatchDownloadFeedback(source, () -> fail(source, error.getMessage())); dispatchDownloadFeedback(source, () -> fail(source, error.getMessage()));
return; return;
} catch (IOException | RuntimeException error) { } catch (IOException | RuntimeException error) {
LOGGER.error("Iris pack download failed for {}", target, error); ModdedIrisLog.error("Iris pack download failed for {}", target, error);
} }
dispatchDownloadFeedback(source, () -> fail(source, IrisLanguage.plain( dispatchDownloadFeedback(source, () -> fail(source, IrisLanguage.plain(
ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE, ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE,
@@ -512,7 +510,7 @@ public final class IrisModdedCommands {
try { try {
return irisGenerator.commandEngine(); return irisGenerator.commandEngine();
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris engine lookup failed for {}", level.dimension().identifier(), e); ModdedIrisLog.error("Iris engine lookup failed for {}", level.dimension().identifier(), e);
return null; return null;
} }
} }
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.spi.IrisPlatforms; import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.core.pack.PackDirectoryResolver; import art.arcane.iris.core.pack.PackDirectoryResolver;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
@@ -40,8 +41,6 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier; import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.Structure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@@ -66,7 +65,6 @@ final class ModdedCommandSuggestions {
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);
static final SuggestionProvider<CommandSourceStack> DIMENSION_NAMES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestDimensionNames(context, builder); static final SuggestionProvider<CommandSourceStack> DIMENSION_NAMES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestDimensionNames(context, builder);
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int TAB_FAILURE_KEYS_MAX = 256; private static final int TAB_FAILURE_KEYS_MAX = 256;
private static final Set<String> REPORTED_TAB_FAILURES = ConcurrentHashMap.newKeySet(); private static final Set<String> REPORTED_TAB_FAILURES = ConcurrentHashMap.newKeySet();
private static final long PACK_NAME_CACHE_TTL_MS = 3_000L; private static final long PACK_NAME_CACHE_TTL_MS = 3_000L;
@@ -180,7 +178,7 @@ final class ModdedCommandSuggestions {
if (REPORTED_TAB_FAILURES.size() > TAB_FAILURE_KEYS_MAX) { if (REPORTED_TAB_FAILURES.size() > TAB_FAILURE_KEYS_MAX) {
REPORTED_TAB_FAILURES.clear(); REPORTED_TAB_FAILURES.clear();
} }
LOGGER.warn("Iris tab-complete for {} in {} failed; suggestions will be empty", suggestion, origin, error); ModdedIrisLog.warn("Iris tab-complete for {} in {} failed; suggestions will be empty", suggestion, origin, error);
} }
private static String tabOrigin(CommandSourceStack source) { private static String tabOrigin(CommandSourceStack source) {
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
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.DataVersion;
import art.arcane.iris.core.pack.PackDirectoryResolver; import art.arcane.iris.core.pack.PackDirectoryResolver;
@@ -35,8 +36,6 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.storage.LevelResource; import net.minecraft.world.level.storage.LevelResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -56,7 +55,6 @@ import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.iris.core.localization.RuntimeUiMessages; import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.volmlib.util.localization.MessageArgument; import art.arcane.volmlib.util.localization.MessageArgument;
public final class ModdedDatapackCommands { public final class ModdedDatapackCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private static final String WORLD_PACK_NAME = "iris"; private static final String WORLD_PACK_NAME = "iris";
@@ -170,7 +168,7 @@ public final class ModdedDatapackCommands {
try { try {
json = dimension.getDimensionType().toJson(DataVersion.getLatest().get()); json = dimension.getDimensionType().toJson(DataVersion.getLatest().get());
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris dimension type generation failed for {}", dimension.getLoadKey(), e); ModdedIrisLog.error("Iris dimension type generation failed for {}", dimension.getLoadKey(), e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_DIMENSION_TYPE_GENERATION_FAILED, MessageArgument.untrusted("value", level.dimension().identifier()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage())))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_DIMENSION_TYPE_GENERATION_FAILED, MessageArgument.untrusted("value", level.dimension().identifier()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage()))));
continue; continue;
} }
@@ -180,7 +178,7 @@ public final class ModdedDatapackCommands {
Files.writeString(output.toPath(), json, StandardCharsets.UTF_8); Files.writeString(output.toPath(), json, StandardCharsets.UTF_8);
written.add(output.getPath()); written.add(output.getPath());
} catch (IOException e) { } catch (IOException e) {
LOGGER.error("Iris dimension type write failed for {}", output, e); ModdedIrisLog.error("Iris dimension type write failed for {}", output, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_FAILED_WRITE, MessageArgument.untrusted("output", output), MessageArgument.untrusted("value", String.valueOf(e.getMessage())))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_FAILED_WRITE, MessageArgument.untrusted("output", output), MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
} }
} }
@@ -204,7 +202,7 @@ public final class ModdedDatapackCommands {
Files.writeString(mcmeta.toPath(), meta, StandardCharsets.UTF_8); Files.writeString(mcmeta.toPath(), meta, StandardCharsets.UTF_8);
written.add(mcmeta.getPath()); written.add(mcmeta.getPath());
} catch (IOException e) { } catch (IOException e) {
LOGGER.error("Iris pack.mcmeta write failed for {}", mcmeta, e); ModdedIrisLog.error("Iris pack.mcmeta write failed for {}", mcmeta, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_FAILED_WRITE_2, MessageArgument.untrusted("mcmeta", mcmeta), MessageArgument.untrusted("value", String.valueOf(e.getMessage())))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_FAILED_WRITE_2, MessageArgument.untrusted("mcmeta", mcmeta), MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
return 0; return 0;
} }
@@ -237,7 +235,7 @@ public final class ModdedDatapackCommands {
} }
} }
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris datapack import scan failed for pack {}", pack.getName(), e); ModdedIrisLog.error("Iris datapack import scan failed for pack {}", pack.getName(), e);
} }
} }
@@ -18,13 +18,12 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.spi.IrisPlatforms; import art.arcane.iris.spi.IrisPlatforms;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands; import net.minecraft.commands.Commands;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
@@ -37,7 +36,6 @@ import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.ModdedCommandMessages; import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.volmlib.util.localization.MessageArgument; import art.arcane.volmlib.util.localization.MessageArgument;
final class ModdedDeveloperCommands { final class ModdedDeveloperCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private ModdedDeveloperCommands() { private ModdedDeveloperCommands() {
@@ -71,7 +69,7 @@ final class ModdedDeveloperCommands {
} }
return 1; return 1;
} catch (SocketException error) { } catch (SocketException error) {
LOGGER.error("Iris developer network dump failed", error); ModdedIrisLog.error("Iris developer network dump failed", error);
ModdedCommandFeedback.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DEVELOPER_COMMANDS_NETWORK_SCAN_FAILED, MessageArgument.untrusted("value", error.getClass().getSimpleName()))); ModdedCommandFeedback.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DEVELOPER_COMMANDS_NETWORK_SCAN_FAILED, MessageArgument.untrusted("value", error.getClass().getSimpleName())));
return 0; return 0;
} }
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
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;
@@ -46,8 +47,6 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource; import net.minecraft.sounds.SoundSource;
import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
@@ -62,7 +61,6 @@ 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 int MAX_HITS = 2_048; private static final int MAX_HITS = 2_048;
private static final int PARTICLE_BATCH_SIZE = 64; 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);
@@ -231,7 +229,7 @@ public final class ModdedDustRevealer {
} }
private static void revealFailure(ModdedScheduler scheduler, RevealRun run, Throwable error) { private static void revealFailure(ModdedScheduler scheduler, RevealRun run, Throwable error) {
LOGGER.error("Iris dust reveal failed for {} at {}", run.key(), coordinates(run.origin()), error); ModdedIrisLog.error("Iris dust reveal failed for {} at {}", run.key(), coordinates(run.origin()), error);
scheduler.global(() -> { scheduler.global(() -> {
if (ACTIVE_RUNS.remove(run.playerId(), run)) { if (ACTIVE_RUNS.remove(run.playerId(), run)) {
run.player().sendSystemMessage(Component.literal( run.player().sendSystemMessage(Component.literal(
@@ -415,7 +413,7 @@ public final class ModdedDustRevealer {
} }
} }
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris dust column-object lookup failed at {}, {}, {}", ModdedIrisLog.error("Iris dust column-object lookup failed at {}, {}, {}",
x, relativeY + minHeight, z, error); x, relativeY + minHeight, z, error);
} }
return null; return null;
@@ -458,7 +456,7 @@ public final class ModdedDustRevealer {
try { try {
return supplier.get(); return supplier.get();
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris dust {} failed", operation, error); ModdedIrisLog.error("Iris dust {} failed", operation, error);
return null; return null;
} }
} }
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.gui.GuiHost; import art.arcane.iris.core.gui.GuiHost;
import art.arcane.iris.core.loader.IrisRegistrant; import art.arcane.iris.core.loader.IrisRegistrant;
import art.arcane.iris.core.localization.IrisLanguage; import art.arcane.iris.core.localization.IrisLanguage;
@@ -29,14 +30,11 @@ import art.arcane.volmlib.util.localization.MessageArgument;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Desktop; import java.awt.Desktop;
import java.io.File; import java.io.File;
final class ModdedEditCommands { final class ModdedEditCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private ModdedEditCommands() { private ModdedEditCommands() {
} }
@@ -123,7 +121,7 @@ final class ModdedEditCommands {
try { try {
Desktop.getDesktop().open(file); Desktop.getDesktop().open(file);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris edit failed to open {}", file, e); ModdedIrisLog.error("Iris edit failed to open {}", file, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_COULD_NOT_OPEN, MessageArgument.untrusted("value", file.getName()), MessageArgument.untrusted("value2", e.getClass().getSimpleName()))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_COULD_NOT_OPEN, MessageArgument.untrusted("value", file.getName()), MessageArgument.untrusted("value2", e.getClass().getSimpleName())));
return 0; return 0;
} }
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.runtime.GoldenHashEngine; import art.arcane.iris.core.runtime.GoldenHashEngine;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.modded.ModdedBlockBuffer; import art.arcane.iris.modded.ModdedBlockBuffer;
@@ -29,8 +30,6 @@ import art.arcane.iris.util.project.hunk.Hunk;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -47,7 +46,6 @@ public final class ModdedGoldenHash {
VERIFY VERIFY
} }
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final AtomicBoolean ACTIVE = new AtomicBoolean(false); private static final AtomicBoolean ACTIVE = new AtomicBoolean(false);
private final CommandSourceStack source; private final CommandSourceStack source;
@@ -96,7 +94,7 @@ public final class ModdedGoldenHash {
MessageArgument.trusted("threads", Math.max(1, threads)), MessageArgument.trusted("threads", Math.max(1, threads)),
MessageArgument.untrusted("mode", mode) MessageArgument.untrusted("mode", mode)
)); ));
LOGGER.info("goldenhash start: dim={} seed={} radius={} threads={} mode={} file={}", ModdedIrisLog.info("goldenhash start: dim={} seed={} radius={} threads={} mode={} file={}",
engine.getDimension().getLoadKey(), engine.getSeedManager().getSeed(), boundedRadius, Math.max(1, threads), mode, scan.hashEngine.getGoldenFile().getName()); engine.getDimension().getLoadKey(), engine.getSeedManager().getSeed(), boundedRadius, Math.max(1, threads), mode, scan.hashEngine.getGoldenFile().getName());
Thread thread = new Thread(() -> { Thread thread = new Thread(() -> {
try { try {
@@ -182,7 +180,7 @@ public final class ModdedGoldenHash {
@Override @Override
public void chunkFailed(int chunkX, int chunkZ, Throwable error) { public void chunkFailed(int chunkX, int chunkZ, Throwable error) {
LOGGER.error("goldenhash chunk {},{} failed", chunkX, chunkZ, error); ModdedIrisLog.error("goldenhash chunk {},{} failed", chunkX, chunkZ, error);
ModdedGoldenHash.this.fail(IrisLanguage.plain( ModdedGoldenHash.this.fail(IrisLanguage.plain(
RuntimeProgressMessages.GOLDEN_CHUNK_FAILED, RuntimeProgressMessages.GOLDEN_CHUNK_FAILED,
MessageArgument.trusted("x", chunkX), MessageArgument.trusted("x", chunkX),
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.localization.IrisLanguage; import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.ModdedCommandMessages; import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
@@ -51,8 +52,6 @@ import net.minecraft.world.entity.Relative;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@@ -64,7 +63,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
final class ModdedLocateCommands { final class ModdedLocateCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final long LOCATE_TIMEOUT_MS = 120000L; private static final long LOCATE_TIMEOUT_MS = 120000L;
private static final int NATIVE_STRUCTURE_LOCATE_RADIUS = 100; private static final int NATIVE_STRUCTURE_LOCATE_RADIUS = 100;
private static final ConcurrentHashMap<UUID, CompletableFuture<Position2>> ACTIVE_LOCATE_REQUESTS = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<UUID, CompletableFuture<Position2>> ACTIVE_LOCATE_REQUESTS = new ConcurrentHashMap<>();
@@ -260,7 +258,7 @@ final class ModdedLocateCommands {
server.execute(() -> teleportToStructure(source, level, player, targetX, targetY, targetZ, server.execute(() -> teleportToStructure(source, level, player, targetX, targetY, targetZ,
"Iris-placed structure " + key)); "Iris-placed structure " + key));
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris structure locate failed for {}", key, e); ModdedIrisLog.error("Iris structure locate failed for {}", key, e);
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCH_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName())))); server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCH_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()))));
} }
}, "Iris Structure Locator"); }, "Iris Structure Locator");
@@ -302,7 +300,7 @@ final class ModdedLocateCommands {
teleportToStructure(source, level, player, targetX, targetY, targetZ, teleportToStructure(source, level, player, targetX, targetY, targetZ,
"native structure " + target.key()); "native structure " + target.key());
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Native structure locate failed for {}", target.key(), e); ModdedIrisLog.error("Native structure locate failed for {}", target.key(), e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCH_NATIVE_STRUCTURE_FAILED, MessageArgument.untrusted("value", target.key()), MessageArgument.untrusted("value2", e.getClass().getSimpleName()))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCH_NATIVE_STRUCTURE_FAILED, MessageArgument.untrusted("value", target.key()), MessageArgument.untrusted("value2", e.getClass().getSimpleName())));
} }
} }
@@ -518,7 +516,7 @@ final class ModdedLocateCommands {
return; return;
} }
if (failure != null) { if (failure != null) {
LOGGER.error("Iris locate failed for {}", label, failure); ModdedIrisLog.error("Iris locate failed for {}", label, failure);
server.execute(() -> { server.execute(() -> {
if (ACTIVE_LOCATE_REQUESTS.remove(playerId, search)) { if (ACTIVE_LOCATE_REQUESTS.remove(playerId, search)) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCH_FAILED_2, MessageArgument.untrusted("failure", failure))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCH_FAILED_2, MessageArgument.untrusted("failure", failure)));
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.tools.TreePlausibilizeBatch; import art.arcane.iris.core.tools.TreePlausibilizeBatch;
import art.arcane.iris.core.tools.TreePlausibilizer; import art.arcane.iris.core.tools.TreePlausibilizer;
@@ -54,8 +55,6 @@ import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.HitResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -74,7 +73,6 @@ import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.iris.core.localization.RuntimeUiMessages; import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.volmlib.util.localization.MessageArgument; import art.arcane.volmlib.util.localization.MessageArgument;
public final class ModdedObjectCommands { public final class ModdedObjectCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private static final long MAX_SAVE_VOLUME = 500000L; private static final long MAX_SAVE_VOLUME = 500000L;
private static final long MAX_AUTOSELECT_VOLUME = 100000L; private static final long MAX_AUTOSELECT_VOLUME = 100000L;
@@ -313,7 +311,7 @@ public final class ModdedObjectCommands {
try { try {
object.write(file); object.write(file);
} catch (IOException e) { } catch (IOException e) {
LOGGER.error("Iris object save failed for {}", file.getAbsolutePath(), e); ModdedIrisLog.error("Iris object save failed for {}", file.getAbsolutePath(), e);
if (finalClaimed) { if (finalClaimed) {
// Never leave a 0-byte claim file permanently blocking non-overwrite saves. // Never leave a 0-byte claim file permanently blocking non-overwrite saves.
file.delete(); file.delete();
@@ -332,7 +330,7 @@ public final class ModdedObjectCommands {
tileNote.append(" (").append(tilesSkipped[0]).append(" tile state(s) could not be captured)"); tileNote.append(" (").append(tilesSkipped[0]).append(" tile state(s) could not be captured)");
} }
server.execute(() -> IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_SAVED_OBJECTS_IOB_X_X_BLOCK_S, MessageArgument.untrusted("value", engine.getData().getDataFolder().getName()), MessageArgument.untrusted("name", name), MessageArgument.untrusted("w", w), MessageArgument.untrusted("h", h), MessageArgument.untrusted("d", d), MessageArgument.untrusted("value2", object.getBlocks().size()), MessageArgument.untrusted("tileNote", tileNote)))); server.execute(() -> IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_SAVED_OBJECTS_IOB_X_X_BLOCK_S, MessageArgument.untrusted("value", engine.getData().getDataFolder().getName()), MessageArgument.untrusted("name", name), MessageArgument.untrusted("w", w), MessageArgument.untrusted("h", h), MessageArgument.untrusted("d", d), MessageArgument.untrusted("value2", object.getBlocks().size()), MessageArgument.untrusted("tileNote", tileNote))));
LOGGER.info("Iris object save: {} {}x{}x{} blocks={} tilesSaved={} tilesSkipped={} -> {}", name, w, h, d, object.getBlocks().size(), tilesSaved[0], tilesSkipped[0], file.getAbsolutePath()); ModdedIrisLog.info("Iris object save: {} {}x{}x{} blocks={} tilesSaved={} tilesSkipped={} -> {}", name, w, h, d, object.getBlocks().size(), tilesSaved[0], tilesSkipped[0], file.getAbsolutePath());
}); });
return 1; return 1;
} }
@@ -378,7 +376,7 @@ public final class ModdedObjectCommands {
String blockKey = BuiltInRegistries.BLOCK.getKey(state.getBlock()).toString(); String blockKey = BuiltInRegistries.BLOCK.getKey(state.getBlock()).toString();
return ModdedTileData.capture(blockKey, snbt); return ModdedTileData.capture(blockKey, snbt);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris tile capture failed at {} {} {}", pos.getX(), pos.getY(), pos.getZ(), e); ModdedIrisLog.error("Iris tile capture failed at {} {} {}", pos.getX(), pos.getY(), pos.getZ(), e);
return null; return null;
} }
} }
@@ -391,7 +389,7 @@ public final class ModdedObjectCommands {
try { try {
object = IrisData.loadAnyObject(key, engine == null ? null : engine.getData()); object = IrisData.loadAnyObject(key, engine == null ? null : engine.getData());
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris object load failed for {}", key, e); ModdedIrisLog.error("Iris object load failed for {}", key, e);
} }
if (object == null || object.getBlocks().size() == 0) { if (object == null || object.getBlocks().size() == 0) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_UNKNOWN_EMPTY_OBJECT, MessageArgument.untrusted("key", key))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_UNKNOWN_EMPTY_OBJECT, MessageArgument.untrusted("key", key)));
@@ -419,7 +417,7 @@ public final class ModdedObjectCommands {
try { try {
object.place(target.getX(), target.getY() + object.getCenter().getY(), target.getZ(), placer, placement, new RNG(), null); object.place(target.getX(), target.getY() + object.getCenter().getY(), target.getZ(), placer, placement, new RNG(), null);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris paste failed for {}", key, e); ModdedIrisLog.error("Iris paste failed for {}", key, e);
ModdedObjectUndo.record(player == null ? ModdedObjectUndo.CONSOLE : player.getUUID(), level, placer.undoSnapshot()); ModdedObjectUndo.record(player == null ? ModdedObjectUndo.CONSOLE : player.getUUID(), level, placer.undoSnapshot());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_PASTE_FAILED_PARTIAL_CHANGES_RECORDED_UNDO, MessageArgument.untrusted("value", e.getClass().getSimpleName()))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_PASTE_FAILED_PARTIAL_CHANGES_RECORDED_UNDO, MessageArgument.untrusted("value", e.getClass().getSimpleName())));
return 0; return 0;
@@ -428,7 +426,7 @@ public final class ModdedObjectCommands {
ModdedObjectUndo.record(owner, level, placer.undoSnapshot()); ModdedObjectUndo.record(owner, level, placer.undoSnapshot());
String tileNote = tileNote(placer); String tileNote = tileNote(placer);
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_PLACED_AT_ROT_WRITE_S_NON_AIR, MessageArgument.untrusted("key", key), MessageArgument.untrusted("value", target.getX()), MessageArgument.untrusted("value2", target.getY()), MessageArgument.untrusted("value3", target.getZ()), MessageArgument.untrusted("rotation", rotation), MessageArgument.untrusted("value4", placer.writes()), MessageArgument.untrusted("value5", placer.nonAirWrites()), MessageArgument.untrusted("tileNote", tileNote))); IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_PLACED_AT_ROT_WRITE_S_NON_AIR, MessageArgument.untrusted("key", key), MessageArgument.untrusted("value", target.getX()), MessageArgument.untrusted("value2", target.getY()), MessageArgument.untrusted("value3", target.getZ()), MessageArgument.untrusted("rotation", rotation), MessageArgument.untrusted("value4", placer.writes()), MessageArgument.untrusted("value5", placer.nonAirWrites()), MessageArgument.untrusted("tileNote", tileNote)));
LOGGER.info("Iris paste: {} at {},{},{} rot={} writes={} nonAir={} tilesRestored={} tilesSkipped={}", ModdedIrisLog.info("Iris paste: {} at {},{},{} rot={} writes={} nonAir={} tilesRestored={} tilesSkipped={}",
key, target.getX(), target.getY(), target.getZ(), rotation, placer.writes(), placer.nonAirWrites(), placer.restoredTiles(), placer.skippedTiles()); key, target.getX(), target.getY(), target.getZ(), rotation, placer.writes(), placer.nonAirWrites(), placer.restoredTiles(), placer.skippedTiles());
return placer.writes() > 0 ? 1 : 0; return placer.writes() > 0 ? 1 : 0;
} }
@@ -608,7 +606,7 @@ public final class ModdedObjectCommands {
try { try {
object = IrisData.loadAnyObject(key, engine == null ? null : engine.getData()); object = IrisData.loadAnyObject(key, engine == null ? null : engine.getData());
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris object load failed for {}", key, e); ModdedIrisLog.error("Iris object load failed for {}", key, e);
} }
if (object == null) { if (object == null) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_UNKNOWN_OBJECT, MessageArgument.untrusted("key", key))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_UNKNOWN_OBJECT, MessageArgument.untrusted("key", key)));
@@ -648,7 +646,7 @@ public final class ModdedObjectCommands {
try { try {
object = IrisData.loadAnyObject(key, engine == null ? null : engine.getData()); object = IrisData.loadAnyObject(key, engine == null ? null : engine.getData());
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris object load failed for {}", key, e); ModdedIrisLog.error("Iris object load failed for {}", key, e);
} }
if (object == null) { if (object == null) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_UNKNOWN_OBJECT_2, MessageArgument.untrusted("key", key))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_UNKNOWN_OBJECT_2, MessageArgument.untrusted("key", key)));
@@ -665,7 +663,7 @@ public final class ModdedObjectCommands {
try { try {
object.write(file); object.write(file);
} catch (IOException e) { } catch (IOException e) {
LOGGER.error("Iris object shrink save failed for {}", file.getAbsolutePath(), e); ModdedIrisLog.error("Iris object shrink save failed for {}", file.getAbsolutePath(), e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_FAILED_SAVE_OBJECT_2, MessageArgument.untrusted("value", file.getName()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage())))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_FAILED_SAVE_OBJECT_2, MessageArgument.untrusted("value", file.getName()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage()))));
return 0; return 0;
} }
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IObjectPlacer; import art.arcane.iris.engine.object.IObjectPlacer;
@@ -37,14 +38,11 @@ import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.Heightmap;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
final class ModdedObjectPlacer implements IObjectPlacer { final class ModdedObjectPlacer implements IObjectPlacer {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int DEFAULT_FLUID_HEIGHT = 63; private static final int DEFAULT_FLUID_HEIGHT = 63;
private final ServerLevel level; private final ServerLevel level;
@@ -199,7 +197,7 @@ final class ModdedObjectPlacer implements IObjectPlacer {
} }
restoredTiles++; restoredTiles++;
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris tile restore failed at {} {} {}", xx, yy, zz, e); ModdedIrisLog.error("Iris tile restore failed at {} {} {}", xx, yy, zz, e);
skippedTiles++; skippedTiles++;
} }
} }
@@ -18,13 +18,12 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
@@ -34,7 +33,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public final class ModdedObjectUndo { public final class ModdedObjectUndo {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int MAX_ENTRIES_PER_OWNER = 32; private static final int MAX_ENTRIES_PER_OWNER = 32;
private static final ConcurrentHashMap<UUID, Deque<Entry>> UNDOS = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<UUID, Deque<Entry>> UNDOS = new ConcurrentHashMap<>();
private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false); private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);
@@ -48,7 +46,7 @@ public final class ModdedObjectUndo {
public static void init() { public static void init() {
if (INITIALIZED.compareAndSet(false, true)) { if (INITIALIZED.compareAndSet(false, true)) {
LOGGER.info("Iris object undo service ready (bounded to {} paste(s) per player)", MAX_ENTRIES_PER_OWNER); ModdedIrisLog.info("Iris object undo service ready (bounded to {} paste(s) per player)", MAX_ENTRIES_PER_OWNER);
} }
} }
@@ -93,7 +91,7 @@ public final class ModdedObjectUndo {
// dimension id must never have blocks replayed into the dead ServerLevel. // dimension id must never have blocks replayed into the dead ServerLevel.
MinecraftServer server = entry.level().getServer(); MinecraftServer server = entry.level().getServer();
if (server == null || server.getLevel(entry.level().dimension()) != entry.level()) { if (server == null || server.getLevel(entry.level().dimension()) != entry.level()) {
LOGGER.warn("Iris object undo: skipped a stale entry for removed dimension {}", ModdedIrisLog.warn("Iris object undo: skipped a stale entry for removed dimension {}",
entry.level().dimension().identifier()); entry.level().dimension().identifier());
continue; continue;
} }
@@ -103,10 +101,10 @@ public final class ModdedObjectUndo {
entry.level().setBlock(block.getKey(), block.getValue(), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE); entry.level().setBlock(block.getKey(), block.getValue(), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE);
writes++; writes++;
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris object undo: failed to revert a block at {}", block.getKey(), e); ModdedIrisLog.error("Iris object undo: failed to revert a block at {}", block.getKey(), e);
} }
} }
LOGGER.info("Iris object undo: reverted {} block(s) in {}", writes, entry.level().dimension().identifier()); ModdedIrisLog.info("Iris object undo: reverted {} block(s) in {}", writes, entry.level().dimension().identifier());
reverted++; reverted++;
} }
return reverted; return reverted;
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.spi.IrisPlatforms; import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.core.pack.PackDirectoryResolver; import art.arcane.iris.core.pack.PackDirectoryResolver;
import art.arcane.iris.core.pack.PackResourceCleanup; import art.arcane.iris.core.pack.PackResourceCleanup;
@@ -31,8 +32,6 @@ import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands; import net.minecraft.commands.Commands;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@@ -44,7 +43,6 @@ import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.ModdedCommandMessages; import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.volmlib.util.localization.MessageArgument; import art.arcane.volmlib.util.localization.MessageArgument;
public final class ModdedPackCommands { public final class ModdedPackCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private ModdedPackCommands() { private ModdedPackCommands() {
@@ -139,7 +137,7 @@ public final class ModdedPackCommands {
} }
server.execute(() -> report(source, result)); server.execute(() -> report(source, result));
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris pack validation failed for {}", target.getName(), e); ModdedIrisLog.error("Iris pack validation failed for {}", target.getName(), e);
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_VALIDATION_FAILED, MessageArgument.untrusted("value", target.getName()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage()))))); server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_VALIDATION_FAILED, MessageArgument.untrusted("value", target.getName()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage())))));
broken++; broken++;
} }
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.pregenerator.PregenListener; import art.arcane.iris.core.pregenerator.PregenListener;
import art.arcane.iris.core.pregenerator.PregenMantleBackpressure; import art.arcane.iris.core.pregenerator.PregenMantleBackpressure;
@@ -31,8 +32,6 @@ import net.minecraft.server.level.ChunkResult;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.TicketType; import net.minecraft.server.level.TicketType;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@@ -48,7 +47,6 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
public final class ModdedPregenMethod implements PregeneratorMethod { public final class ModdedPregenMethod implements PregeneratorMethod {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final TicketType PREGEN_TICKET = new TicketType(TicketType.NO_TIMEOUT, TicketType.FLAG_LOADING | TicketType.FLAG_KEEP_DIMENSION_ACTIVE); private static final TicketType PREGEN_TICKET = new TicketType(TicketType.NO_TIMEOUT, TicketType.FLAG_LOADING | TicketType.FLAG_KEEP_DIMENSION_ACTIVE);
private static final int ADAPTIVE_TIMEOUT_STEP = 3; private static final int ADAPTIVE_TIMEOUT_STEP = 3;
private static final long ADAPTIVE_RECOVERY_INTERVAL = 64L; private static final long ADAPTIVE_RECOVERY_INTERVAL = 64L;
@@ -106,7 +104,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
@Override @Override
public void init() { public void init() {
pauseGuard.suspend(); pauseGuard.suspend();
LOGGER.info("Iris modded pregen init: dim={} mode={} inFlightCap={} timeout={}s workerPool={} chunkSystem={}", ModdedIrisLog.info("Iris modded pregen init: dim={} mode={} inFlightCap={} timeout={}s workerPool={} chunkSystem={}",
level.dimension().identifier(), level.dimension().identifier(),
sync ? "sync" : "async", sync ? "sync" : "async",
sync ? 1 : maxInFlight, sync ? 1 : maxInFlight,
@@ -114,7 +112,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
describeWorkerPool(), describeWorkerPool(),
ModdedGenPool.describeChunkSystem()); ModdedGenPool.describeChunkSystem());
if (!sync && !ModdedGenPool.parallelChunkSystem()) { if (!sync && !ModdedGenPool.parallelChunkSystem()) {
LOGGER.info("Iris pregen note: this loader uses the vanilla main-thread chunk system, which caps pregen throughput. For Bukkit-level speed on Fabric install C2ME (Concurrent Chunk Management Engine); on servers use Paper."); ModdedIrisLog.info("Iris pregen note: this loader uses the vanilla main-thread chunk system, which caps pregen throughput. For Bukkit-level speed on Fabric install C2ME (Concurrent Chunk Management Engine); on servers use Paper.");
} }
} }
@@ -128,7 +126,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} }
LOGGER.info("Iris modded pregen done: dim={} completed={} peakInFlight={} finalLimit={}", ModdedIrisLog.info("Iris modded pregen done: dim={} completed={} peakInFlight={} finalLimit={}",
level.dimension().identifier(), completed.get(), inFlightPeak.get(), adaptiveLimit.get()); level.dimension().identifier(), completed.get(), inFlightPeak.get(), adaptiveLimit.get());
if (deferFinalSaveIfRequested()) { if (deferFinalSaveIfRequested()) {
return; return;
@@ -245,7 +243,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
} }
long remainingNanos = deadline - System.nanoTime(); long remainingNanos = deadline - System.nanoTime();
if (remainingNanos <= 0L) { if (remainingNanos <= 0L) {
LOGGER.warn("Iris pregen level save did not complete in time for {}", level.dimension().identifier()); ModdedIrisLog.warn("Iris pregen level save did not complete in time for {}", level.dimension().identifier());
return; return;
} }
long waitMillis = Math.max(1L, Math.min(FINAL_SAVE_POLL_MILLIS, long waitMillis = Math.max(1L, Math.min(FINAL_SAVE_POLL_MILLIS,
@@ -260,7 +258,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
continue; continue;
} catch (ExecutionException e) { } catch (ExecutionException e) {
Throwable cause = e.getCause() == null ? e : e.getCause(); Throwable cause = e.getCause() == null ? e : e.getCause();
LOGGER.error("Iris pregen level save failed for {}", level.dimension().identifier(), cause); ModdedIrisLog.error("Iris pregen level save failed for {}", level.dimension().identifier(), cause);
throw new IllegalStateException("Iris pregen level save failed for " throw new IllegalStateException("Iris pregen level save failed for "
+ level.dimension().identifier(), cause); + level.dimension().identifier(), cause);
} }
@@ -337,7 +335,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
try { try {
Object result = loadFuture.get(timeoutSeconds, TimeUnit.SECONDS); Object result = loadFuture.get(timeoutSeconds, TimeUnit.SECONDS);
if (result instanceof ChunkResult<?> chunkResult && !chunkResult.isSuccess()) { if (result instanceof ChunkResult<?> chunkResult && !chunkResult.isSuccess()) {
LOGGER.warn("Iris pregen chunk {},{} returned no chunk: {}", x, z, chunkResult.getError()); ModdedIrisLog.warn("Iris pregen chunk {},{} returned no chunk: {}", x, z, chunkResult.getError());
listener.onChunkFailed(x, z); listener.onChunkFailed(x, z);
return; return;
} }
@@ -364,7 +362,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
// this abort the pregen thread spins hot against the dead chunk source until the JVM dies. // this abort the pregen thread spins hot against the dead chunk source until the JVM dies.
if (level.getServer().isStopped() || !level.getServer().isRunning()) { if (level.getServer().isStopped() || !level.getServer().isRunning()) {
if (SERVER_DEAD_LOGGED.compareAndSet(false, true)) { if (SERVER_DEAD_LOGGED.compareAndSet(false, true)) {
LOGGER.error("Iris pregen aborting: the server is no longer running (dim={})", level.dimension().identifier()); ModdedIrisLog.error("Iris pregen aborting: the server is no longer running (dim={})", level.dimension().identifier());
} }
listener.onChunkFailed(x, z); listener.onChunkFailed(x, z);
ModdedPregenJob.stop(); ModdedPregenJob.stop();
@@ -401,7 +399,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
return; return;
} }
if (result instanceof ChunkResult<?> chunkResult && !chunkResult.isSuccess()) { if (result instanceof ChunkResult<?> chunkResult && !chunkResult.isSuccess()) {
LOGGER.warn("Iris pregen chunk {},{} returned no chunk: {}", x, z, chunkResult.getError()); ModdedIrisLog.warn("Iris pregen chunk {},{} returned no chunk: {}", x, z, chunkResult.getError());
listener.onChunkFailed(x, z); listener.onChunkFailed(x, z);
return; return;
} }
@@ -425,10 +423,10 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
private void logChunkFailure(int x, int z, Throwable failure) { private void logChunkFailure(int x, int z, Throwable failure) {
Throwable cause = unwrap(failure); Throwable cause = unwrap(failure);
if (failureDetailLogged.compareAndSet(false, true)) { if (failureDetailLogged.compareAndSet(false, true)) {
LOGGER.warn("Iris pregen chunk {},{} failed; first failure follows", x, z, cause); ModdedIrisLog.warn("Iris pregen chunk {},{} failed; first failure follows", x, z, cause);
return; return;
} }
LOGGER.warn("Iris pregen chunk {},{} failed: {}", x, z, cause.toString()); ModdedIrisLog.warn("Iris pregen chunk {},{} failed: {}", x, z, cause.toString());
} }
private void markFinished() { private void markFinished() {
@@ -500,7 +498,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
try { try {
engine.getMantle().forceCleanupChunk(x, z); engine.getMantle().forceCleanupChunk(x, z);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.debug("Iris pregen mantle cleanup skipped for {},{}: {}", x, z, e.toString()); ModdedIrisLog.debug("Iris pregen mantle cleanup skipped for {},{}: {}", x, z, e.toString());
} }
} }
@@ -566,7 +564,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
try { try {
current = dedicated.pauseWhenEmptySeconds(); current = dedicated.pauseWhenEmptySeconds();
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.warn("Iris pregen could not read pause-when-empty-seconds: {}", e.toString()); ModdedIrisLog.warn("Iris pregen could not read pause-when-empty-seconds: {}", e.toString());
return; return;
} }
if (current <= 0) { if (current <= 0) {
@@ -591,7 +589,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
} }
suspendedFrom.set(current); suspendedFrom.set(current);
armCrashRestore(); armCrashRestore();
LOGGER.info("Iris pregen: suspending pause-when-empty (was {}s), restored when the job ends", current); ModdedIrisLog.info("Iris pregen: suspending pause-when-empty (was {}s), restored when the job ends", current);
} }
private void restore() { private void restore() {
@@ -606,9 +604,9 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
} }
try { try {
dedicated.setPauseWhenEmptySeconds(previous); dedicated.setPauseWhenEmptySeconds(previous);
LOGGER.info("Iris pregen: {} pause-when-empty ({}s)", what, previous); ModdedIrisLog.info("Iris pregen: {} pause-when-empty ({}s)", what, previous);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris pregen could not restore pause-when-empty-seconds={}: {}. Set pause-when-empty-seconds={} in server.properties.", ModdedIrisLog.error("Iris pregen could not restore pause-when-empty-seconds={}: {}. Set pause-when-empty-seconds={} in server.properties.",
previous, e.toString(), previous); previous, e.toString(), previous);
} }
} }
@@ -655,11 +653,11 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
if (!pauseStillArmed()) { if (!pauseStillArmed()) {
return; return;
} }
LOGGER.error("Iris pregen is timing out on an empty server while pause-when-empty-seconds is active: the paused server stops ticking. Set pause-when-empty-seconds=0 in server.properties, or keep a player online while pregenerating."); ModdedIrisLog.error("Iris pregen is timing out on an empty server while pause-when-empty-seconds is active: the paused server stops ticking. Set pause-when-empty-seconds=0 in server.properties, or keep a player online while pregenerating.");
} }
private void refuse(int current, String reason) { private void refuse(int current, String reason) {
LOGGER.error("Iris pregen could not suspend pause-when-empty-seconds={} ({}). The server stops ticking once empty, which stalls pregen: set pause-when-empty-seconds=0 in server.properties, or keep a player online while pregenerating.", ModdedIrisLog.error("Iris pregen could not suspend pause-when-empty-seconds={} ({}). The server stops ticking once empty, which stalls pregen: set pause-when-empty-seconds=0 in server.properties, or keep a player online while pregenerating.",
current, reason); current, reason);
} }
} }
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.tools.WorldMaintenance; import art.arcane.iris.core.tools.WorldMaintenance;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.mantle.EngineMantle; import art.arcane.iris.engine.mantle.EngineMantle;
@@ -51,8 +52,6 @@ import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -65,7 +64,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import art.arcane.iris.core.localization.IrisLanguage; import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.ModdedCommandMessages; import art.arcane.iris.core.localization.ModdedCommandMessages;
public final class ModdedRegen { public final class ModdedRegen {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int APPLY_AHEAD = 8; private static final int APPLY_AHEAD = 8;
private static final long CHUNK_SLOT_TIMEOUT_MILLIS = 120000L; private static final long CHUNK_SLOT_TIMEOUT_MILLIS = 120000L;
private static final long FINAL_APPLY_TIMEOUT_MILLIS = 300000L; private static final long FINAL_APPLY_TIMEOUT_MILLIS = 300000L;
@@ -101,7 +99,7 @@ public final class ModdedRegen {
ModdedRegen job = new ModdedRegen(source, level, generator, engine, centerX, centerZ, radius); ModdedRegen job = new ModdedRegen(source, level, generator, engine, centerX, centerZ, radius);
int chunks = (job.radius * 2 + 1) * (job.radius * 2 + 1); int chunks = (job.radius * 2 + 1) * (job.radius * 2 + 1);
job.ok("Regen started: " + chunks + " chunk(s) around " + centerX + "," + centerZ + ". Deleting and regenerating in place."); job.ok("Regen started: " + chunks + " chunk(s) around " + centerX + "," + centerZ + ". Deleting and regenerating in place.");
LOGGER.info("Iris regen start: dim={} center={},{} radius={} chunks={}", ModdedIrisLog.info("Iris regen start: dim={} center={},{} radius={} chunks={}",
level.dimension().identifier(), centerX, centerZ, job.radius, chunks); level.dimension().identifier(), centerX, centerZ, job.radius, chunks);
Thread thread = new Thread(job::run, "Iris Regenerate"); Thread thread = new Thread(job::run, "Iris Regenerate");
thread.setDaemon(true); thread.setDaemon(true);
@@ -117,11 +115,11 @@ public final class ModdedRegen {
List<int[]> targets = ChunkSpiral.centerOut(centerX, centerZ, radius); List<int[]> targets = ChunkSpiral.centerOut(centerX, centerZ, radius);
int applied = regenerate(targets); int applied = regenerate(targets);
ok("Regen finished: " + applied + "/" + targets.size() + " chunk(s) in " + Form.duration(M.ms() - startedAt, 2)); ok("Regen finished: " + applied + "/" + targets.size() + " chunk(s) in " + Form.duration(M.ms() - startedAt, 2));
LOGGER.info("Iris regen done: {}/{} chunks in {}ms", applied, targets.size(), M.ms() - startedAt); ModdedIrisLog.info("Iris regen done: {}/{} chunks in {}ms", applied, targets.size(), M.ms() - startedAt);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris regen failed", e); ModdedIrisLog.error("Iris regen failed", e);
fail("Regen failed: " + e); fail("Regen failed: " + e);
} finally { } finally {
WorldMaintenance.endWorldMaintenance(worldIdentity, "regen"); WorldMaintenance.endWorldMaintenance(worldIdentity, "regen");
@@ -155,7 +153,7 @@ public final class ModdedRegen {
int chunkZ = target[1]; int chunkZ = target[1];
if (!inFlight.tryAcquire(CHUNK_SLOT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { if (!inFlight.tryAcquire(CHUNK_SLOT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
aborted.set(true); aborted.set(true);
LOGGER.error("Iris regen aborted: chunk {},{} waited {}ms for an apply slot ({}/{} done)", ModdedIrisLog.error("Iris regen aborted: chunk {},{} waited {}ms for an apply slot ({}/{} done)",
chunkX, chunkZ, CHUNK_SLOT_TIMEOUT_MILLIS, completed.get(), total); chunkX, chunkZ, CHUNK_SLOT_TIMEOUT_MILLIS, completed.get(), total);
fail("Regen aborted: apply pipeline stalled at " + completed.get() + "/" + total + " chunk(s)"); fail("Regen aborted: apply pipeline stalled at " + completed.get() + "/" + total + " chunk(s)");
break; break;
@@ -173,7 +171,7 @@ public final class ModdedRegen {
try { try {
engine.generate(chunkX << 4, chunkZ << 4, blocks, biomes, false); engine.generate(chunkX << 4, chunkZ << 4, blocks, biomes, false);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris regen chunk {},{} generation failed", chunkX, chunkZ, e); ModdedIrisLog.error("Iris regen chunk {},{} generation failed", chunkX, chunkZ, e);
fail("Chunk " + chunkX + "," + chunkZ + " generation FAILED: " + e.getClass().getSimpleName()); fail("Chunk " + chunkX + "," + chunkZ + " generation FAILED: " + e.getClass().getSimpleName());
completed.incrementAndGet(); completed.incrementAndGet();
inFlight.release(); inFlight.release();
@@ -190,7 +188,7 @@ public final class ModdedRegen {
success = true; success = true;
applied.incrementAndGet(); applied.incrementAndGet();
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris regen chunk {},{} apply failed", chunkX, chunkZ, e); ModdedIrisLog.error("Iris regen chunk {},{} apply failed", chunkX, chunkZ, e);
fail("Chunk " + chunkX + "," + chunkZ + " apply FAILED: " + e.getClass().getSimpleName()); fail("Chunk " + chunkX + "," + chunkZ + " apply FAILED: " + e.getClass().getSimpleName());
} finally { } finally {
int done = completed.incrementAndGet(); int done = completed.incrementAndGet();
@@ -212,7 +210,7 @@ public final class ModdedRegen {
if (!allApplied.await(FINAL_APPLY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { if (!allApplied.await(FINAL_APPLY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
aborted.set(true); aborted.set(true);
long outstanding = allApplied.getCount(); long outstanding = allApplied.getCount();
LOGGER.error("Iris regen aborted: {} of {} chunk(s) did not finish within {}ms", ModdedIrisLog.error("Iris regen aborted: {} of {} chunk(s) did not finish within {}ms",
outstanding, total, FINAL_APPLY_TIMEOUT_MILLIS); outstanding, total, FINAL_APPLY_TIMEOUT_MILLIS);
fail("Regen aborted: " + outstanding + " of " + total + " chunk(s) never finished"); fail("Regen aborted: " + outstanding + " of " + total + " chunk(s) never finished");
} }
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.structure.StructureIndexService; import art.arcane.iris.core.structure.StructureIndexService;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
@@ -40,8 +41,6 @@ import net.minecraft.commands.Commands;
import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.commands.SharedSuggestionProvider;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
@@ -53,7 +52,6 @@ import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.ModdedCommandMessages; import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.volmlib.util.localization.MessageArgument; import art.arcane.volmlib.util.localization.MessageArgument;
public final class ModdedStructureCommands { public final class ModdedStructureCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private static final SuggestionProvider<CommandSourceStack> IRIS_STRUCTURE_KEYS = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestIrisStructureKeys(context, builder); private static final SuggestionProvider<CommandSourceStack> IRIS_STRUCTURE_KEYS = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestIrisStructureKeys(context, builder);
@@ -213,7 +211,7 @@ public final class ModdedStructureCommands {
piece.getObject().place(piece.getX(), piece.getY(), piece.getZ(), placer, config, rng, null, null, data); piece.getObject().place(piece.getX(), piece.getY(), piece.getZ(), placer, config, rng, null, null, data);
} }
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris structure place failed for {}", key, e); ModdedIrisLog.error("Iris structure place failed for {}", key, e);
ModdedObjectUndo.record(owner, level, placer.undoSnapshot()); ModdedObjectUndo.record(owner, level, placer.undoSnapshot());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_PLACE_FAILED_PARTIAL_CHANGES_RECORDED_UNDO, MessageArgument.untrusted("value", e.getClass().getSimpleName()))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_PLACE_FAILED_PARTIAL_CHANGES_RECORDED_UNDO, MessageArgument.untrusted("value", e.getClass().getSimpleName())));
return 0; return 0;
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.gui.GuiHost; import art.arcane.iris.core.gui.GuiHost;
import art.arcane.iris.core.gui.NoiseExplorerGUI; import art.arcane.iris.core.gui.NoiseExplorerGUI;
@@ -63,8 +64,6 @@ 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zeroturnaround.zip.ZipUtil; import org.zeroturnaround.zip.ZipUtil;
import java.awt.Desktop; import java.awt.Desktop;
@@ -85,7 +84,6 @@ import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.ModdedCommandMessages; import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.volmlib.util.localization.MessageArgument; import art.arcane.volmlib.util.localization.MessageArgument;
public final class ModdedStudioCommands { public final class ModdedStudioCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private static final Pattern PROJECT_NAME = Pattern.compile("[a-z0-9_-]+"); private static final Pattern PROJECT_NAME = Pattern.compile("[a-z0-9_-]+");
private static final Pattern STUDIO_ID_SANITIZER = Pattern.compile("[^a-z0-9_-]"); private static final Pattern STUDIO_ID_SANITIZER = Pattern.compile("[^a-z0-9_-]");
@@ -284,7 +282,7 @@ public final class ModdedStudioCommands {
try { try {
workspace = ModdedWorkspaceGenerator.writeWorkspace(IrisData.get(folder), folder, open); workspace = ModdedWorkspaceGenerator.writeWorkspace(IrisData.get(folder), folder, open);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris workspace write failed for {}", folder, e); ModdedIrisLog.error("Iris workspace write failed for {}", folder, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_FAILED_WRITE_WORKSPACE, MessageArgument.untrusted("value", folder.getAbsolutePath()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage())))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_FAILED_WRITE_WORKSPACE, MessageArgument.untrusted("value", folder.getAbsolutePath()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage()))));
return 0; return 0;
} }
@@ -299,7 +297,7 @@ public final class ModdedStudioCommands {
try { try {
Desktop.getDesktop().open(workspace); Desktop.getDesktop().open(workspace);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris workspace open failed for {}", workspace, e); ModdedIrisLog.error("Iris workspace open failed for {}", workspace, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_COULD_NOT_OPEN, MessageArgument.untrusted("value", workspace.getName()), MessageArgument.untrusted("value2", e.getClass().getSimpleName()))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_COULD_NOT_OPEN, MessageArgument.untrusted("value", workspace.getName()), MessageArgument.untrusted("value2", e.getClass().getSimpleName())));
return 0; return 0;
} }
@@ -380,8 +378,9 @@ public final class ModdedStudioCommands {
try { try {
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.fail(source, "Pack '" + pack server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(
+ "' is not installed. Use /iris download pack=overworld or pack=underworld, then restart.")); ModdedCommandMessages.MODDED_STUDIO_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART,
MessageArgument.untrusted("pack", pack))));
return; return;
} }
IrisData data = IrisData.get(packFolder); IrisData data = IrisData.get(packFolder);
@@ -393,7 +392,7 @@ public final class ModdedStudioCommands {
try { try {
ModdedWorkspaceGenerator.writeWorkspace(data, packFolder, true); ModdedWorkspaceGenerator.writeWorkspace(data, packFolder, true);
} catch (Throwable workspaceError) { } catch (Throwable workspaceError) {
LOGGER.error("Iris workspace write failed for {}", packFolder, workspaceError); ModdedIrisLog.error("Iris workspace write failed for {}", packFolder, workspaceError);
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain( server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(
ModdedCommandMessages.MODDED_STUDIO_COMMANDS_FAILED_WRITE_WORKSPACE, ModdedCommandMessages.MODDED_STUDIO_COMMANDS_FAILED_WRITE_WORKSPACE,
MessageArgument.untrusted("value", packFolder.getAbsolutePath()), MessageArgument.untrusted("value", packFolder.getAbsolutePath()),
@@ -407,7 +406,7 @@ public final class ModdedStudioCommands {
} }
}); });
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris studio open failed for {}", pack, e); ModdedIrisLog.error("Iris studio open failed for {}", pack, e);
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_OPEN_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e))))); server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_OPEN_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e)))));
} }
} }
@@ -417,7 +416,7 @@ public final class ModdedStudioCommands {
try { try {
handle = ModdedDimensionManager.create(server, dimensionId, pack, pack, seed); handle = ModdedDimensionManager.create(server, dimensionId, pack, pack, seed);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris console studio injection failed for {} ({})", dimensionId, pack, e); ModdedIrisLog.error("Iris console studio injection failed for {} ({})", dimensionId, pack, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_INJECTION_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e)))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_INJECTION_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e))));
return; return;
} }
@@ -430,7 +429,7 @@ public final class ModdedStudioCommands {
surface = engine.getMinHeight() + engine.getHeight(8, 8, false) + 2; surface = engine.getMinHeight() + engine.getHeight(8, 8, false) + 2;
} }
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris console studio surface probe failed for {}", dimensionId, e); ModdedIrisLog.error("Iris console studio surface probe failed for {}", dimensionId, e);
} }
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_CONSOLE_STUDIO_OPEN_NOW_RUNS_SEED_TRANSIENT_NOT_WRITTEN_IRIS, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("seed", seed))); IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_CONSOLE_STUDIO_OPEN_NOW_RUNS_SEED_TRANSIENT_NOT_WRITTEN_IRIS, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("seed", seed)));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_ENTER_IT_WITH_EXECUTE_RUN_TP_S_8_5_8, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("surface", surface))); IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_ENTER_IT_WITH_EXECUTE_RUN_TP_S_8_5_8, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("surface", surface)));
@@ -447,7 +446,7 @@ public final class ModdedStudioCommands {
try { try {
handle = ModdedDimensionManager.create(server, dimensionId, pack, pack, seed); handle = ModdedDimensionManager.create(server, dimensionId, pack, pack, seed);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris studio injection failed for {} ({})", dimensionId, pack, e); ModdedIrisLog.error("Iris studio injection failed for {} ({})", dimensionId, pack, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_INJECTION_FAILED_2, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e)))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_INJECTION_FAILED_2, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e))));
return; return;
} }
@@ -460,7 +459,7 @@ public final class ModdedStudioCommands {
surface = engine.getMinHeight() + engine.getHeight(8, 8, false) + 2; surface = engine.getMinHeight() + engine.getHeight(8, 8, false) + 2;
} }
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris studio surface probe failed for {}", dimensionId, e); ModdedIrisLog.error("Iris studio surface probe failed for {}", dimensionId, e);
} }
player.teleportTo(studio, 8.5D, surface, 8.5D, java.util.Set.<Relative>of(), player.getYRot(), player.getXRot(), false); player.teleportTo(studio, 8.5D, surface, 8.5D, java.util.Set.<Relative>of(), player.getYRot(), player.getXRot(), false);
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_OPEN_NOW_RUNS_SEED_USE_IRIS_STUDIO_CLOSE_WHEN, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("seed", seed))); IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_OPEN_NOW_RUNS_SEED_USE_IRIS_STUDIO_CLOSE_WHEN, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("seed", seed)));
@@ -480,7 +479,7 @@ public final class ModdedStudioCommands {
try { try {
ModdedDimensionManager.remove(server, dimensionId, true); ModdedDimensionManager.remove(server, dimensionId, true);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris studio close failed for {}", dimensionId, e); ModdedIrisLog.error("Iris studio close failed for {}", dimensionId, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_CLOSE_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e)))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_CLOSE_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e))));
return 0; return 0;
} }
@@ -553,7 +552,7 @@ public final class ModdedStudioCommands {
surface = engine.getMinHeight() + engine.getHeight(8, 8, false) + 2; surface = engine.getMinHeight() + engine.getHeight(8, 8, false) + 2;
} }
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris tpstudio surface probe failed", e); ModdedIrisLog.error("Iris tpstudio surface probe failed", e);
} }
player.teleportTo(studio, 8.5D, surface, 8.5D, java.util.Set.<Relative>of(), player.getYRot(), player.getXRot(), false); player.teleportTo(studio, 8.5D, surface, 8.5D, java.util.Set.<Relative>of(), player.getYRot(), player.getXRot(), false);
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_TELEPORTED_YOUR_STUDIO, MessageArgument.untrusted("dimensionId", dimensionId))); IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_TELEPORTED_YOUR_STUDIO, MessageArgument.untrusted("dimensionId", dimensionId)));
@@ -593,22 +592,23 @@ public final class ModdedStudioCommands {
try { try {
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.fail(source, "Template pack '" + template server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(
+ "' is not installed. Install its zip with /iris download link=<zip-url>, then restart.")); ModdedCommandMessages.MODDED_STUDIO_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART,
MessageArgument.untrusted("pack", template))));
return; return;
} }
IrisProjectCopier.copyProject(templateFolder, target, template, name); IrisProjectCopier.copyProject(templateFolder, target, template, name);
try { try {
ModdedWorkspaceGenerator.writeWorkspace(IrisData.get(target), target); ModdedWorkspaceGenerator.writeWorkspace(IrisData.get(target), target);
} catch (IOException e) { } catch (IOException e) {
LOGGER.error("Iris studio create workspace generation failed for {}", name, e); ModdedIrisLog.error("Iris studio create workspace generation failed for {}", name, e);
} }
server.execute(() -> { server.execute(() -> {
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_CREATED_PROJECT_AT, MessageArgument.untrusted("name", name), MessageArgument.untrusted("value", target.getAbsolutePath()))); IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_CREATED_PROJECT_AT, MessageArgument.untrusted("name", name), MessageArgument.untrusted("value", target.getAbsolutePath())));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_EDIT_DIMENSIONS_JSON_REST_PACK_VSCODE_WORKSPACE_WITH_JSON_SCHEMA, MessageArgument.untrusted("name", name))); IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_EDIT_DIMENSIONS_JSON_REST_PACK_VSCODE_WORKSPACE_WITH_JSON_SCHEMA, MessageArgument.untrusted("name", name)));
}); });
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris studio create failed for {}", name, e); ModdedIrisLog.error("Iris studio create failed for {}", name, e);
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PROJECT_CREATION_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e))))); server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PROJECT_CREATION_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e)))));
} }
}, "Iris Studio Create"); }, "Iris Studio Create");
@@ -630,7 +630,7 @@ public final class ModdedStudioCommands {
File result = compilePackage(folder, dimKey); File result = compilePackage(folder, dimKey);
server.execute(() -> IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACKAGE_COMPILED, MessageArgument.untrusted("value", result.getAbsolutePath())))); server.execute(() -> IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACKAGE_COMPILED, MessageArgument.untrusted("value", result.getAbsolutePath()))));
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris package failed for {}", dimKey, e); ModdedIrisLog.error("Iris package failed for {}", dimKey, e);
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACKAGING_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e))))); server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACKAGING_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e)))));
} }
}, "Iris Studio Package"); }, "Iris Studio Package");
@@ -728,7 +728,7 @@ public final class ModdedStudioCommands {
IO.copyFile(objectFile, new File(folder, "objects/" + objectKey + ".iob")); IO.copyFile(objectFile, new File(folder, "objects/" + objectKey + ".iob"));
hashes.append(IO.hash(objectFile)); hashes.append(IO.hash(objectFile));
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris package failed to copy object {}", objectKey, e); ModdedIrisLog.error("Iris package failed to copy object {}", objectKey, e);
} }
} }
@@ -793,7 +793,7 @@ public final class ModdedStudioCommands {
IO.writeAll(new File(folder, category + "/" + key + ".json"), json); IO.writeAll(new File(folder, category + "/" + key + ".json"), json);
return IO.hash(json); return IO.hash(json);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris package failed to write {}/{}", category, key, e); ModdedIrisLog.error("Iris package failed to write {}/{}", category, key, e);
return ""; return "";
} }
} }
@@ -838,7 +838,7 @@ public final class ModdedStudioCommands {
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_RARITY, MessageArgument.untrusted("key", key), MessageArgument.untrusted("rarity", rarity), MessageArgument.untrusted("value", Form.f((double) count.get() / totalTasks * 100, 2)))); IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_RARITY, MessageArgument.untrusted("key", key), MessageArgument.untrusted("rarity", rarity), MessageArgument.untrusted("value", Form.f((double) count.get() / totalTasks * 100, 2))));
})); }));
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris region sampling failed", e); ModdedIrisLog.error("Iris region sampling failed", e);
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_REGION_SAMPLING_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName())))); server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_REGION_SAMPLING_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()))));
} }
}, "Iris Region Sampler"); }, "Iris Region Sampler");
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.IrisStructureLocator; import art.arcane.iris.engine.framework.IrisStructureLocator;
import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy; import art.arcane.iris.engine.framework.NativeStructureGenerationPolicy;
@@ -31,8 +32,6 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier; import net.minecraft.resources.Identifier;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.Structure;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
@@ -44,7 +43,6 @@ import java.util.TreeMap;
import java.util.function.Predicate; import java.util.function.Predicate;
final class ModdedUnregisteredStructures { final class ModdedUnregisteredStructures {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private ModdedUnregisteredStructures() { private ModdedUnregisteredStructures() {
} }
@@ -67,13 +65,13 @@ final class ModdedUnregisteredStructures {
.filter((ExcludedStructure entry) -> entry.status() == ReportStatus.UNPLACED) .filter((ExcludedStructure entry) -> entry.status() == ReportStatus.UNPLACED)
.count(); .count();
long hidden = excluded.size() - unregistered - unplaced; long hidden = excluded.size() - unregistered - unplaced;
LOGGER.info("[Iris goto unregistered] {} structure candidate(s) excluded from /iris goto structure in {}", ModdedIrisLog.info("[Iris goto unregistered] {} structure candidate(s) excluded from /iris goto structure in {}",
excluded.size(), dimension); excluded.size(), dimension);
for (ExcludedStructure entry : excluded) { for (ExcludedStructure entry : excluded) {
LOGGER.info("[Iris goto unregistered] [{}] {} - {}", ModdedIrisLog.info("[Iris goto unregistered] [{}] {} - {}",
entry.status().label(), entry.key(), entry.reason()); entry.status().label(), entry.key(), entry.reason());
} }
LOGGER.info("[Iris goto unregistered] Inventory scope is the live registry, this pack's " ModdedIrisLog.info("[Iris goto unregistered] Inventory scope is the live registry, this pack's "
+ "nativeStructures placements, and structureLoader editable resources. This is deterministic " + "nativeStructures placements, and structureLoader editable resources. This is deterministic "
+ "eligibility analysis and performs no chunk search; absent unmanaged datapack resources " + "eligibility analysis and performs no chunk search; absent unmanaged datapack resources "
+ "cannot be inferred after registry loading."); + "cannot be inferred after registry loading.");
@@ -82,7 +80,7 @@ final class ModdedUnregisteredStructures {
+ unregistered + " unregistered, " + unplaced + " unplaced)."); + unregistered + " unregistered, " + unplaced + " unplaced).");
return 1; return 1;
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris failed to build the excluded structure report for {}", ModdedIrisLog.error("Iris failed to build the excluded structure report for {}",
level.dimension().identifier(), error); level.dimension().identifier(), error);
IrisModdedCommands.fail(source, IrisModdedCommands.fail(source,
"Iris could not build the excluded structure report; see the server console."); "Iris could not build the excluded structure report; see the server console.");
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
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.volmlib.util.localization.MessageArgument; import art.arcane.volmlib.util.localization.MessageArgument;
@@ -42,8 +43,6 @@ 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.item.component.TooltipDisplay;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.Color; import java.awt.Color;
import java.util.List; import java.util.List;
@@ -52,7 +51,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
public final class ModdedWandService { public final class ModdedWandService {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final ConcurrentHashMap<UUID, Selection> SELECTIONS = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<UUID, Selection> SELECTIONS = new ConcurrentHashMap<>();
private static final String WAND_TAG = "iris_wand"; private static final String WAND_TAG = "iris_wand";
private static final String DUST_TAG = "iris_dust"; private static final String DUST_TAG = "iris_dust";
@@ -213,7 +211,7 @@ public final class ModdedWandService {
draw(player.level(), player, selection); draw(player.level(), player, selection);
} }
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris wand selection draw failed", e); ModdedIrisLog.error("Iris wand selection draw failed", e);
} }
} }
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.localization.IrisLanguage; import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.IrisMessages; import art.arcane.iris.core.localization.IrisMessages;
import art.arcane.iris.core.localization.ModdedCommandMessages; import art.arcane.iris.core.localization.ModdedCommandMessages;
@@ -66,8 +67,6 @@ import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.storage.loot.LootTable; import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.HitResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -77,7 +76,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate; import java.util.function.Predicate;
public final class ModdedWhatCommands { public final class ModdedWhatCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = private static final Predicate<CommandSourceStack> GATE =
Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private static final SuggestionProvider<CommandSourceStack> MARKER_TYPES = private static final SuggestionProvider<CommandSourceStack> MARKER_TYPES =
@@ -343,7 +341,7 @@ public final class ModdedWhatCommands {
MessageArgument.untrusted("object", object))); MessageArgument.untrusted("object", object)));
} }
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris object lookup failed for /iris what block at {}, {}, {}", ModdedIrisLog.error("Iris object lookup failed for /iris what block at {}, {}, {}",
pos.getX(), pos.getY(), pos.getZ(), error); pos.getX(), pos.getY(), pos.getZ(), error);
} }
} }
@@ -486,7 +484,7 @@ public final class ModdedWhatCommands {
private static void markerFailure(CommandSourceStack source, private static void markerFailure(CommandSourceStack source,
ModdedScheduler scheduler, MarkerRun run, Throwable error) { ModdedScheduler scheduler, MarkerRun run, Throwable error) {
LOGGER.error("Iris marker scan failed for {}", run.marker(), error); ModdedIrisLog.error("Iris marker scan failed for {}", run.marker(), error);
scheduler.global(() -> { scheduler.global(() -> {
if (ACTIVE_MARKER_RUNS.remove(run.playerId(), run)) { if (ACTIVE_MARKER_RUNS.remove(run.playerId(), run)) {
IrisModdedCommands.fail(source, IrisLanguage.plain( IrisModdedCommands.fail(source, IrisLanguage.plain(
@@ -514,7 +512,7 @@ public final class ModdedWhatCommands {
private static void logLookupFailure(CommandSourceStack source, private static void logLookupFailure(CommandSourceStack source,
String operation, Throwable error, String operation, Throwable error,
TextKey message) { TextKey message) {
LOGGER.error("Iris /what {} lookup failed in {}", operation, ModdedIrisLog.error("Iris /what {} lookup failed in {}", operation,
source.getLevel().dimension().identifier(), error); source.getLevel().dimension().identifier(), error);
IrisModdedCommands.fail(source, IrisLanguage.plain( IrisModdedCommands.fail(source, IrisLanguage.plain(
message, message,
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.command; package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.pack.BrokenPackException; import art.arcane.iris.core.pack.BrokenPackException;
import art.arcane.iris.core.pack.PackValidationRegistry; import art.arcane.iris.core.pack.PackValidationRegistry;
@@ -42,8 +43,6 @@ import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.commands.arguments.IdentifierArgument; import net.minecraft.commands.arguments.IdentifierArgument;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
@@ -57,7 +56,6 @@ import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.iris.core.localization.RuntimeUiMessages; import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.volmlib.util.localization.MessageArgument; import art.arcane.volmlib.util.localization.MessageArgument;
public final class ModdedWorldCommands { public final class ModdedWorldCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
private static final String DEFAULT_NAMESPACE = "irisworldgen"; private static final String DEFAULT_NAMESPACE = "irisworldgen";
private static final long DEFAULT_SEED = 1337L; private static final long DEFAULT_SEED = 1337L;
@@ -174,8 +172,9 @@ public final class ModdedWorldCommands {
if (packFolder.isDirectory()) { if (packFolder.isDirectory()) {
return enableInstalled(source, server, dimensionId, pack, packDimension, seed); return enableInstalled(source, server, dimensionId, pack, packDimension, seed);
} }
IrisModdedCommands.fail(source, "Pack '" + pack IrisModdedCommands.fail(source, IrisLanguage.plain(
+ "' is not installed. Use /iris download pack=overworld or pack=underworld, then restart."); ModdedCommandMessages.MODDED_WORLD_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART,
MessageArgument.untrusted("pack", pack)));
return 0; return 0;
} }
@@ -189,7 +188,7 @@ public final class ModdedWorldCommands {
try { try {
ModdedDimensionManager.createPersistent(server, dimensionId, pack, packDimension, seed); ModdedDimensionManager.createPersistent(server, dimensionId, pack, packDimension, seed);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris world injection failed for {} (pack={} dim={})", dimensionId, pack, packDimension, e); ModdedIrisLog.error("Iris world injection failed for {} (pack={} dim={})", dimensionId, pack, packDimension, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_FAILED_INJECT_IRIS_WORLD, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e)))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_FAILED_INJECT_IRIS_WORLD, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e))));
return 0; return 0;
} }
@@ -220,7 +219,7 @@ public final class ModdedWorldCommands {
try { try {
ModdedDimensionManager.createPersistent(server, dimensionId, pack, packDimension, seed); ModdedDimensionManager.createPersistent(server, dimensionId, pack, packDimension, seed);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris primary world injection failed for {} (pack={} dim={})", dimensionId, pack, packDimension, e); ModdedIrisLog.error("Iris primary world injection failed for {} (pack={} dim={})", dimensionId, pack, packDimension, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_FAILED_INJECT_IRIS_PRIMARY_WORLD, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e)))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_FAILED_INJECT_IRIS_PRIMARY_WORLD, MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e))));
return 0; return 0;
} }
@@ -265,8 +264,9 @@ public final class ModdedWorldCommands {
if (packFolder.isDirectory()) { if (packFolder.isDirectory()) {
return applyMainWorld(source, pack, packDimension, packRaw, seed); return applyMainWorld(source, pack, packDimension, packRaw, seed);
} }
IrisModdedCommands.fail(source, "Pack '" + pack IrisModdedCommands.fail(source, IrisLanguage.plain(
+ "' is not installed. Use /iris download pack=overworld or pack=underworld, then restart."); ModdedCommandMessages.MODDED_WORLD_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART,
MessageArgument.untrusted("pack", pack)));
return 0; return 0;
} }
@@ -279,7 +279,7 @@ public final class ModdedWorldCommands {
return 0; return 0;
} }
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris main world pack load failed for {} (dim={})", pack, packDimension, e); ModdedIrisLog.error("Iris main world pack load failed for {} (dim={})", pack, packDimension, e);
if (PackValidationRegistry.get(pack) == null) { if (PackValidationRegistry.get(pack) == null) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_PACK_IS_NOT_READY_YET_STILL_LOADING_VALIDATING_TRY_COMMAND, MessageArgument.untrusted("pack", pack))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_PACK_IS_NOT_READY_YET_STILL_LOADING_VALIDATING_TRY_COMMAND, MessageArgument.untrusted("pack", pack)));
return 0; return 0;
@@ -384,7 +384,7 @@ public final class ModdedWorldCommands {
try { try {
removed = ModdedDimensionManager.removePersistent(server, dimensionId, wipeStorage); removed = ModdedDimensionManager.removePersistent(server, dimensionId, wipeStorage);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris world removal failed for {}", dimensionId, e); ModdedIrisLog.error("Iris world removal failed for {}", dimensionId, e);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_FAILED_REMOVE_IRIS_WORLD, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e)))); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_FAILED_REMOVE_IRIS_WORLD, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("value", e.getClass().getSimpleName()), MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(e))));
return 0; return 0;
} }
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.service; package art.arcane.iris.modded.service;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.service.EngineMaintenance; import art.arcane.iris.core.service.EngineMaintenance;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
@@ -27,8 +28,6 @@ import art.arcane.iris.modded.ModdedWorldEngines;
import art.arcane.iris.spi.IrisLogging; import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.util.project.context.IrisContext; import art.arcane.iris.util.project.context.IrisContext;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@@ -42,7 +41,6 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public final class ModdedEngineMaintenanceService implements ModdedTickableService { public final class ModdedEngineMaintenanceService implements ModdedTickableService {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final long MAINTENANCE_PERIOD_MILLIS = 2_000L; private static final long MAINTENANCE_PERIOD_MILLIS = 2_000L;
private static final long SAVE_PERIOD_MILLIS = 60_000L; private static final long SAVE_PERIOD_MILLIS = 60_000L;
private static final long SHUTDOWN_TIMEOUT_SECONDS = 30L; private static final long SHUTDOWN_TIMEOUT_SECONDS = 30L;
@@ -120,7 +118,7 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi
} catch (RejectedExecutionException exception) { } catch (RejectedExecutionException exception) {
inFlight.remove(engine); inFlight.remove(engine);
if (active == service && !active.isShutdown()) { if (active == service && !active.isShutdown()) {
LOGGER.error("Iris rejected engine maintenance for {}", engineName(engine), exception); ModdedIrisLog.error("Iris rejected engine maintenance for {}", engineName(engine), exception);
} }
} }
} }
@@ -148,7 +146,7 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi
EngineMaintenance.Outcome outcome = EngineMaintenance.run(engine); EngineMaintenance.Outcome outcome = EngineMaintenance.run(engine);
if (outcome.unloadedTectonicPlates() > 0) { if (outcome.unloadedTectonicPlates() > 0) {
LOGGER.debug("Iris unloaded {} tectonic plates in {}ms for {}", ModdedIrisLog.debug("Iris unloaded {} tectonic plates in {}ms for {}",
outcome.unloadedTectonicPlates(), outcome.unloadDurationMillis(), engineName(engine)); outcome.unloadedTectonicPlates(), outcome.unloadDurationMillis(), engineName(engine));
} }
} catch (GenerationSessionException exception) { } catch (GenerationSessionException exception) {
@@ -156,13 +154,13 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi
return; return;
} }
IrisLogging.reportError(exception); IrisLogging.reportError(exception);
LOGGER.error("Iris engine maintenance session failed for {}", engineName(engine), exception); ModdedIrisLog.error("Iris engine maintenance session failed for {}", engineName(engine), exception);
} catch (Throwable exception) { } catch (Throwable exception) {
if (EngineMaintenance.isMantleClosed(exception)) { if (EngineMaintenance.isMantleClosed(exception)) {
return; return;
} }
IrisLogging.reportError(exception); IrisLogging.reportError(exception);
LOGGER.error("Iris engine maintenance failed for {}", engineName(engine), exception); ModdedIrisLog.error("Iris engine maintenance failed for {}", engineName(engine), exception);
} }
} }
@@ -180,7 +178,7 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi
return; return;
} }
IrisLogging.reportError(exception); IrisLogging.reportError(exception);
LOGGER.error("Iris engine save failed for {}", engineName(engine), exception); ModdedIrisLog.error("Iris engine save failed for {}", engineName(engine), exception);
} }
} }
@@ -212,13 +210,13 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi
IllegalStateException failure = new IllegalStateException( IllegalStateException failure = new IllegalStateException(
"Iris engine maintenance workers did not stop after shutdownNow"); "Iris engine maintenance workers did not stop after shutdownNow");
IrisLogging.reportError(failure); IrisLogging.reportError(failure);
LOGGER.error("Iris engine maintenance did not terminate; active engine lifecycle leases will block unsafe shutdown", failure); ModdedIrisLog.error("Iris engine maintenance did not terminate; active engine lifecycle leases will block unsafe shutdown", failure);
return false; return false;
} catch (InterruptedException exception) { } catch (InterruptedException exception) {
active.shutdownNow(); active.shutdownNow();
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
IrisLogging.reportError(exception); IrisLogging.reportError(exception);
LOGGER.error("Interrupted while draining Iris engine maintenance", exception); ModdedIrisLog.error("Interrupted while draining Iris engine maintenance", exception);
return false; return false;
} }
} }
@@ -18,11 +18,10 @@
package art.arcane.iris.modded.service; package art.arcane.iris.modded.service;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.framework.MeteredCache; import art.arcane.iris.engine.framework.MeteredCache;
import art.arcane.iris.engine.framework.PreservationRegistry; import art.arcane.iris.engine.framework.PreservationRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.List; import java.util.List;
@@ -31,7 +30,6 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public final class ModdedPreservationService implements ModdedService, PreservationRegistry { public final class ModdedPreservationService implements ModdedService, PreservationRegistry {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final long DEREFERENCE_INTERVAL_MILLIS = 60000L; private static final long DEREFERENCE_INTERVAL_MILLIS = 60000L;
private final List<Thread> threads = new CopyOnWriteArrayList<>(); private final List<Thread> threads = new CopyOnWriteArrayList<>();
@@ -107,17 +105,17 @@ public final class ModdedPreservationService implements ModdedService, Preservat
} }
try { try {
thread.interrupt(); thread.interrupt();
LOGGER.info("Iris preservation interrupted thread {}", thread.getName()); ModdedIrisLog.info("Iris preservation interrupted thread {}", thread.getName());
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris preservation failed to interrupt thread {}", thread.getName(), error); ModdedIrisLog.error("Iris preservation failed to interrupt thread {}", thread.getName(), error);
} }
} }
for (ExecutorService service : services) { for (ExecutorService service : services) {
try { try {
service.shutdownNow(); service.shutdownNow();
LOGGER.info("Iris preservation shut down executor {}", service); ModdedIrisLog.info("Iris preservation shut down executor {}", service);
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris preservation failed to shut down executor {}", service, error); ModdedIrisLog.error("Iris preservation failed to shut down executor {}", service, error);
} }
} }
} }
@@ -60,6 +60,6 @@ public final class ModdedSettingsHotloadService implements ModdedTickableService
} }
private static File settingsFile() { private static File settingsFile() {
return IrisPlatforms.get().dataFile("settings.json"); return IrisPlatforms.get().dataFile("iris.json");
} }
} }
@@ -18,6 +18,7 @@
package art.arcane.iris.modded.service; package art.arcane.iris.modded.service;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.gui.PregeneratorJob; import art.arcane.iris.core.gui.PregeneratorJob;
import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.tools.WorldMaintenance; import art.arcane.iris.core.tools.WorldMaintenance;
@@ -37,8 +38,6 @@ import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.io.ReactiveFolder; import art.arcane.volmlib.util.io.ReactiveFolder;
import art.arcane.volmlib.util.scheduling.ChronoLatch; import art.arcane.volmlib.util.scheduling.ChronoLatch;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.util.HashSet; import java.util.HashSet;
@@ -52,7 +51,6 @@ import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public final class ModdedStudioHotloadService implements ModdedTickableService, EnginePlatformHooks { public final class ModdedStudioHotloadService implements ModdedTickableService, EnginePlatformHooks {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final String STUDIO_DIMENSION_PREFIX = "irisworldgen:studio_"; private static final String STUDIO_DIMENSION_PREFIX = "irisworldgen:studio_";
private static final long POLL_MILLIS = 250L; private static final long POLL_MILLIS = 250L;
private static final long CHECK_LATCH_MILLIS = 1_000L; private static final long CHECK_LATCH_MILLIS = 1_000L;
@@ -237,7 +235,7 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
folder.check(); folder.check();
} }
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris studio hotload check failed for {}", dimensionId, e); ModdedIrisLog.error("Iris studio hotload check failed for {}", dimensionId, e);
} }
} }
@@ -249,9 +247,9 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
try { try {
engine.hotloadSilently(); engine.hotloadSilently();
generator.onHotload(); generator.onHotload();
LOGGER.info("Iris studio hotload {} pack={} {}ms", dimensionId, engine.getDimension().getLoadKey(), System.currentTimeMillis() - start); ModdedIrisLog.info("Iris studio hotload {} pack={} {}ms", dimensionId, engine.getDimension().getLoadKey(), System.currentTimeMillis() - start);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris studio hotload failed for {}", dimensionId, e); ModdedIrisLog.error("Iris studio hotload failed for {}", dimensionId, e);
throw new IllegalStateException("Iris studio hotload failed for " + dimensionId, e); throw new IllegalStateException("Iris studio hotload failed for " + dimensionId, e);
} }
} }
@@ -275,7 +273,7 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
IrisData data = engine.getData(); IrisData data = engine.getData();
ModdedWorkspaceGenerator.writeWorkspace(data, data.getDataFolder()); ModdedWorkspaceGenerator.writeWorkspace(data, data.getDataFolder());
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Iris {} failed for {}", operation, engine.getDimension().getLoadKey(), e); ModdedIrisLog.error("Iris {} failed for {}", operation, engine.getDimension().getLoadKey(), e);
} }
} }
@@ -1,5 +1,6 @@
package art.arcane.iris.modded.service; package art.arcane.iris.modded.service;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.modded.ModdedEngineBootstrap; import art.arcane.iris.modded.ModdedEngineBootstrap;
import art.arcane.iris.modded.ModdedScheduler; import art.arcane.iris.modded.ModdedScheduler;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@@ -12,8 +13,6 @@ import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -21,7 +20,6 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
final class ModdedTreeFellerPresentation { final class ModdedTreeFellerPresentation {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final int MIN_BLOCKS_PER_PULSE = 4; private static final int MIN_BLOCKS_PER_PULSE = 4;
private static final int MAX_BLOCKS_PER_PULSE = 64; private static final int MAX_BLOCKS_PER_PULSE = 64;
private static final int TARGET_EROSION_PULSES = 60; private static final int TARGET_EROSION_PULSES = 60;
@@ -215,13 +213,13 @@ final class ModdedTreeFellerPresentation {
private void reportEffectFailure(Throwable error) { private void reportEffectFailure(Throwable error) {
if (effectFailureReported.compareAndSet(false, true)) { if (effectFailureReported.compareAndSet(false, true)) {
LOGGER.error("Iris modded tree-feller presentation failed", error); ModdedIrisLog.error("Iris modded tree-feller presentation failed", error);
} }
} }
private void reportDeliveryFailure(Throwable error) { private void reportDeliveryFailure(Throwable error) {
if (deliveryFailureReported.compareAndSet(false, true)) { if (deliveryFailureReported.compareAndSet(false, true)) {
LOGGER.error("Iris modded tree-feller drop delivery failed", error); ModdedIrisLog.error("Iris modded tree-feller drop delivery failed", error);
} }
} }
} }
@@ -1,5 +1,6 @@
package art.arcane.iris.modded.service; package art.arcane.iris.modded.service;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.service.tree.TreeDefinitionIndex; import art.arcane.iris.core.service.tree.TreeDefinitionIndex;
import art.arcane.iris.core.service.tree.TreeMarkerTraversal; import art.arcane.iris.core.service.tree.TreeMarkerTraversal;
@@ -23,8 +24,6 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -38,7 +37,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
public final class ModdedTreeFellerService implements ModdedTickableService { public final class ModdedTreeFellerService implements ModdedTickableService {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final ThreadLocal<Integer> BREAK_PROBE_DEPTH = ThreadLocal.withInitial(() -> 0); private static final ThreadLocal<Integer> BREAK_PROBE_DEPTH = ThreadLocal.withInitial(() -> 0);
private final AtomicBoolean enabled = new AtomicBoolean(); private final AtomicBoolean enabled = new AtomicBoolean();
@@ -245,7 +243,7 @@ public final class ModdedTreeFellerService implements ModdedTickableService {
(x, y, z) -> markerAt(prepared.engine(), prepared.minimumY(), x, y, z) (x, y, z) -> markerAt(prepared.engine(), prepared.minimumY(), x, y, z)
); );
} catch (Throwable error) { } catch (Throwable error) {
LOGGER.error("Iris modded tree-feller discovery failed", error); ModdedIrisLog.error("Iris modded tree-feller discovery failed", error);
discovery = new TreeMarkerTraversal.Discovery(List.of(), false); discovery = new TreeMarkerTraversal.Discovery(List.of(), false);
} }
TreeMarkerTraversal.Discovery resolved = discovery; TreeMarkerTraversal.Discovery resolved = discovery;
@@ -0,0 +1,38 @@
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 IrisClientHotloadTest {
private static final IrisMessage.DimensionStatus OVERWORLD = new IrisMessage.DimensionStatus(
"overworld",
"pack",
1L,
-64,
320,
true
);
@Test
public void successfulCurrentPackHotloadInvalidatesWorldCaches() {
IrisMessage.StudioHotload hotload = new IrisMessage.StudioHotload("pack", 0, false, "");
assertTrue(IrisClient.shouldInvalidateForHotload(OVERWORLD, hotload));
}
@Test
public void failedOrUnrelatedHotloadRetainsWorldCaches() {
assertFalse(IrisClient.shouldInvalidateForHotload(
OVERWORLD,
new IrisMessage.StudioHotload("pack", 0, true, "failed")
));
assertFalse(IrisClient.shouldInvalidateForHotload(
OVERWORLD,
new IrisMessage.StudioHotload("other", 0, false, "")
));
assertFalse(IrisClient.shouldInvalidateForHotload(null, null));
}
}
@@ -5,7 +5,10 @@ import org.junit.Test;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
@@ -24,4 +27,49 @@ public class ModdedIrisLogLevelCoverageTest {
String body = source.substring(router, source.indexOf("public static void debug(", router)); String body = source.substring(router, source.indexOf("public static void debug(", router));
assertTrue(body, body.contains("default -> info(message);")); assertTrue(body, body.contains("default -> info(message);"));
} }
@Test
public void slf4jStyleArgumentsAndTrailingThrowableArePreserved() {
RuntimeException failure = new RuntimeException("broken");
ModdedIrisLog.RenderedLog rendered = ModdedIrisLog.render("chunk {},{} failed", 4, 9, failure);
assertEquals("chunk 4,9 failed", rendered.message());
assertEquals(failure, rendered.error());
}
@Test
public void formattedDebugThrowableUsesTheVisibleDebugRouteWhenEnabled() throws IOException {
String source = Files.readString(Path.of(System.getProperty("iris.moddedCommonSources"),
"art/arcane/iris/modded/ModdedIrisLog.java"));
int start = source.indexOf("public static void debug(String format, Object... arguments)");
int end = source.indexOf("public static void info(String message)", start);
assertTrue("formatted debug overload not found", start >= 0);
assertTrue("formatted debug overload boundary not found", end > start);
String body = source.substring(start, end);
assertTrue(body, body.contains("if (!debugEnabled())"));
assertTrue(body, body.contains("LOGGER.info(\"[Iris/DEBUG] \" + clean(rendered.message()), rendered.error())"));
}
@Test
public void moddedProductionUsesTheIrisLogFrontDoor() throws IOException {
Path root = Path.of(System.getProperty("iris.moddedCommonSources"));
try (Stream<Path> files = Files.walk(root)) {
List<Path> bypasses = files
.filter(path -> path.toString().endsWith(".java"))
.filter(path -> !path.getFileName().toString().equals("ModdedIrisLog.java"))
.filter(path -> {
try {
String source = Files.readString(path);
return source.contains("LoggerFactory.getLogger")
|| source.contains("private static final Logger LOGGER");
} catch (IOException unreadable) {
throw new IllegalStateException(unreadable);
}
})
.toList();
assertTrue(bypasses.toString(), bypasses.isEmpty());
}
}
} }
@@ -91,7 +91,7 @@ public class ModdedPlatformPathsTest {
File iris = new File(temporaryFolder.getRoot(), "iris"); File iris = new File(temporaryFolder.getRoot(), "iris");
assertEquals(iris, platform.dataFolder()); assertEquals(iris, platform.dataFolder());
assertEquals(new File(iris, "settings.json"), platform.dataFile("settings.json")); assertEquals(new File(iris, "iris.json"), platform.dataFile("iris.json"));
assertEquals(new File(iris, "parity"), platform.dataFolder("parity")); assertEquals(new File(iris, "parity"), platform.dataFolder("parity"));
} }
@@ -85,13 +85,12 @@ public class IrisSettings {
private static IrisSettings read() { private static IrisSettings read() {
IrisSettings loaded = new IrisSettings(); IrisSettings loaded = new IrisSettings();
File s = IrisPlatforms.get().dataFile("settings.json"); File s = IrisPlatforms.get().dataFile("iris.json");
if (!s.exists()) { if (!s.exists()) {
try { try {
IO.writeAll(s, new JSONObject(new Gson().toJson(loaded)).toString(4)); IO.writeAll(s, new JSONObject(new Gson().toJson(loaded)).toString(4));
} catch (JSONException | IOException e) { } catch (JSONException | IOException e) {
e.printStackTrace();
IrisLogging.reportError(e); IrisLogging.reportError(e);
} }
@@ -106,32 +105,18 @@ public class IrisSettings {
loaded = parsed; loaded = parsed;
} }
migrateLegacyKeys(loaded, ss);
try { try {
IO.writeAll(s, new JSONObject(new Gson().toJson(loaded)).toString(4)); IO.writeAll(s, new JSONObject(new Gson().toJson(loaded)).toString(4));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace();
} }
} catch (Throwable ee) { } catch (Throwable ee) {
// IrisLogging.reportError(ee); causes a self-reference & stackoverflow // IrisLogging.reportError(ee); causes a self-reference & stackoverflow
IrisLogging.error("Configuration Error in settings.json! " + ee.getClass().getSimpleName() + ": " + ee.getMessage()); IrisLogging.error("Configuration Error in iris.json! " + ee.getClass().getSimpleName() + ": " + ee.getMessage());
} }
return loaded; return loaded;
} }
private static void migrateLegacyKeys(IrisSettings target, String rawJson) {
JSONObject root = new JSONObject(rawJson);
JSONObject worldObject = root.optJSONObject("world");
if (worldObject == null || !worldObject.has("anbientEntitySpawningSystem")) {
return;
}
target.getWorld().setAmbientEntitySpawningSystem(worldObject.optBoolean("anbientEntitySpawningSystem", target.getWorld().isAmbientEntitySpawningSystem()));
IrisLogging.info("Migrated legacy settings key world.anbientEntitySpawningSystem -> world.ambientEntitySpawningSystem");
}
public static void invalidate() { public static void invalidate() {
synchronized (SETTINGS_LOCK) { synchronized (SETTINGS_LOCK) {
settings = null; settings = null;
@@ -169,7 +154,6 @@ public class IrisSettings {
if (parsed == null) { if (parsed == null) {
throw new IllegalArgumentException("Iris settings snapshot did not contain an object"); throw new IllegalArgumentException("Iris settings snapshot did not contain an object");
} }
migrateLegacyKeys(parsed, rawJson);
} catch (RuntimeException failure) { } catch (RuntimeException failure) {
throw new IllegalArgumentException("Iris settings snapshot is invalid", failure); throw new IllegalArgumentException("Iris settings snapshot is invalid", failure);
} }
@@ -179,12 +163,11 @@ public class IrisSettings {
} }
public void forceSave() { public void forceSave() {
File s = IrisPlatforms.get().dataFile("settings.json"); File s = IrisPlatforms.get().dataFile("iris.json");
try { try {
IO.writeAll(s, new JSONObject(new Gson().toJson(this)).toString(4)); IO.writeAll(s, new JSONObject(new Gson().toJson(this)).toString(4));
} catch (JSONException | IOException e) { } catch (JSONException | IOException e) {
e.printStackTrace();
IrisLogging.reportError(e); IrisLogging.reportError(e);
} }
} }
@@ -321,17 +304,18 @@ public class IrisSettings {
@Data @Data
public static class IrisSettingsGeneral { public static class IrisSettingsGeneral {
public String language = "en_US"; public String language = "en_US";
public boolean metrics = true;
public boolean commandSounds = true; public boolean commandSounds = true;
public boolean debug = false; public boolean debug = false;
public boolean dumpMantleOnError = false; public boolean dumpMantleOnError = false;
public boolean disableNMS = false; public boolean disableNMS = false;
public boolean pluginMetrics = true;
public boolean splashLogoStartup = true; public boolean splashLogoStartup = true;
public boolean useConsoleCustomColors = true; public boolean useConsoleCustomColors = true;
public boolean useCustomColorsIngame = true; public boolean useCustomColorsIngame = true;
/** /**
* Boss bar progress loaders for jobs, studio opens, world creation, chunk jobs and pack * Boss bars for jobs, Studio opens, chunk jobs, and pack downloads. Ordinary
* downloads. Turning this off keeps the action bar progress line; only the bar goes away. * world creation uses only its action-bar lifecycle meter; creation-time
* pregeneration retains its dedicated long-running boss bar.
*/ */
public boolean progressBossBar = true; public boolean progressBossBar = true;
public boolean adjustVanillaHeight = false; public boolean adjustVanillaHeight = false;
@@ -56,6 +56,8 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.AtomicMoveNotSupportedException;
@@ -281,11 +283,7 @@ public class ServerConfigurator {
IrisLogging.error("Unable to install datapacks, fixer is null!"); IrisLogging.error("Unable to install datapacks, fixer is null!");
return DatapackInstallResult.failedResult(); return DatapackInstallResult.failedResult();
} }
if (fullInstall) { IrisLogging.debug("Checking Data Packs...");
IrisLogging.info("Checking Data Packs...");
} else {
IrisLogging.debug("Checking Data Packs...");
}
List<File> packRoots; List<File> packRoots;
List<IrisGeneratorBinding> bindings; List<IrisGeneratorBinding> bindings;
try { try {
@@ -349,11 +347,7 @@ public class ServerConfigurator {
} }
} }
} }
if (fullInstall) { IrisLogging.debug("Data Packs Setup!");
IrisLogging.info("Data Packs Setup!");
} else {
IrisLogging.debug("Data Packs Setup!");
}
boolean verifiedRestartRequired = fullInstall && verifyDataPacksPost(); boolean verifiedRestartRequired = fullInstall && verifyDataPacksPost();
boolean restartRequired = fullInstall && (reapply.changed() || verifiedRestartRequired); boolean restartRequired = fullInstall && (reapply.changed() || verifiedRestartRequired);
@@ -1026,12 +1020,21 @@ public class ServerConfigurator {
? "Iris startup validation requires a restart." ? "Iris startup validation requires a restart."
: reason.trim(); : reason.trim();
IrisLogging.warn(restartReason + " Restarting server before default worlds are loaded."); IrisLogging.warn(restartReason + " Restarting server before default worlds are loaded.");
boolean restartInvoked = false;
try { try {
Bukkit.restart(); restartInvoked = invokeImmediateRestartIfSupported(Bukkit.class);
} catch (Throwable failure) { } catch (ReflectiveOperationException | RuntimeException | LinkageError failure) {
IrisLogging.reportError("Unable to restart the server at the Iris startup boundary.", failure); Throwable cause = failure instanceof InvocationTargetException invocationFailure
&& invocationFailure.getCause() != null
? invocationFailure.getCause()
: failure;
IrisLogging.reportError("Unable to restart the server at the Iris startup boundary.", cause);
}
if (restartInvoked) {
IrisLogging.error("The immediate Iris startup restart returned unexpectedly; stopping the server instead.");
} else {
IrisLogging.warn("This server has no immediate restart API; stopping at the Iris startup boundary instead.");
} }
IrisLogging.error("The immediate Iris startup restart returned unexpectedly; stopping the server instead.");
try { try {
Bukkit.shutdown(); Bukkit.shutdown();
} catch (Throwable failure) { } catch (Throwable failure) {
@@ -1039,6 +1042,17 @@ public class ServerConfigurator {
} }
} }
static boolean invokeImmediateRestartIfSupported(Class<?> bukkitApi) throws ReflectiveOperationException {
Method restartMethod;
try {
restartMethod = Objects.requireNonNull(bukkitApi, "Bukkit API class").getMethod("restart");
} catch (NoSuchMethodException ignored) {
return false;
}
restartMethod.invoke(null);
return true;
}
public static boolean verifyDataPackInstalled(IrisDimension dimension) { public static boolean verifyDataPackInstalled(IrisDimension dimension) {
KSet<String> keys = new KSet<>(); KSet<String> keys = new KSet<>();
boolean warn = false; boolean warn = false;
@@ -1056,11 +1070,9 @@ public class ServerConfigurator {
if (!INMS.get().supportsDataPacks()) { if (!INMS.get().supportsDataPacks()) {
if (!keys.isEmpty()) { if (!keys.isEmpty()) {
IrisLogging.warn("===================================================================================");
IrisLogging.warn("Pack " + key + " has " + keys.size() + " custom biome(s). "); IrisLogging.warn("Pack " + key + " has " + keys.size() + " custom biome(s). ");
IrisLogging.warn("Your server version does not yet support datapacks for iris."); IrisLogging.warn("Your server version does not yet support datapacks for iris.");
IrisLogging.warn("The world will generate these biomes as backup biomes."); IrisLogging.warn("The world will generate these biomes as backup biomes.");
IrisLogging.warn("====================================================================================");
} }
return true; return true;
@@ -92,7 +92,6 @@ public final class SettingsHotloadWatch implements AutoCloseable {
} catch (RuntimeException failure) { } catch (RuntimeException failure) {
IrisLogging.error("Iris settings and locale hotload watcher failed: " + failureDetail(failure)); IrisLogging.error("Iris settings and locale hotload watcher failed: " + failureDetail(failure));
IrisLogging.reportError(failure); IrisLogging.reportError(failure);
failure.printStackTrace();
} }
} }
} }
@@ -113,7 +112,7 @@ public final class SettingsHotloadWatch implements AutoCloseable {
boolean missing = "missing".equals(snapshot.signature()); boolean missing = "missing".equals(snapshot.signature());
if (isSettingsFile(file)) { if (isSettingsFile(file)) {
if (missing) { if (missing) {
IrisLogging.warn("settings.json was removed; retaining the last valid runtime settings."); IrisLogging.warn("iris.json was removed; retaining the last valid runtime settings.");
return true; return true;
} }
if (snapshot.normalizedContent() == null) { if (snapshot.normalizedContent() == null) {
@@ -210,7 +209,6 @@ public final class SettingsHotloadWatch implements AutoCloseable {
} catch (RuntimeException failure) { } catch (RuntimeException failure) {
IrisLogging.error("Rejected invalid settings hotload from " + file.getAbsolutePath() + ": " + failureDetail(failure)); IrisLogging.error("Rejected invalid settings hotload from " + file.getAbsolutePath() + ": " + failureDetail(failure));
IrisLogging.reportError(failure); IrisLogging.reportError(failure);
failure.printStackTrace();
return false; return false;
} }
} }
@@ -221,7 +219,6 @@ public final class SettingsHotloadWatch implements AutoCloseable {
} catch (RuntimeException failure) { } catch (RuntimeException failure) {
IrisLogging.error("Rejected invalid locale hotload from " + file.getAbsolutePath() + ": " + failureDetail(failure)); IrisLogging.error("Rejected invalid locale hotload from " + file.getAbsolutePath() + ": " + failureDetail(failure));
IrisLogging.reportError(failure); IrisLogging.reportError(failure);
failure.printStackTrace();
return false; return false;
} }
} }
@@ -251,7 +248,6 @@ public final class SettingsHotloadWatch implements AutoCloseable {
} }
IrisLogging.error("Failed to read watched Iris file " + path + ": " + failureDetail(failure)); IrisLogging.error("Failed to read watched Iris file " + path + ": " + failureDetail(failure));
IrisLogging.reportError(failure); IrisLogging.reportError(failure);
failure.printStackTrace();
} }
private void clearCaptureFailure(File file) { private void clearCaptureFailure(File file) {
@@ -262,12 +258,12 @@ public final class SettingsHotloadWatch implements AutoCloseable {
File file = delta.file(); File file = delta.file();
if (isSettingsFile(file)) { if (isSettingsFile(file)) {
if (delta.after() != null) { if (delta.after() != null) {
IrisLogging.info("Hotloaded settings.json"); IrisLogging.debug("Hotloaded iris.json");
} }
return; return;
} }
if (IrisLanguage.isActiveOverrideFile(file)) { if (IrisLanguage.isActiveOverrideFile(file)) {
IrisLogging.info("Hotloaded locale override " + file.getName()); IrisLogging.debug("Hotloaded locale override " + file.getName());
} }
} }
@@ -983,8 +983,8 @@ public final class VisionGUI extends JPanel implements MouseWheelListener, KeyLi
notifyUser(IrisLanguage.plain(DesktopUiMessages.VISION_NO_PLAYER)); notifyUser(IrisLanguage.plain(DesktopUiMessages.VISION_NO_PLAYER));
return; return;
} }
int worldX = (int) screenToWorldX(point.x); int worldX = floorWorldCoordinate(screenToWorldX(point.x));
int worldZ = (int) screenToWorldZ(point.y); int worldZ = floorWorldCoordinate(screenToWorldZ(point.y));
overlay.teleport(worldX, worldZ); overlay.teleport(worldX, worldZ);
notifyUser(IrisLanguage.plain( notifyUser(IrisLanguage.plain(
DesktopUiMessages.VISION_TELEPORTING, DesktopUiMessages.VISION_TELEPORTING,
@@ -1009,6 +1009,10 @@ public final class VisionGUI extends JPanel implements MouseWheelListener, KeyLi
controller.close(); controller.close();
} }
static int floorWorldCoordinate(double coordinate) {
return (int) StrictMath.floor(coordinate);
}
private static String modeName(RenderType type) { private static String modeName(RenderType type) {
return IrisLanguage.plain(modeKey(type)); return IrisLanguage.plain(modeKey(type));
} }
@@ -944,7 +944,7 @@ public final class IrisWorldRemovalService {
PlatformChunkGenerator generator = null; PlatformChunkGenerator generator = null;
if (world != null) { if (world != null) {
Path loadedDirectory = world.getWorldFolder().toPath().toAbsolutePath().normalize(); Path loadedDirectory = world.getWorldFolder().toPath().toAbsolutePath().normalize();
WorldRemovalPathPolicy.validateStoragePath(target.levelRoot(), target.worldKey(), loadedDirectory); WorldRemovalPathPolicy.validateStorageRoot(target.levelRoot(), target.worldKey(), loadedDirectory);
generator = IrisToolbelt.access(world); generator = IrisToolbelt.access(world);
} }
boolean registryManaged = IrisWorlds.get().getWorlds().containsKey(target.worldKey().toString()); boolean registryManaged = IrisWorlds.get().getWorlds().containsKey(target.worldKey().toString());
@@ -208,9 +208,11 @@ public final class WorldLifecycleService {
return worldsProviderBackend; return worldsProviderBackend;
} }
if (request.studio() && capabilities.serverFamily().isPaperLike()) { if (capabilities.regionizedRuntime()
|| capabilities.serverFamily() == ServerFamily.FOLIA
|| (request.studio() && capabilities.serverFamily().isPaperLike())) {
if (!paperLikeRuntimeBackend.supports(request, capabilities)) { if (!paperLikeRuntimeBackend.supports(request, capabilities)) {
throw new IllegalStateException("World lifecycle backend paper_like_runtime is unavailable for studio create on " throw new IllegalStateException("World lifecycle backend paper_like_runtime is unavailable for runtime create on "
+ capabilities.serverFamily().id() + ": " + capabilities.paperLikeResolution()); + capabilities.serverFamily().id() + ": " + capabilities.paperLikeResolution());
} }
return paperLikeRuntimeBackend; return paperLikeRuntimeBackend;
@@ -10,7 +10,6 @@ import art.arcane.iris.util.common.scheduling.J;
import art.arcane.volmlib.util.bukkit.WorldIdentity; import art.arcane.volmlib.util.bukkit.WorldIdentity;
import art.arcane.volmlib.util.scheduling.FoliaScheduler; import art.arcane.volmlib.util.scheduling.FoliaScheduler;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
@@ -472,12 +471,8 @@ final class WorldLifecycleSupport {
) { ) {
String worldName = world.getName(); String worldName = world.getName();
try { try {
try { if (capabilities.minecraftServer() == null || capabilities.removeLevelMethod() == null) {
return CompletableFuture.completedFuture(Bukkit.unloadWorld(world, save)); return CompletableFuture.completedFuture(Bukkit.unloadWorld(world, save));
} catch (UnsupportedOperationException unsupported) {
if (capabilities.minecraftServer() == null || capabilities.removeLevelMethod() == null) {
return CompletableFuture.failedFuture(unsupported);
}
} }
if (!announceManualWorldUnload(world)) { if (!announceManualWorldUnload(world)) {
@@ -489,8 +484,9 @@ final class WorldLifecycleSupport {
} }
Method getHandleMethod = world.getClass().getMethod("getHandle"); Method getHandleMethod = world.getClass().getMethod("getHandle");
Object serverLevel = getHandleMethod.invoke(world); Object serverLevel = getHandleMethod.invoke(world);
CompletableFuture<Boolean> operation = closeServerLevelAsync(world, serverLevel) CompletableFuture<Boolean> operation = detachServerLevelAsync(capabilities, serverLevel, world)
.thenCompose(unused -> detachServerLevelAsync(capabilities, serverLevel, world)) .thenCompose(unused -> drainChunkTasksAsync(world, serverLevel))
.thenCompose(unused -> closeServerLevelAsync(world, serverLevel))
.thenApply(unused -> WorldIdentity.resolve(WorldIdentity.key(world)).isEmpty()); .thenApply(unused -> WorldIdentity.resolve(WorldIdentity.key(world)).isEmpty());
return contextualizeUnloadFailure(worldName, operation); return contextualizeUnloadFailure(worldName, operation);
} catch (Throwable e) { } catch (Throwable e) {
@@ -527,7 +523,13 @@ final class WorldLifecycleSupport {
boolean save boolean save
) { ) {
CompletableFuture<Boolean> callbackFuture = new CompletableFuture<>(); CompletableFuture<Boolean> callbackFuture = new CompletableFuture<>();
Consumer<Boolean> callback = unloaded -> callbackFuture.complete(Boolean.TRUE.equals(unloaded)); Consumer<Object> callback = unloaded -> {
try {
callbackFuture.complete(asyncUnloadSucceeded(unloaded));
} catch (Throwable failure) {
callbackFuture.completeExceptionally(unwrap(failure));
}
};
try { try {
unloadWorldAsyncMethod.invoke(bukkitServer, world, save, callback); unloadWorldAsyncMethod.invoke(bukkitServer, world, save, callback);
} catch (Throwable e) { } catch (Throwable e) {
@@ -536,6 +538,17 @@ final class WorldLifecycleSupport {
return callbackFuture; return callbackFuture;
} }
private static boolean asyncUnloadSucceeded(Object result) throws ReflectiveOperationException {
if (result instanceof Boolean unloaded) {
return unloaded;
}
if (result == null) {
return false;
}
Method isSuccessMethod = result.getClass().getMethod("isSuccess");
return Boolean.TRUE.equals(isSuccessMethod.invoke(result));
}
private static CompletableFuture<Boolean> contextualizeUnloadFailure( private static CompletableFuture<Boolean> contextualizeUnloadFailure(
String worldName, String worldName,
CompletableFuture<Boolean> operation CompletableFuture<Boolean> operation
@@ -569,33 +582,59 @@ final class WorldLifecycleSupport {
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
if (!J.isFolia()) { Runnable closeTask = () -> {
try { try {
closeMethod.invoke(serverLevel); closeMethod.invoke(serverLevel);
return CompletableFuture.completedFuture(null);
} catch (Throwable e) { } catch (Throwable e) {
return CompletableFuture.failedFuture(unwrap(e)); throw new RuntimeException(unwrap(e));
} }
};
return runGlobalAsync(closeTask).orTimeout(90L, TimeUnit.SECONDS);
}
private static CompletableFuture<Void> drainChunkTasksAsync(World world, Object serverLevel) {
Method schedulerMethod;
try {
schedulerMethod = CapabilityResolution.resolveMethod(
serverLevel.getClass(),
"moonrise$getChunkTaskScheduler",
method -> method.getParameterCount() == 0
);
} catch (Throwable e) {
return CompletableFuture.failedFuture(unwrap(e));
}
if (schedulerMethod == null) {
return CompletableFuture.completedFuture(null);
} }
Location spawn = world.getSpawnLocation(); return J.afut(() -> {
int chunkX = spawn == null ? 0 : spawn.getBlockX() >> 4;
int chunkZ = spawn == null ? 0 : spawn.getBlockZ() >> 4;
CompletableFuture<Void> closeFuture = new CompletableFuture<>();
boolean scheduled = J.runRegion(world, chunkX, chunkZ, () -> {
try { try {
closeMethod.invoke(serverLevel); Object scheduler = schedulerMethod.invoke(serverLevel);
closeFuture.complete(null); Method haltMethod = CapabilityResolution.resolveMethod(
scheduler.getClass(),
"halt",
method -> {
Class<?>[] parameters = method.getParameterTypes();
return parameters.length == 2
&& boolean.class.equals(parameters[0])
&& long.class.equals(parameters[1]);
}
);
if (haltMethod == null) {
return;
}
Object halted = haltMethod.invoke(
scheduler,
true,
TimeUnit.SECONDS.toNanos(90L));
if (halted instanceof Boolean complete && !complete) {
throw new IllegalStateException(
"Chunk scheduler drain timed out for world \"" + world.getName() + "\".");
}
} catch (Throwable e) { } catch (Throwable e) {
closeFuture.completeExceptionally(unwrap(e)); throw new RuntimeException(unwrap(e));
} }
}); }).orTimeout(90L, TimeUnit.SECONDS);
if (!scheduled) {
return CompletableFuture.failedFuture(new IllegalStateException(
"Failed to schedule region close task for world \"" + world.getName() + "\"."
));
}
return closeFuture.orTimeout(90L, TimeUnit.SECONDS);
} }
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
@@ -47,7 +47,6 @@ public class WorldEditLink {
} catch (Throwable e) { } catch (Throwable e) {
if (errorThrottle.flip()) { if (errorThrottle.flip()) {
IrisLogging.error("Could not get selection"); IrisLogging.error("Could not get selection");
e.printStackTrace();
IrisLogging.reportError(e); IrisLogging.reportError(e);
} }
invalidate(); invalidate();
@@ -14,6 +14,7 @@ import art.arcane.iris.core.nms.container.BlockProperty;
import art.arcane.iris.core.nms.container.Pair; import art.arcane.iris.core.nms.container.Pair;
import art.arcane.iris.core.service.ExternalDataSVC; import art.arcane.iris.core.service.ExternalDataSVC;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.volmlib.util.collection.KMap; import art.arcane.volmlib.util.collection.KMap;
import art.arcane.iris.util.common.data.IrisCustomData; import art.arcane.iris.util.common.data.IrisCustomData;
import org.bukkit.block.Block; import org.bukkit.block.Block;
@@ -82,7 +83,7 @@ public class NexoDataProvider extends ExternalDataProvider {
try { try {
return builder.build(); return builder.build();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); IrisLogging.reportError("Failed to build Nexo item data for " + itemId + ".", e);
throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key()); throw new MissingResourceException("Failed to find ItemData!", itemId.namespace(), itemId.key());
} }
} }
@@ -278,7 +278,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
} }
} catch (Throwable e) { } catch (Throwable e) {
IrisLogging.reportError(e); IrisLogging.reportError(e);
e.printStackTrace();
} }
return null; return null;
@@ -450,7 +449,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
return r; return r;
} catch (Throwable e) { } catch (Throwable e) {
IrisLogging.reportError(e); IrisLogging.reportError(e);
e.printStackTrace();
IrisLogging.error("Failed to create loader! " + registrant.getCanonicalName()); IrisLogging.error("Failed to create loader! " + registrant.getCanonicalName());
} }
@@ -561,7 +559,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
)); ));
} }
IrisLogging.reportError(failure); IrisLogging.reportError(failure);
failure.printStackTrace();
throw failure; throw failure;
} }
@@ -730,7 +727,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
.map(s -> s.split("\\Q.\\E")[0]) .map(s -> s.split("\\Q.\\E")[0])
.forEach(s -> l.add("snippet/" + s)); .forEach(s -> l.add("snippet/" + s));
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); IrisLogging.reportError("Failed to scan Iris snippets in " + snippetFolder + ".", e);
} }
return l; return l;
@@ -183,10 +183,6 @@ public final class BukkitCommandMessagesExtended {
"iris.bukkit.commandiris.install_pack_and_restart", "iris.bukkit.commandiris.install_pack_and_restart",
C.YELLOW + "Install it with " + C.AQUA + "{command}" + C.YELLOW + " and restart the server." C.YELLOW + "Install it with " + C.AQUA + "{command}" + C.YELLOW + " and restart the server."
); );
public static final TextKey COMMAND_IRIS_WORLD_STAGING_COMPLETED_RESTARTING_SERVER_GENERATE_LOAD = TextKey.of(
"iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load",
C.GREEN + "World staging completed. Iris is restarting the server to generate/load \"" + "{worldName}" + "\"."
);
public static final TextKey COMMAND_IRIS_EXCEPTION_RAISED_DURING_CREATION_SEE_CONSOLE_MORE_DETAILS = TextKey.of( public static final TextKey COMMAND_IRIS_EXCEPTION_RAISED_DURING_CREATION_SEE_CONSOLE_MORE_DETAILS = TextKey.of(
"iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details",
C.RED + "Exception raised during creation. See the console for more details." C.RED + "Exception raised during creation. See the console for more details."
@@ -195,26 +191,6 @@ public final class BukkitCommandMessagesExtended {
"iris.bukkit.commandiris.successfully_created_your_world", "iris.bukkit.commandiris.successfully_created_your_world",
C.GREEN + "Successfully created your world!" C.GREEN + "Successfully created your world!"
); );
public static final TextKey COMMAND_IRIS_RUNTIME_WORLD_CREATION_IS_DISABLED_ON_FOLIA = TextKey.of(
"iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia",
C.YELLOW + "Runtime world creation is disabled on Folia."
);
public static final TextKey COMMAND_IRIS_PREPARING_WORLD_FILES_BUKKIT_YML_NEXT_STARTUP = TextKey.of(
"iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup",
C.YELLOW + "Preparing world files and bukkit.yml for next startup..."
);
public static final TextKey COMMAND_IRIS_FAILED_STAGE_WORLD_FILES_DIMENSION = TextKey.of(
"iris.bukkit.commandiris.failed_stage_world_files_dimension",
C.RED + "Failed to stage world files for dimension \"" + "{value}" + "\"."
);
public static final TextKey COMMAND_IRIS_STAGED_IRIS_WORLD_WITH_GENERATOR_IRIS_SEED = TextKey.of(
"iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed",
C.GREEN + "Staged Iris world \"" + "{name}" + "\" with generator Iris:" + "{value}" + " and seed " + "{seed}" + "."
);
public static final TextKey COMMAND_IRIS_FAILED_UPDATE_BUKKIT_YML = TextKey.of(
"iris.bukkit.commandiris.failed_update_bukkit_yml",
C.RED + "Failed to update bukkit.yml: " + "{value}"
);
public static final TextKey COMMAND_IRIS_SPECIFIED_PLAYER_DOES_NOT_EXIST = TextKey.of( public static final TextKey COMMAND_IRIS_SPECIFIED_PLAYER_DOES_NOT_EXIST = TextKey.of(
"iris.bukkit.commandiris.specified_player_does_not_exist", "iris.bukkit.commandiris.specified_player_does_not_exist",
C.RED + "The specified player does not exist." C.RED + "The specified player does not exist."
@@ -327,10 +303,6 @@ public final class BukkitCommandMessagesExtended {
"iris.bukkit.commandiris.loading_world", "iris.bukkit.commandiris.loading_world",
C.GREEN + "Loading world: " + "{logicalWorldName}" C.GREEN + "Loading world: " + "{logicalWorldName}"
); );
public static final TextKey COMMAND_IRIS_FOLIA_CANNOT_LOAD_NEW_WORLDS_AT_RUNTIME_RESTART_SERVER_LOAD = TextKey.of(
"iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load",
C.YELLOW + "Folia cannot load new worlds at runtime. Restart the server to load \"" + "{logicalWorldName}" + "\"."
);
public static final TextKey COMMAND_IRIS_LOADED_SUCCESSFULLY = TextKey.of( public static final TextKey COMMAND_IRIS_LOADED_SUCCESSFULLY = TextKey.of(
"iris.bukkit.commandiris.loaded_successfully", "iris.bukkit.commandiris.loaded_successfully",
C.GREEN + "{logicalWorldName}" + " loaded successfully." C.GREEN + "{logicalWorldName}" + " loaded successfully."
@@ -874,14 +846,8 @@ public final class BukkitCommandMessagesExtended {
COMMAND_IRIS_TRY_ONE_OVERWORLD_VANILLA_FLAT_THEEND, COMMAND_IRIS_TRY_ONE_OVERWORLD_VANILLA_FLAT_THEEND,
COMMAND_IRIS_DIMENSION_NOT_FOUND, COMMAND_IRIS_DIMENSION_NOT_FOUND,
COMMAND_IRIS_INSTALL_PACK_AND_RESTART, COMMAND_IRIS_INSTALL_PACK_AND_RESTART,
COMMAND_IRIS_WORLD_STAGING_COMPLETED_RESTARTING_SERVER_GENERATE_LOAD,
COMMAND_IRIS_EXCEPTION_RAISED_DURING_CREATION_SEE_CONSOLE_MORE_DETAILS, COMMAND_IRIS_EXCEPTION_RAISED_DURING_CREATION_SEE_CONSOLE_MORE_DETAILS,
COMMAND_IRIS_SUCCESSFULLY_CREATED_YOUR_WORLD, COMMAND_IRIS_SUCCESSFULLY_CREATED_YOUR_WORLD,
COMMAND_IRIS_RUNTIME_WORLD_CREATION_IS_DISABLED_ON_FOLIA,
COMMAND_IRIS_PREPARING_WORLD_FILES_BUKKIT_YML_NEXT_STARTUP,
COMMAND_IRIS_FAILED_STAGE_WORLD_FILES_DIMENSION,
COMMAND_IRIS_STAGED_IRIS_WORLD_WITH_GENERATOR_IRIS_SEED,
COMMAND_IRIS_FAILED_UPDATE_BUKKIT_YML,
COMMAND_IRIS_SPECIFIED_PLAYER_DOES_NOT_EXIST, COMMAND_IRIS_SPECIFIED_PLAYER_DOES_NOT_EXIST,
COMMAND_IRIS_IRIS_V_BY_VOLMIT_SOFTWARE, COMMAND_IRIS_IRIS_V_BY_VOLMIT_SOFTWARE,
COMMAND_IRIS_TO, COMMAND_IRIS_TO,
@@ -910,7 +876,6 @@ public final class BukkitCommandMessagesExtended {
COMMAND_IRIS_IS_NOT_IRIS_WORLD, COMMAND_IRIS_IS_NOT_IRIS_WORLD,
COMMAND_IRIS_COULD_NOT_DETERMINE_IRIS_DIMENSION, COMMAND_IRIS_COULD_NOT_DETERMINE_IRIS_DIMENSION,
COMMAND_IRIS_LOADING_WORLD, COMMAND_IRIS_LOADING_WORLD,
COMMAND_IRIS_FOLIA_CANNOT_LOAD_NEW_WORLDS_AT_RUNTIME_RESTART_SERVER_LOAD,
COMMAND_IRIS_LOADED_SUCCESSFULLY, COMMAND_IRIS_LOADED_SUCCESSFULLY,
COMMAND_IRIS_THIS_IS_NOT_IRIS_WORLD_IRIS_WORLDS_3, COMMAND_IRIS_THIS_IS_NOT_IRIS_WORLD_IRIS_WORLDS_3,
COMMAND_IRIS_EVACUATING_WORLD, COMMAND_IRIS_EVACUATING_WORLD,
@@ -406,10 +406,6 @@ public final class BukkitRuntimeMessages {
"iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details",
C.RED + "Some schematics failed to convert. Check the console for details." C.RED + "Some schematics failed to convert. Check the console for details."
); );
public static final TextKey STUDIO_S_V_C_INSTALLING_PACKAGE = TextKey.of(
"iris.bukkit.runtime.studiosvc.installing_package",
C.GOLD + "World pack " + C.AQUA + "{name}" + ":" + "{loadKey}" + C.GRAY + " | " + C.WHITE + "Publishing snapshot"
);
public static final TextKey STUDIO_S_V_C_PACK_COPY_REQUIRES_ASYNC_THREAD = TextKey.of( public static final TextKey STUDIO_S_V_C_PACK_COPY_REQUIRES_ASYNC_THREAD = TextKey.of(
"iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread",
C.RED + "Iris refused to copy the world pack on the Bukkit primary thread." C.RED + "Iris refused to copy the world pack on the Bukkit primary thread."
@@ -708,7 +704,6 @@ public final class BukkitRuntimeMessages {
IRIS_CONVERTER_FAILED_CONVERT, IRIS_CONVERTER_FAILED_CONVERT,
IRIS_CONVERTER_CONVERTED_3, IRIS_CONVERTER_CONVERTED_3,
IRIS_CONVERTER_SOME_SCHEMATICS_FAILED_CONVERT_CHECK_CONSOLE_DETAILS, IRIS_CONVERTER_SOME_SCHEMATICS_FAILED_CONVERT_CHECK_CONSOLE_DETAILS,
STUDIO_S_V_C_INSTALLING_PACKAGE,
STUDIO_S_V_C_PACK_COPY_REQUIRES_ASYNC_THREAD, STUDIO_S_V_C_PACK_COPY_REQUIRES_ASYNC_THREAD,
STUDIO_S_V_C_PACK_INSTALL_FAILED, STUDIO_S_V_C_PACK_INSTALL_FAILED,
STUDIO_S_V_C_LOOKING_PACKAGE, STUDIO_S_V_C_LOOKING_PACKAGE,
@@ -130,7 +130,6 @@ public final class IrisLanguage {
dataFolder = resolvedRoot; dataFolder = resolvedRoot;
IrisLogging.error("Rejected locale setting '" + locale + "'; continuing with " + activeLocale + "."); IrisLogging.error("Rejected locale setting '" + locale + "'; continuing with " + activeLocale + ".");
IrisLogging.reportError(exception); IrisLogging.reportError(exception);
exception.printStackTrace();
return false; return false;
} }
@@ -159,7 +158,6 @@ public final class IrisLanguage {
} catch (RuntimeException exception) { } catch (RuntimeException exception) {
IrisLogging.error("Rejected locale setting '" + configured + "'; continuing with " + activeLocale + "."); IrisLogging.error("Rejected locale setting '" + configured + "'; continuing with " + activeLocale + ".");
IrisLogging.reportError(exception); IrisLogging.reportError(exception);
exception.printStackTrace();
return false; return false;
} }
@@ -214,7 +212,7 @@ public final class IrisLanguage {
activeLocale = requestedLocale; activeLocale = requestedLocale;
int warnings = result.validation().warnings().size(); int warnings = result.validation().warnings().size();
IrisLogging.info("Loaded locale " + requestedLocale + " with " + warnings + " fallback " IrisLogging.debug("Loaded locale " + requestedLocale + " with " + warnings + " fallback "
+ (warnings == 1 ? "entry" : "entries") + "."); + (warnings == 1 ? "entry" : "entries") + ".");
return true; return true;
} }
@@ -228,7 +226,6 @@ public final class IrisLanguage {
+ failure.getClass().getSimpleName() + failure.getClass().getSimpleName()
+ (failure.getMessage() == null ? "" : " - " + failure.getMessage())); + (failure.getMessage() == null ? "" : " - " + failure.getMessage()));
IrisLogging.reportError(failure); IrisLogging.reportError(failure);
failure.printStackTrace();
} }
} }
} }
@@ -641,7 +638,6 @@ public final class IrisLanguage {
} }
if (result.failure() != null) { if (result.failure() != null) {
IrisLogging.reportError(result.failure()); IrisLogging.reportError(result.failure());
result.failure().printStackTrace();
} }
} }
@@ -1043,13 +1043,9 @@ public final class ModdedCommandMessages {
"iris.modded.moddedstudiocommands.creating_project_from_template", "iris.modded.moddedstudiocommands.creating_project_from_template",
"Creating project '" + "{name}" + "' from template '" + "{template}" + "'..." "Creating project '" + "{name}" + "' from template '" + "{template}" + "'..."
); );
public static final TextKey MODDED_STUDIO_COMMANDS_TEMPLATE_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS = TextKey.of( public static final TextKey MODDED_STUDIO_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART = TextKey.of(
"iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions", "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart",
"Template '" + "{template}" + "' is not installed; downloading IrisDimensions/" + "{template2}" + "..." "Required pack '" + "{pack}" + "' is not installed. Install it with /iris download, then restart."
);
public static final TextKey MODDED_STUDIO_COMMANDS_TEMPLATE_COULD_NOT_BE_DOWNLOADED_INSTALL_PACK_WITH_DIMENSIONS_JSON = TextKey.of(
"iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json",
"Template '" + "{template}" + "' could not be downloaded; install a pack with dimensions/" + "{template2}" + ".json first."
); );
public static final TextKey MODDED_STUDIO_COMMANDS_CREATED_PROJECT_AT = TextKey.of( public static final TextKey MODDED_STUDIO_COMMANDS_CREATED_PROJECT_AT = TextKey.of(
"iris.modded.moddedstudiocommands.created_project_at", "iris.modded.moddedstudiocommands.created_project_at",
@@ -1095,13 +1091,9 @@ public final class ModdedCommandMessages {
"iris.modded.moddedstudiocommands.region_sampling_failed", "iris.modded.moddedstudiocommands.region_sampling_failed",
"Region sampling failed: " + "{value}" "Region sampling failed: " + "{value}"
); );
public static final TextKey MODDED_WORLD_COMMANDS_PACK_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS = TextKey.of( public static final TextKey MODDED_WORLD_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART = TextKey.of(
"iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions", "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart",
"Pack '" + "{pack}" + "' is not installed; downloading IrisDimensions/" + "{pack2}" + "..." "Required pack '" + "{pack}" + "' is not installed. Install it with /iris download, then restart."
);
public static final TextKey MODDED_WORLD_COMMANDS_PACK_COULD_NOT_BE_DOWNLOADED_CHECK_NAME_INSTALL_IT_WITH = TextKey.of(
"iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with",
"Pack '" + "{pack}" + "' could not be downloaded; check the name or install it with /iris download " + "{pack2}" + "."
); );
public static final TextKey MODDED_WORLD_COMMANDS_FAILED_INJECT_IRIS_WORLD = TextKey.of( public static final TextKey MODDED_WORLD_COMMANDS_FAILED_INJECT_IRIS_WORLD = TextKey.of(
"iris.modded.moddedworldcommands.failed_inject_iris_world", "iris.modded.moddedworldcommands.failed_inject_iris_world",
@@ -1139,14 +1131,6 @@ public final class ModdedCommandMessages {
"iris.modded.moddedworldcommands.invalid_seed_use_number_random", "iris.modded.moddedworldcommands.invalid_seed_use_number_random",
"Invalid seed '" + "{seedRaw}" + "'. Use a number or 'random'." "Invalid seed '" + "{seedRaw}" + "'. Use a number or 'random'."
); );
public static final TextKey MODDED_WORLD_COMMANDS_PACK_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS_2 = TextKey.of(
"iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2",
"Pack '" + "{pack}" + "' is not installed; downloading IrisDimensions/" + "{pack2}" + "..."
);
public static final TextKey MODDED_WORLD_COMMANDS_PACK_COULD_NOT_BE_DOWNLOADED_CHECK_NAME_INSTALL_IT_WITH_2 = TextKey.of(
"iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2",
"Pack '" + "{pack}" + "' could not be downloaded; check the name or install it with /iris download " + "{pack2}" + "."
);
public static final TextKey MODDED_WORLD_COMMANDS_PACK_IS_NOT_READY_YET_STILL_LOADING_VALIDATING_TRY_COMMAND = TextKey.of( public static final TextKey MODDED_WORLD_COMMANDS_PACK_IS_NOT_READY_YET_STILL_LOADING_VALIDATING_TRY_COMMAND = TextKey.of(
"iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command",
"Pack '" + "{pack}" + "' is not ready yet (still loading or validating). Try the command again in a moment." "Pack '" + "{pack}" + "' is not ready yet (still loading or validating). Try the command again in a moment."
@@ -1512,8 +1496,7 @@ public final class ModdedCommandMessages {
MODDED_STUDIO_COMMANDS_INVALID_PROJECT_NAME_ALLOWED_Z_0_9, MODDED_STUDIO_COMMANDS_INVALID_PROJECT_NAME_ALLOWED_Z_0_9,
MODDED_STUDIO_COMMANDS_PACK_ALREADY_EXISTS_AT, MODDED_STUDIO_COMMANDS_PACK_ALREADY_EXISTS_AT,
MODDED_STUDIO_COMMANDS_CREATING_PROJECT_FROM_TEMPLATE, MODDED_STUDIO_COMMANDS_CREATING_PROJECT_FROM_TEMPLATE,
MODDED_STUDIO_COMMANDS_TEMPLATE_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS, MODDED_STUDIO_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART,
MODDED_STUDIO_COMMANDS_TEMPLATE_COULD_NOT_BE_DOWNLOADED_INSTALL_PACK_WITH_DIMENSIONS_JSON,
MODDED_STUDIO_COMMANDS_CREATED_PROJECT_AT, MODDED_STUDIO_COMMANDS_CREATED_PROJECT_AT,
MODDED_STUDIO_COMMANDS_EDIT_DIMENSIONS_JSON_REST_PACK_VSCODE_WORKSPACE_WITH_JSON_SCHEMA, MODDED_STUDIO_COMMANDS_EDIT_DIMENSIONS_JSON_REST_PACK_VSCODE_WORKSPACE_WITH_JSON_SCHEMA,
MODDED_STUDIO_COMMANDS_PROJECT_CREATION_FAILED, MODDED_STUDIO_COMMANDS_PROJECT_CREATION_FAILED,
@@ -1525,8 +1508,7 @@ public final class ModdedCommandMessages {
MODDED_STUDIO_COMMANDS_SAMPLING_REGION_DISTRIBUTION_X_CHUNKS_AROUND_YOU, MODDED_STUDIO_COMMANDS_SAMPLING_REGION_DISTRIBUTION_X_CHUNKS_AROUND_YOU,
MODDED_STUDIO_COMMANDS_RARITY, MODDED_STUDIO_COMMANDS_RARITY,
MODDED_STUDIO_COMMANDS_REGION_SAMPLING_FAILED, MODDED_STUDIO_COMMANDS_REGION_SAMPLING_FAILED,
MODDED_WORLD_COMMANDS_PACK_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS, MODDED_WORLD_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART,
MODDED_WORLD_COMMANDS_PACK_COULD_NOT_BE_DOWNLOADED_CHECK_NAME_INSTALL_IT_WITH,
MODDED_WORLD_COMMANDS_FAILED_INJECT_IRIS_WORLD, MODDED_WORLD_COMMANDS_FAILED_INJECT_IRIS_WORLD,
MODDED_WORLD_COMMANDS_CREATED_IRIS_WORLD_FROM_PACK_DIMENSION_SEED, MODDED_WORLD_COMMANDS_CREATED_IRIS_WORLD_FROM_PACK_DIMENSION_SEED,
MODDED_WORLD_COMMANDS_IT_IS_LIVE_NOW_RE_INJECTED_ON_EVERY_STARTUP_TELEPORT, MODDED_WORLD_COMMANDS_IT_IS_LIVE_NOW_RE_INJECTED_ON_EVERY_STARTUP_TELEPORT,
@@ -1536,8 +1518,6 @@ public final class ModdedCommandMessages {
MODDED_WORLD_COMMANDS_INSTEAD_IS_NOW_CONFIGURED_PRIMARY_WORLD_PLAYERS_VANILLA_OVERWORLD_ARE, MODDED_WORLD_COMMANDS_INSTEAD_IS_NOW_CONFIGURED_PRIMARY_WORLD_PLAYERS_VANILLA_OVERWORLD_ARE,
MODDED_WORLD_COMMANDS_IRIS_MAIN_WORLD_OVERRIDE_CLEARED_OVERWORLD_KEEPS_ITS_CURRENT_GENERATOR, MODDED_WORLD_COMMANDS_IRIS_MAIN_WORLD_OVERRIDE_CLEARED_OVERWORLD_KEEPS_ITS_CURRENT_GENERATOR,
MODDED_WORLD_COMMANDS_INVALID_SEED_USE_NUMBER_RANDOM, MODDED_WORLD_COMMANDS_INVALID_SEED_USE_NUMBER_RANDOM,
MODDED_WORLD_COMMANDS_PACK_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS_2,
MODDED_WORLD_COMMANDS_PACK_COULD_NOT_BE_DOWNLOADED_CHECK_NAME_INSTALL_IT_WITH_2,
MODDED_WORLD_COMMANDS_PACK_IS_NOT_READY_YET_STILL_LOADING_VALIDATING_TRY_COMMAND, MODDED_WORLD_COMMANDS_PACK_IS_NOT_READY_YET_STILL_LOADING_VALIDATING_TRY_COMMAND,
MODDED_WORLD_COMMANDS_FAILED_WRITE_SERVER_PROPERTIES_CHECK_FILE_PERMISSIONS_SET_LEVEL_TYPE, MODDED_WORLD_COMMANDS_FAILED_WRITE_SERVER_PROPERTIES_CHECK_FILE_PERMISSIONS_SET_LEVEL_TYPE,
MODDED_WORLD_COMMANDS_IRIS_MAIN_WORLD_SET_PRESET_SEED, MODDED_WORLD_COMMANDS_IRIS_MAIN_WORLD_SET_PRESET_SEED,
@@ -36,11 +36,11 @@ public final class ModdedHelpMessages {
); );
public static final TextKey COMMAND_DEBUG_TOGGLE_IRIS_DEBUG_LOGGING_AND_SAVE_SETTINGS_JSON = TextKey.of( public static final TextKey COMMAND_DEBUG_TOGGLE_IRIS_DEBUG_LOGGING_AND_SAVE_SETTINGS_JSON = TextKey.of(
"iris.modded.help.entry.command.debug", "iris.modded.help.entry.command.debug",
"Toggle Iris debug logging and save settings.json" "Toggle Iris debug logging and save iris.json"
); );
public static final TextKey COMMAND_RELOAD_RELOAD_SETTINGS_JSON_ALSO_HOTLOADED_AUTOMATICALLY_EVERY_3S = TextKey.of( public static final TextKey COMMAND_RELOAD_RELOAD_SETTINGS_JSON_ALSO_HOTLOADED_AUTOMATICALLY_EVERY_3S = TextKey.of(
"iris.modded.help.entry.command.reload", "iris.modded.help.entry.command.reload",
"Reload settings.json (also hotloaded automatically every 3s)" "Reload iris.json (also hotloaded automatically every 3s)"
); );
public static final TextKey COMMAND_DOWNLOAD_DOWNLOAD_A_PACK_PROJECT = TextKey.of( public static final TextKey COMMAND_DOWNLOAD_DOWNLOAD_A_PACK_PROJECT = TextKey.of(
"iris.modded.help.entry.command.download", "iris.modded.help.entry.command.download",
@@ -25,16 +25,10 @@ public final class RuntimeProgressMessages {
public static final TextKey STUDIO_STAGE_CREATE_WORLD = TextKey.of("iris.runtime.studio.stage.create_world", "Creating world"); public static final TextKey STUDIO_STAGE_CREATE_WORLD = TextKey.of("iris.runtime.studio.stage.create_world", "Creating world");
public static final TextKey STUDIO_STAGE_APPLY_WORLD_RULES = TextKey.of("iris.runtime.studio.stage.apply_world_rules", "Applying world rules"); public static final TextKey STUDIO_STAGE_APPLY_WORLD_RULES = TextKey.of("iris.runtime.studio.stage.apply_world_rules", "Applying world rules");
public static final TextKey STUDIO_STAGE_PREPARE_GENERATOR = TextKey.of("iris.runtime.studio.stage.prepare_generator", "Preparing generator"); public static final TextKey STUDIO_STAGE_PREPARE_GENERATOR = TextKey.of("iris.runtime.studio.stage.prepare_generator", "Preparing generator");
public static final TextKey STUDIO_STAGE_REQUEST_ENTRY_CHUNK = TextKey.of("iris.runtime.studio.stage.request_entry_chunk", "Loading entry chunk");
public static final TextKey STUDIO_STAGE_RESOLVE_SAFE_ENTRY = TextKey.of("iris.runtime.studio.stage.resolve_safe_entry", "Finding safe spawn");
public static final TextKey STUDIO_STAGE_TELEPORT_PLAYER = TextKey.of("iris.runtime.studio.stage.teleport_player", "Teleporting"); public static final TextKey STUDIO_STAGE_TELEPORT_PLAYER = TextKey.of("iris.runtime.studio.stage.teleport_player", "Teleporting");
public static final TextKey STUDIO_STAGE_FINALIZE_OPEN = TextKey.of("iris.runtime.studio.stage.finalize_open", "Finalizing"); public static final TextKey STUDIO_STAGE_FINALIZE_OPEN = TextKey.of("iris.runtime.studio.stage.finalize_open", "Finalizing");
public static final TextKey STUDIO_STAGE_CLEANUP = TextKey.of("iris.runtime.studio.stage.cleanup", "Cleaning up"); public static final TextKey STUDIO_STAGE_CLEANUP = TextKey.of("iris.runtime.studio.stage.cleanup", "Cleaning up");
public static final TextKey WORLD_CREATE_TELEPORT_FAILED = TextKey.of("iris.runtime.world_create.teleport_failed", C.YELLOW + "The world was created, but automatic teleport failed. Try /iris teleport world={world}"); public static final TextKey WORLD_CREATE_TELEPORT_FAILED = TextKey.of("iris.runtime.world_create.teleport_failed", C.YELLOW + "The world was created, but automatic teleport failed. Try /iris teleport world={world}");
public static final TextKey WORLD_CREATE_BOSSBAR_WORKING = TextKey.of("iris.runtime.world_create.bossbar.working", C.GOLD + "World " + C.AQUA + "{world}" + C.GRAY + " | " + C.WHITE + "Starting");
public static final TextKey WORLD_CREATE_BOSSBAR_PROGRESS = TextKey.of("iris.runtime.world_create.bossbar.progress", C.GOLD + "World " + C.AQUA + "{world}" + C.GRAY + " | " + C.YELLOW + "{percent}% " + C.WHITE + "{stage}");
public static final TextKey WORLD_CREATE_BOSSBAR_FAILED = TextKey.of("iris.runtime.world_create.bossbar.failed", C.GOLD + "World " + C.AQUA + "{world}" + C.GRAY + " | " + C.RED + "FAILED " + C.DARK_GRAY + "{percent}%");
public static final TextKey WORLD_CREATE_BOSSBAR_READY = TextKey.of("iris.runtime.world_create.bossbar.ready", C.GOLD + "World " + C.AQUA + "{world}" + C.GRAY + " | " + C.GREEN + "READY 100%");
public static final TextKey WORLD_CREATE_LIFECYCLE_ACTION = TextKey.of("iris.runtime.world_create.lifecycle.action", "{bar}" + C.GRAY + " " + C.YELLOW + "{percent}%" + C.GRAY + " | " + C.WHITE + "{stage}{detail}" + C.DARK_GRAY + " {elapsed}"); public static final TextKey WORLD_CREATE_LIFECYCLE_ACTION = TextKey.of("iris.runtime.world_create.lifecycle.action", "{bar}" + C.GRAY + " " + C.YELLOW + "{percent}%" + C.GRAY + " | " + C.WHITE + "{stage}{detail}" + C.DARK_GRAY + " {elapsed}");
public static final TextKey WORLD_CREATE_LIFECYCLE_ACTION_FAILED = TextKey.of("iris.runtime.world_create.lifecycle.action.failed", "{bar}" + C.GRAY + " " + C.RED + "FAILED" + C.GRAY + " | " + C.WHITE + "{stage}{detail}" + C.DARK_GRAY + " {elapsed}"); public static final TextKey WORLD_CREATE_LIFECYCLE_ACTION_FAILED = TextKey.of("iris.runtime.world_create.lifecycle.action.failed", "{bar}" + C.GRAY + " " + C.RED + "FAILED" + C.GRAY + " | " + C.WHITE + "{stage}{detail}" + C.DARK_GRAY + " {elapsed}");
public static final TextKey WORLD_CREATE_LIFECYCLE_ACTION_READY = TextKey.of("iris.runtime.world_create.lifecycle.action.ready", "{bar}" + C.GRAY + " " + C.GREEN + "100%" + C.GRAY + " | " + C.GREEN + "World ready" + C.DARK_GRAY + " {elapsed}"); public static final TextKey WORLD_CREATE_LIFECYCLE_ACTION_READY = TextKey.of("iris.runtime.world_create.lifecycle.action.ready", "{bar}" + C.GRAY + " " + C.GREEN + "100%" + C.GRAY + " | " + C.GREEN + "World ready" + C.DARK_GRAY + " {elapsed}");
@@ -49,7 +43,7 @@ public final class RuntimeProgressMessages {
public static final TextKey WORLD_CREATE_STAGE_PREPARE_GENERATOR = TextKey.of("iris.runtime.world_create.stage.prepare_generator", "Preparing generator"); public static final TextKey WORLD_CREATE_STAGE_PREPARE_GENERATOR = TextKey.of("iris.runtime.world_create.stage.prepare_generator", "Preparing generator");
public static final TextKey WORLD_CREATE_STAGE_CREATE_WORLD = TextKey.of("iris.runtime.world_create.stage.create_world", "Generating spawn"); public static final TextKey WORLD_CREATE_STAGE_CREATE_WORLD = TextKey.of("iris.runtime.world_create.stage.create_world", "Generating spawn");
public static final TextKey WORLD_CREATE_STAGE_REGISTER_WORLD = TextKey.of("iris.runtime.world_create.stage.register_world", "Registering world"); public static final TextKey WORLD_CREATE_STAGE_REGISTER_WORLD = TextKey.of("iris.runtime.world_create.stage.register_world", "Registering world");
public static final TextKey WORLD_CREATE_STAGE_TELEPORT_PLAYER = TextKey.of("iris.runtime.world_create.stage.teleport_player", "Finding safe entry"); public static final TextKey WORLD_CREATE_STAGE_TELEPORT_PLAYER = TextKey.of("iris.runtime.world_create.stage.teleport_player", "Entering world");
public static final TextKey WORLD_CREATE_STAGE_PREGENERATE = TextKey.of("iris.runtime.world_create.stage.pregenerate", "Pregenerating"); public static final TextKey WORLD_CREATE_STAGE_PREGENERATE = TextKey.of("iris.runtime.world_create.stage.pregenerate", "Pregenerating");
public static final TextKey WORLD_CREATE_STAGE_FINALIZE = TextKey.of("iris.runtime.world_create.stage.finalize", "Finalizing"); public static final TextKey WORLD_CREATE_STAGE_FINALIZE = TextKey.of("iris.runtime.world_create.stage.finalize", "Finalizing");
public static final TextKey WORLD_CREATE_STAGE_COMPLETE = TextKey.of("iris.runtime.world_create.stage.complete", "World ready"); public static final TextKey WORLD_CREATE_STAGE_COMPLETE = TextKey.of("iris.runtime.world_create.stage.complete", "World ready");
@@ -124,16 +118,10 @@ public final class RuntimeProgressMessages {
STUDIO_STAGE_CREATE_WORLD, STUDIO_STAGE_CREATE_WORLD,
STUDIO_STAGE_APPLY_WORLD_RULES, STUDIO_STAGE_APPLY_WORLD_RULES,
STUDIO_STAGE_PREPARE_GENERATOR, STUDIO_STAGE_PREPARE_GENERATOR,
STUDIO_STAGE_REQUEST_ENTRY_CHUNK,
STUDIO_STAGE_RESOLVE_SAFE_ENTRY,
STUDIO_STAGE_TELEPORT_PLAYER, STUDIO_STAGE_TELEPORT_PLAYER,
STUDIO_STAGE_FINALIZE_OPEN, STUDIO_STAGE_FINALIZE_OPEN,
STUDIO_STAGE_CLEANUP, STUDIO_STAGE_CLEANUP,
WORLD_CREATE_TELEPORT_FAILED, WORLD_CREATE_TELEPORT_FAILED,
WORLD_CREATE_BOSSBAR_WORKING,
WORLD_CREATE_BOSSBAR_PROGRESS,
WORLD_CREATE_BOSSBAR_FAILED,
WORLD_CREATE_BOSSBAR_READY,
WORLD_CREATE_LIFECYCLE_ACTION, WORLD_CREATE_LIFECYCLE_ACTION,
WORLD_CREATE_LIFECYCLE_ACTION_FAILED, WORLD_CREATE_LIFECYCLE_ACTION_FAILED,
WORLD_CREATE_LIFECYCLE_ACTION_READY, WORLD_CREATE_LIFECYCLE_ACTION_READY,
@@ -70,7 +70,7 @@ public class INMS {
private static INMSBinding bind() { private static INMSBinding bind() {
boolean disableNms = IrisSettings.get().getGeneral().isDisableNMS(); boolean disableNms = IrisSettings.get().getGeneral().isDisableNMS();
if (disableNms) { if (disableNms) {
IrisLogging.info("Craftbukkit BUKKIT <-> " + NMSBinding1X.class.getSimpleName() + " Successfully Bound"); IrisLogging.debug("Craftbukkit BUKKIT <-> " + NMSBinding1X.class.getSimpleName() + " Successfully Bound");
IrisLogging.warn("NMS support is disabled. Iris world creation is unavailable until general.disableNMS=false."); IrisLogging.warn("NMS support is disabled. Iris world creation is unavailable until general.disableNMS=false.");
return new NMSBinding1X(); return new NMSBinding1X();
} }
@@ -87,7 +87,6 @@ public class INMS {
} catch (Throwable e) { } catch (Throwable e) {
IrisLogging.reportError(e); IrisLogging.reportError(e);
IrisLogging.error("Failed to determine server minecraft version!"); IrisLogging.error("Failed to determine server minecraft version!");
e.printStackTrace();
if (e instanceof IllegalStateException illegalStateException) { if (e instanceof IllegalStateException illegalStateException) {
throw illegalStateException; throw illegalStateException;
} }
@@ -96,19 +95,18 @@ public class INMS {
} }
private static INMSBinding bindExact(String code) { private static INMSBinding bindExact(String code) {
IrisLogging.info("Locating exact NMS Binding for " + code); IrisLogging.debug("Locating exact NMS Binding for " + code);
try { try {
Class<?> clazz = Class.forName("art.arcane.iris.core.nms." + code + ".NMSBinding"); Class<?> clazz = Class.forName("art.arcane.iris.core.nms." + code + ".NMSBinding");
Object candidate = clazz.getConstructor().newInstance(); Object candidate = clazz.getConstructor().newInstance();
if (candidate instanceof INMSBinding binding) { if (candidate instanceof INMSBinding binding) {
IrisLogging.info("Craftbukkit " + code + " <-> " + candidate.getClass().getSimpleName() + " Successfully Bound"); IrisLogging.debug("Craftbukkit " + code + " <-> " + candidate.getClass().getSimpleName() + " Successfully Bound");
return binding; return binding;
} }
throw new IllegalStateException("Exact NMS binding class for " + code throw new IllegalStateException("Exact NMS binding class for " + code
+ " does not implement " + INMSBinding.class.getName()); + " does not implement " + INMSBinding.class.getName());
} catch (Throwable e) { } catch (Throwable e) {
IrisLogging.reportError(e); IrisLogging.reportError(e);
e.printStackTrace();
if (e instanceof IllegalStateException illegalStateException) { if (e instanceof IllegalStateException illegalStateException) {
throw illegalStateException; throw illegalStateException;
} }
@@ -54,8 +54,6 @@ import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.awt.Color; import java.awt.Color;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -272,14 +270,6 @@ public interface INMSBinding {
default void uninjectBukkit() { default void uninjectBukkit() {
} }
default void writeCurrentPaperWorldData(
Path sourceWorldDirectory,
Path targetWorldDirectory,
long seed
) throws IOException {
throw new UnsupportedOperationException("The active NMS binding does not support current Paper world data staging.");
}
default boolean awaitServerShutdownBoundary(long timeout, TimeUnit unit) { default boolean awaitServerShutdownBoundary(long timeout, TimeUnit unit) {
return true; return true;
} }
@@ -251,7 +251,7 @@ public final class ContentKeyValidator {
/** /**
* Whether unresolved pack content keys are blocking errors instead of warnings. Enabled by * Whether unresolved pack content keys are blocking errors instead of warnings. Enabled by
* {@code -Diris.strictContent} or {@code general.strictContentKeys} in settings.json; the system property wins. * {@code -Diris.strictContent} or {@code general.strictContentKeys} in iris.json; the system property wins.
*/ */
public static boolean strictContent() { public static boolean strictContent() {
String property = System.getProperty(STRICT_PROPERTY); String property = System.getProperty(STRICT_PROPERTY);
@@ -223,7 +223,6 @@ public class IrisPack {
IO.writeAll(ws, generateWorkspaceConfig()); IO.writeAll(ws, generateWorkspaceConfig());
} catch (IOException e1) { } catch (IOException e1) {
IrisLogging.reportError(e1); IrisLogging.reportError(e1);
e1.printStackTrace();
} }
} }
@@ -20,6 +20,7 @@ package art.arcane.iris.core.pack;
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.spi.IrisLogging;
import art.arcane.iris.spi.IrisPlatforms; import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.core.service.StudioSVC; import art.arcane.iris.core.service.StudioSVC;
import art.arcane.volmlib.util.format.Form; import art.arcane.volmlib.util.format.Form;
@@ -126,7 +127,7 @@ public class IrisPackRepository {
try { try {
FileUtils.copyDirectory(work.listFiles()[0], pack); FileUtils.copyDirectory(work.listFiles()[0], pack);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); IrisLogging.reportError("Failed to install Iris pack into " + pack + ".", e);
} }
})).execute(sender, whenComplete); })).execute(sender, whenComplete);
} else { } else {
@@ -94,7 +94,15 @@ final class PackRiverValidator {
validateWater(path + ".water", water, errors); validateWater(path + ".water", water, errors);
} }
if (biomes != null) { if (biomes != null) {
validateBiomePools(packFolder, path + ".biomes", biomes, false, errors, warnings); validateBiomePools(
packFolder,
path + ".biomes",
biomes,
false,
usesOverworldNativeStructureRoles(context),
errors,
warnings
);
} }
boolean sinkholeTerminal = terrain != null boolean sinkholeTerminal = terrain != null
&& "SINKHOLE_GROTTO".equals(stringValue(terrain, "terminalMode", "DRY_CHANNEL")); && "SINKHOLE_GROTTO".equals(stringValue(terrain, "terminalMode", "DRY_CHANNEL"));
@@ -108,7 +116,7 @@ final class PackRiverValidator {
if (Double.isFinite(meanderStrength) && meanderStrength > cellSize) { if (Double.isFinite(meanderStrength) && meanderStrength > cellSize) {
warnings.add(path + ".terrain.meanderStrength exceeds topology.cellSize; reaches may require large cache halos."); warnings.add(path + ".terrain.meanderStrength exceeds topology.cellSize; reaches may require large cache halos.");
} }
validateTopologyComplexity(path, topology, terrain, errors); validateTopologyComplexity(packFolder, path, topology, terrain, errors);
} }
if (sinkholeTerminal && caves != null) { if (sinkholeTerminal && caves != null) {
validateSinkholeCapability( validateSinkholeCapability(
@@ -130,8 +138,14 @@ final class PackRiverValidator {
PackJsonFieldChecks.validateOptionalIntegerRange(path, topology, "minimumSourcesPerTile", 0, 64, errors); PackJsonFieldChecks.validateOptionalIntegerRange(path, topology, "minimumSourcesPerTile", 0, 64, errors);
PackJsonFieldChecks.validateOptionalIntegerRange(path, topology, "sinkSearchReaches", 0, 7, errors); PackJsonFieldChecks.validateOptionalIntegerRange(path, topology, "sinkSearchReaches", 0, 7, errors);
PackJsonFieldChecks.validateOptionalIntegerRange(path, topology, "routingBasinCells", 8, 256, errors); PackJsonFieldChecks.validateOptionalIntegerRange(path, topology, "routingBasinCells", 8, 256, errors);
PackJsonFieldChecks.validateOptionalIntegerRange(path, topology, "routingDeviationScaleCells", 8, 256, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, topology, "routingDeviationStrengthCells", 0D, 32D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, topology, "routingPlateauHeight", 1D, 64D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, topology, "routingPlateauHeight", 1D, 64D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, topology, "routingNoiseWeight", 0D, 1024D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, topology, "routingNoiseWeight", 0D, 1024D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, topology, "flowAlignmentWeight", 0D, 1024D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, topology, "confluenceWeight", 0D, 1024D, errors);
PackJsonFieldChecks.validateOptionalIntegerRange(path, topology, "branchSoftCap", 1, 8, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, topology, "branchChildShrinkFactor", 0D, 1D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, topology, "terrainHeightWeight", 0D, 16D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, topology, "terrainHeightWeight", 0D, 16D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, topology, "terrainSlopeWeight", 0D, 16D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, topology, "terrainSlopeWeight", 0D, 16D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, topology, "oceanAttraction", 0D, 16D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, topology, "oceanAttraction", 0D, 16D, errors);
@@ -155,6 +169,7 @@ final class PackRiverValidator {
validateStyledRange(packFolder, terrain, "channelWidth", path, 1D, 2048D, errors, warnings); validateStyledRange(packFolder, terrain, "channelWidth", path, 1D, 2048D, errors, warnings);
validateStyledRange(packFolder, terrain, "bankWidth", path, 0D, 2048D, errors, warnings); validateStyledRange(packFolder, terrain, "bankWidth", path, 0D, 2048D, errors, warnings);
validateStyledRange(packFolder, terrain, "depth", path, 1D, 512D, errors, warnings); validateStyledRange(packFolder, terrain, "depth", path, 1D, 512D, errors, warnings);
validateStyledRange(packFolder, terrain, "tunnelWidthMultiplier", path, 1D, 8D, errors, warnings);
PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "maxChannelWidth", 1D, 2048D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "maxChannelWidth", 1D, 2048D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "maxBankWidth", 0D, 2048D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "maxBankWidth", 0D, 2048D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "maxDepth", 1D, 512D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "maxDepth", 1D, 512D, errors);
@@ -162,6 +177,9 @@ final class PackRiverValidator {
PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "orderDepthFactor", 0D, 8D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "orderDepthFactor", 0D, 8D, errors);
PackJsonFieldChecks.validateOptionalIntegerRange(path, terrain, "maxIncision", 0, 512, errors); PackJsonFieldChecks.validateOptionalIntegerRange(path, terrain, "maxIncision", 0, 512, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "bankExponent", 0.125D, 16D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "bankExponent", 0.125D, 16D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "tunnelMouthBlend", 0D, 16D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "tunnelFloorVariation", 0D, 8D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "tunnelRoofVariation", 0D, 16D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "meanderStrength", 0D, 1024D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "meanderStrength", 0D, 1024D, errors);
PackJsonFieldChecks.validateOptionalIntegerRange(path, terrain, "meanderSubdivisions", 1, 64, errors); PackJsonFieldChecks.validateOptionalIntegerRange(path, terrain, "meanderSubdivisions", 1, 64, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "bedRoughness", 0D, 8D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "bedRoughness", 0D, 8D, errors);
@@ -169,8 +187,32 @@ final class PackRiverValidator {
PackJsonFieldChecks.validateOptionalIntegerRange(path, terrain, "terminalTaper", 8, 1024, errors); PackJsonFieldChecks.validateOptionalIntegerRange(path, terrain, "terminalTaper", 8, 1024, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "dryContinuationChance", 0D, 1D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, terrain, "dryContinuationChance", 0D, 1D, errors);
validateNoiseChance(packFolder, terrain, "incision", path, errors); validateNoiseChance(packFolder, terrain, "incision", path, errors);
validateStyle(packFolder, terrain, "tunnelFloorStyle", path, errors);
validateStyle(packFolder, terrain, "tunnelRoofStyle", path, errors);
validateStyle(packFolder, terrain, "meanderStyle", path, errors); validateStyle(packFolder, terrain, "meanderStyle", path, errors);
validateStyle(packFolder, terrain, "bedRoughnessStyle", path, errors); validateStyle(packFolder, terrain, "bedRoughnessStyle", path, errors);
double maximumChannelWidth = doubleValue(terrain, "maxChannelWidth", 10D);
double maximumTunnelWidthMultiplier = styledRangeMaximum(
packFolder,
terrain,
"tunnelWidthMultiplier",
path,
1D
);
double tunnelMouthBlend = doubleValue(terrain, "tunnelMouthBlend", 2D);
if (Double.isFinite(maximumChannelWidth) && maximumChannelWidth >= 1D && maximumChannelWidth <= 2048D
&& Double.isFinite(maximumTunnelWidthMultiplier)
&& maximumTunnelWidthMultiplier >= 1D && maximumTunnelWidthMultiplier <= 8D
&& Double.isFinite(tunnelMouthBlend) && tunnelMouthBlend >= 0D && tunnelMouthBlend <= 16D) {
String violation = RiverTopologyComplexity.tunnelPlanViolation(
maximumChannelWidth,
maximumTunnelWidthMultiplier,
tunnelMouthBlend
);
if (violation != null) {
errors.add(path + " exceeds the safe derived hydrology budget. " + violation);
}
}
} }
private static void validateWater(String path, JSONObject water, List<String> errors) { private static void validateWater(String path, JSONObject water, List<String> errors) {
@@ -188,6 +230,7 @@ final class PackRiverValidator {
} }
private static void validateTopologyComplexity( private static void validateTopologyComplexity(
File packFolder,
String path, String path,
JSONObject topology, JSONObject topology,
JSONObject terrain, JSONObject terrain,
@@ -201,6 +244,14 @@ final class PackRiverValidator {
int meanderSubdivisions = integerValue(terrain, "meanderSubdivisions", 8); int meanderSubdivisions = integerValue(terrain, "meanderSubdivisions", 8);
double maximumChannelWidth = doubleValue(terrain, "maxChannelWidth", 10D); double maximumChannelWidth = doubleValue(terrain, "maxChannelWidth", 10D);
double maximumBankWidth = doubleValue(terrain, "maxBankWidth", 4D); double maximumBankWidth = doubleValue(terrain, "maxBankWidth", 4D);
double maximumTunnelWidthMultiplier = styledRangeMaximum(
packFolder,
terrain,
"tunnelWidthMultiplier",
path,
1D
);
double tunnelMouthBlend = doubleValue(terrain, "tunnelMouthBlend", 2D);
if (cellSize < 64 || cellSize > 4096 if (cellSize < 64 || cellSize > 4096
|| tileCells < 1 || tileCells > 64 || tileCells < 1 || tileCells > 64
|| !Double.isFinite(siteJitter) || siteJitter < 0D || siteJitter > 0.49D || !Double.isFinite(siteJitter) || siteJitter < 0D || siteJitter > 0.49D
@@ -208,10 +259,16 @@ final class PackRiverValidator {
|| !Double.isFinite(meanderStrength) || meanderStrength < 0D || meanderStrength > 1024D || !Double.isFinite(meanderStrength) || meanderStrength < 0D || meanderStrength > 1024D
|| meanderSubdivisions < 1 || meanderSubdivisions > 64 || meanderSubdivisions < 1 || meanderSubdivisions > 64
|| !Double.isFinite(maximumChannelWidth) || maximumChannelWidth < 1D || maximumChannelWidth > 2048D || !Double.isFinite(maximumChannelWidth) || maximumChannelWidth < 1D || maximumChannelWidth > 2048D
|| !Double.isFinite(maximumBankWidth) || maximumBankWidth < 0D || maximumBankWidth > 2048D) { || !Double.isFinite(maximumBankWidth) || maximumBankWidth < 0D || maximumBankWidth > 2048D
|| !Double.isFinite(maximumTunnelWidthMultiplier)
|| maximumTunnelWidthMultiplier < 1D || maximumTunnelWidthMultiplier > 8D
|| !Double.isFinite(tunnelMouthBlend) || tunnelMouthBlend < 0D || tunnelMouthBlend > 16D) {
return; return;
} }
double maximumReachRadius = maximumChannelWidth * 0.5D + maximumBankWidth; double maximumSurfaceRadius = maximumChannelWidth * 0.5D + maximumBankWidth;
double maximumTunnelRadius = maximumChannelWidth * 0.5D * maximumTunnelWidthMultiplier
+ tunnelMouthBlend;
double maximumReachRadius = Math.max(maximumSurfaceRadius, maximumTunnelRadius);
RiverTopologyComplexity.Estimate estimate = RiverTopologyComplexity.estimate( RiverTopologyComplexity.Estimate estimate = RiverTopologyComplexity.estimate(
cellSize, cellSize,
tileCells, tileCells,
@@ -239,6 +296,7 @@ final class PackRiverValidator {
PackJsonFieldChecks.validateOptionalIntegerRange(path, caves, "grottoHorizontalRadius", 2, 128, errors); PackJsonFieldChecks.validateOptionalIntegerRange(path, caves, "grottoHorizontalRadius", 2, 128, errors);
PackJsonFieldChecks.validateOptionalIntegerRange(path, caves, "grottoVerticalRadius", 2, 128, errors); PackJsonFieldChecks.validateOptionalIntegerRange(path, caves, "grottoVerticalRadius", 2, 128, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, caves, "grottoWarpStrength", 0D, 32D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, caves, "grottoWarpStrength", 0D, 32D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, caves, "parentBiomeInheritance", 0D, 1D, errors);
PackJsonFieldChecks.validateOptionalIntegerRange(path, caves, "maxFloodRadius", 4, 256, errors); PackJsonFieldChecks.validateOptionalIntegerRange(path, caves, "maxFloodRadius", 4, 256, errors);
PackJsonFieldChecks.validateOptionalIntegerRange(path, caves, "maxFloodDepth", 4, 256, errors); PackJsonFieldChecks.validateOptionalIntegerRange(path, caves, "maxFloodDepth", 4, 256, errors);
PackJsonFieldChecks.validateOptionalIntegerRange(path, caves, "maxFloodVolume", 64, 1048576, errors); PackJsonFieldChecks.validateOptionalIntegerRange(path, caves, "maxFloodVolume", 64, 1048576, errors);
@@ -525,12 +583,18 @@ final class PackRiverValidator {
PackJsonFieldChecks.validateOptionalDoubleRange(path, override, "continuationChanceMultiplier", 0D, 16D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, override, "continuationChanceMultiplier", 0D, 16D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, override, "caveEntryMultiplier", 0D, 16D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, override, "caveEntryMultiplier", 0D, 16D, errors);
PackJsonFieldChecks.validateOptionalEnum(path, override, "terminalMode", TERMINAL_MODES, errors); PackJsonFieldChecks.validateOptionalEnum(path, override, "terminalMode", TERMINAL_MODES, errors);
validateBiomePool(packFolder, path, override, "channelBiomes", RiverBiomeRole.CHANNEL, true, errors, warnings); boolean validateSuitability = reachesOverworldNativeStructureRoles(
validateBiomePool(packFolder, path, override, "bankBiomes", RiverBiomeRole.BANK, true, errors, warnings); packFolder, resourceKey, resourceType, contexts);
validateBiomePool(packFolder, path, override, "mouthBiomes", RiverBiomeRole.MOUTH, true, errors, warnings); validateBiomePool(packFolder, path, override, "channelBiomes", RiverBiomeRole.CHANNEL, true,
validateBiomePool(packFolder, path, override, "dryBiomes", RiverBiomeRole.DRY, true, errors, warnings); validateSuitability, errors, warnings);
validateBiomePool(packFolder, path, override, "bankBiomes", RiverBiomeRole.BANK, true,
validateSuitability, errors, warnings);
validateBiomePool(packFolder, path, override, "mouthBiomes", RiverBiomeRole.MOUTH, true,
validateSuitability, errors, warnings);
validateBiomePool(packFolder, path, override, "dryBiomes", RiverBiomeRole.DRY, true,
validateSuitability, errors, warnings);
validateBiomePool(packFolder, path, override, "floodedCaveBiomes", RiverBiomeRole.FLOODED_CAVE, true, validateBiomePool(packFolder, path, override, "floodedCaveBiomes", RiverBiomeRole.FLOODED_CAVE, true,
errors, warnings); validateSuitability, errors, warnings);
if ("SINKHOLE_GROTTO".equals(stringValue(override, "terminalMode", null))) { if ("SINKHOLE_GROTTO".equals(stringValue(override, "terminalMode", null))) {
validateOverrideSinkhole( validateOverrideSinkhole(
packFolder, packFolder,
@@ -545,18 +609,24 @@ final class PackRiverValidator {
} }
private static void validateBiomePools(File packFolder, String path, JSONObject biomes, boolean allowNull, private static void validateBiomePools(File packFolder, String path, JSONObject biomes, boolean allowNull,
boolean validateSuitability,
List<String> errors, List<String> warnings) { List<String> errors, List<String> warnings) {
validateStyle(packFolder, biomes, "selectionStyle", path, errors); validateStyle(packFolder, biomes, "selectionStyle", path, errors);
validateBiomePool(packFolder, path, biomes, "channel", RiverBiomeRole.CHANNEL, allowNull, errors, warnings); validateBiomePool(packFolder, path, biomes, "channel", RiverBiomeRole.CHANNEL, allowNull,
validateBiomePool(packFolder, path, biomes, "bank", RiverBiomeRole.BANK, allowNull, errors, warnings); validateSuitability, errors, warnings);
validateBiomePool(packFolder, path, biomes, "mouth", RiverBiomeRole.MOUTH, allowNull, errors, warnings); validateBiomePool(packFolder, path, biomes, "bank", RiverBiomeRole.BANK, allowNull,
validateBiomePool(packFolder, path, biomes, "dry", RiverBiomeRole.DRY, allowNull, errors, warnings); validateSuitability, errors, warnings);
validateBiomePool(packFolder, path, biomes, "mouth", RiverBiomeRole.MOUTH, allowNull,
validateSuitability, errors, warnings);
validateBiomePool(packFolder, path, biomes, "dry", RiverBiomeRole.DRY, allowNull,
validateSuitability, errors, warnings);
validateBiomePool(packFolder, path, biomes, "floodedCave", RiverBiomeRole.FLOODED_CAVE, allowNull, validateBiomePool(packFolder, path, biomes, "floodedCave", RiverBiomeRole.FLOODED_CAVE, allowNull,
errors, warnings); validateSuitability, errors, warnings);
} }
private static void validateBiomePool(File packFolder, String path, JSONObject owner, String field, private static void validateBiomePool(File packFolder, String path, JSONObject owner, String field,
RiverBiomeRole role, boolean allowNull, RiverBiomeRole role, boolean allowNull,
boolean validateSuitability,
List<String> errors, List<String> warnings) { List<String> errors, List<String> warnings) {
if (!owner.has(field)) { if (!owner.has(field)) {
return; return;
@@ -588,10 +658,36 @@ final class PackRiverValidator {
errors.add(entryPath + " references missing biome '" + key + "'."); errors.add(entryPath + " references missing biome '" + key + "'.");
continue; continue;
} }
validateBiomeSuitability(entryPath, key, role, PackValidationIo.readJson(biomeFile), warnings); if (validateSuitability) {
validateBiomeSuitability(entryPath, key, role, PackValidationIo.readJson(biomeFile), warnings);
}
} }
} }
private static boolean usesOverworldNativeStructureRoles(DimensionRiverContext context) {
return "NORMAL".equals(stringValue(context.dimension(), "environment", "NORMAL"));
}
private static boolean reachesOverworldNativeStructureRoles(
File packFolder,
String resourceKey,
String resourceType,
List<DimensionRiverContext> contexts
) {
for (DimensionRiverContext context : contexts) {
if (!usesOverworldNativeStructureRoles(context)) {
continue;
}
boolean reachable = "Region".equals(resourceType)
? context.regionKeys().contains(resourceKey)
: referencedSurfaceBiomes(packFolder, context.regionKeys()).contains(resourceKey);
if (reachable) {
return true;
}
}
return false;
}
private static void validateBiomeSuitability(String path, String biomeKey, RiverBiomeRole role, private static void validateBiomeSuitability(String path, String biomeKey, RiverBiomeRole role,
JSONObject biome, List<String> warnings) { JSONObject biome, List<String> warnings) {
if (biome == null || role == RiverBiomeRole.DRY || role == RiverBiomeRole.FLOODED_CAVE) { if (biome == null || role == RiverBiomeRole.DRY || role == RiverBiomeRole.FLOODED_CAVE) {
@@ -661,6 +757,26 @@ final class PackRiverValidator {
validateStyle(packFolder, range, "style", rangePath, errors); validateStyle(packFolder, range, "style", rangePath, errors);
} }
private static double styledRangeMaximum(
File packFolder,
JSONObject owner,
String field,
String path,
double fallback
) {
if (!owner.has(field)) {
return fallback;
}
JSONObject range = resolveObject(
packFolder,
owner.opt(field),
"snippet/style-range/",
path + "." + field,
new ArrayList<>()
);
return range == null ? fallback : doubleValue(range, "max", fallback);
}
private static void validateStyle(File packFolder, JSONObject owner, String field, String path, private static void validateStyle(File packFolder, JSONObject owner, String field, String path,
List<String> errors) { List<String> errors) {
if (!owner.has(field)) { if (!owner.has(field)) {
@@ -173,7 +173,6 @@ public class PregenCacheImpl implements PregenCache {
return readPlate(x, z, input); return readPlate(x, z, input);
} catch (IOException e) { } catch (IOException e) {
IrisLogging.error("Failed to read pregen cache " + file); IrisLogging.error("Failed to read pregen cache " + file);
e.printStackTrace();
IrisLogging.reportError(e); IrisLogging.reportError(e);
} }
@@ -195,7 +194,6 @@ public class PregenCacheImpl implements PregenCache {
plate.dirty = false; plate.dirty = false;
} catch (Throwable e) { } catch (Throwable e) {
IrisLogging.error("Failed to write pregen cache " + (file != null ? file : "c." + plate.x + "." + plate.z)); IrisLogging.error("Failed to write pregen cache " + (file != null ? file : "c." + plate.x + "." + plate.z));
e.printStackTrace();
IrisLogging.reportError(e); IrisLogging.reportError(e);
} }
} }
@@ -464,7 +464,6 @@ public class AsyncPregenMethod implements PregeneratorMethod {
IrisLogging.reportError(throwable); IrisLogging.reportError(throwable);
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace();
} }
return null; return null;
@@ -489,7 +488,6 @@ public class AsyncPregenMethod implements PregeneratorMethod {
} }
} catch (Throwable e) { } catch (Throwable e) {
IrisLogging.reportError(e); IrisLogging.reportError(e);
e.printStackTrace();
} finally { } finally {
try { try {
markFinished(success); markFinished(success);
@@ -520,7 +518,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
String suppressedText = suppressed <= 0 ? "" : " suppressed=" + suppressed; String suppressedText = suppressed <= 0 ? "" : " suppressed=" + suppressed;
// Slow chunk loads are what the adaptive in flight limit exists to absorb, and this line is already // Slow chunk loads are what the adaptive in flight limit exists to absorb, and this line is already
// interval throttled with a suppression count. It reports the throttle working, not a fault. // interval throttled with a suppression count. It reports the throttle working, not a fault.
IrisLogging.info("Async pregen chunk load at " + x + "," + z IrisLogging.debug("Async pregen chunk load at " + x + "," + z
+ " is still pending after " + slowRequestWarningSeconds + "s." + " is still pending after " + slowRequestWarningSeconds + "s."
+ " adaptiveLimit=" + adaptiveInFlightLimit.get() + " adaptiveLimit=" + adaptiveInFlightLimit.get()
+ suppressedText + " " + metricsSnapshot()); + suppressedText + " " + metricsSnapshot());
@@ -583,7 +581,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
} }
if (lastAdaptiveLogAt.compareAndSet(last, now)) { if (lastAdaptiveLogAt.compareAndSet(last, now)) {
IrisLogging.info("Async pregen adaptive limit " + mode + " -> " + value + " " + metricsSnapshot()); IrisLogging.debug("Async pregen adaptive limit " + mode + " -> " + value + " " + metricsSnapshot());
} }
} }
@@ -1001,7 +999,6 @@ public class AsyncPregenMethod implements PregeneratorMethod {
IrisLogging.warn("For more information see https://docs.papermc.io/paper/reference/global-configuration#chunk_system_worker_threads"); IrisLogging.warn("For more information see https://docs.papermc.io/paper/reference/global-configuration#chunk_system_worker_threads");
if (e instanceof InvocationTargetException) { if (e instanceof InvocationTargetException) {
IrisLogging.reportError(e); IrisLogging.reportError(e);
e.printStackTrace();
} }
} }
return 0; return 0;
@@ -1023,7 +1020,6 @@ public class AsyncPregenMethod implements PregeneratorMethod {
} catch (Throwable e) { } catch (Throwable e) {
IrisLogging.reportError(e); IrisLogging.reportError(e);
IrisLogging.error("Failed to reset worker threads"); IrisLogging.error("Failed to reset worker threads");
e.printStackTrace();
} }
return i; return i;
}); });
@@ -119,7 +119,6 @@ public class IrisCodeWorkspace {
IO.writeAll(ws, rendered); IO.writeAll(ws, rendered);
} catch (Throwable e1) { } catch (Throwable e1) {
IrisLogging.reportError(e1); IrisLogging.reportError(e1);
e1.printStackTrace();
} }
} }

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