From e20bb86f40022551af3626619434e2432a467625 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Mon, 24 Aug 2026 02:27:26 -0400 Subject: [PATCH] f --- .../v26_2_R1/CurrentPaperWorldDataWriter.java | 130 ----- .../iris/core/nms/v26_2_R1/NMSBinding.java | 16 +- ...dingCurrentPaperWorldDataContractTest.java | 132 ----- .../src/main/java/art/arcane/iris/Iris.java | 29 +- .../iris/core/commands/CommandDeveloper.java | 6 +- .../iris/core/commands/CommandIris.java | 128 ----- .../iris/core/commands/CommandObject.java | 44 +- .../iris/core/commands/CommandPregen.java | 1 - .../iris/core/commands/CommandStudio.java | 6 - .../iris/core/gui/BukkitVisionOverlay.java | 222 +++++++- .../iris/core/service/IrisEngineSVC.java | 1 - .../core/service/IrisProtocolService.java | 17 + .../art/arcane/iris/core/service/WandSVC.java | 136 ++++- .../arcane/iris/core/wand/WandSelection.java | 106 ++-- .../CommandIrisFoliaCreateContractTest.java | 41 +- .../BukkitVisionOverlayFoliaContractTest.java | 255 +++++++++ .../runtime/StudioPlayerModeContractTest.java | 24 +- .../art/arcane/iris/client/IrisClient.java | 27 +- .../fabric/mixin/PackRepositoryMixin.java | 4 +- .../iris/modded/IrisModdedChunkGenerator.java | 17 +- .../arcane/iris/modded/MainWorldService.java | 27 +- .../arcane/iris/modded/ModdedBiomeWriter.java | 7 +- .../iris/modded/ModdedBlockBreakHandler.java | 11 +- .../iris/modded/ModdedDimensionManager.java | 25 +- .../modded/ModdedDimensionRegistryStore.java | 19 +- .../iris/modded/ModdedDimensionStorage.java | 5 +- .../iris/modded/ModdedEngineBootstrap.java | 17 +- .../iris/modded/ModdedForcedDatapack.java | 49 +- .../art/arcane/iris/modded/ModdedGenPool.java | 25 +- .../modded/ModdedImportedFeatureStage.java | 15 +- .../art/arcane/iris/modded/ModdedIrisLog.java | 68 +++ .../arcane/iris/modded/ModdedMixinAudit.java | 19 +- .../arcane/iris/modded/ModdedModConfig.java | 7 +- .../arcane/iris/modded/ModdedParityProbe.java | 47 +- .../iris/modded/ModdedPrimaryWorldRouter.java | 5 +- .../iris/modded/ModdedProtocolHandler.java | 19 +- .../arcane/iris/modded/ModdedScheduler.java | 11 +- .../iris/modded/ModdedServerLevels.java | 5 +- .../iris/modded/ModdedServiceManager.java | 13 +- .../art/arcane/iris/modded/ModdedStartup.java | 31 +- .../arcane/iris/modded/ModdedWorldCheck.java | 45 +- .../iris/modded/ModdedWorldEngines.java | 25 +- .../modded/WorldCheckDimensionContract.java | 11 +- .../iris/modded/WorldCheckPredicates.java | 5 +- .../iris/modded/WorldCheckStructureAudit.java | 43 +- .../api/ModdedCustomContentRegistry.java | 34 +- .../modded/command/IrisModdedCommands.java | 16 +- .../command/ModdedCommandSuggestions.java | 6 +- .../command/ModdedDatapackCommands.java | 12 +- .../command/ModdedDeveloperCommands.java | 6 +- .../modded/command/ModdedDustRevealer.java | 10 +- .../modded/command/ModdedEditCommands.java | 6 +- .../iris/modded/command/ModdedGoldenHash.java | 8 +- .../modded/command/ModdedLocateCommands.java | 10 +- .../modded/command/ModdedObjectCommands.java | 22 +- .../modded/command/ModdedObjectPlacer.java | 6 +- .../iris/modded/command/ModdedObjectUndo.java | 12 +- .../modded/command/ModdedPackCommands.java | 6 +- .../modded/command/ModdedPregenMethod.java | 38 +- .../iris/modded/command/ModdedRegen.java | 18 +- .../command/ModdedStructureCommands.java | 6 +- .../modded/command/ModdedStudioCommands.java | 46 +- .../command/ModdedUnregisteredStructures.java | 12 +- .../modded/command/ModdedWandService.java | 6 +- .../modded/command/ModdedWhatCommands.java | 10 +- .../modded/command/ModdedWorldCommands.java | 22 +- .../ModdedEngineMaintenanceService.java | 18 +- .../service/ModdedPreservationService.java | 12 +- .../service/ModdedSettingsHotloadService.java | 2 +- .../service/ModdedStudioHotloadService.java | 12 +- .../service/ModdedTreeFellerPresentation.java | 8 +- .../service/ModdedTreeFellerService.java | 6 +- .../iris/client/IrisClientHotloadTest.java | 38 ++ .../ModdedIrisLogLevelCoverageTest.java | 48 ++ .../iris/modded/ModdedPlatformPathsTest.java | 2 +- .../art/arcane/iris/core/IrisSettings.java | 30 +- .../arcane/iris/core/ServerConfigurator.java | 44 +- .../iris/core/SettingsHotloadWatch.java | 10 +- .../art/arcane/iris/core/gui/VisionGUI.java | 8 +- .../lifecycle/IrisWorldRemovalService.java | 2 +- .../core/lifecycle/WorldLifecycleService.java | 6 +- .../core/lifecycle/WorldLifecycleSupport.java | 93 +++- .../arcane/iris/core/link/WorldEditLink.java | 1 - .../iris/core/link/data/NexoDataProvider.java | 3 +- .../art/arcane/iris/core/loader/IrisData.java | 5 +- .../BukkitCommandMessagesExtended.java | 35 -- .../localization/BukkitRuntimeMessages.java | 5 - .../iris/core/localization/IrisLanguage.java | 6 +- .../localization/ModdedCommandMessages.java | 36 +- .../core/localization/ModdedHelpMessages.java | 4 +- .../localization/RuntimeProgressMessages.java | 14 +- .../java/art/arcane/iris/core/nms/INMS.java | 8 +- .../art/arcane/iris/core/nms/INMSBinding.java | 10 - .../iris/core/pack/ContentKeyValidator.java | 2 +- .../art/arcane/iris/core/pack/IrisPack.java | 1 - .../iris/core/pack/IrisPackRepository.java | 3 +- .../iris/core/pack/PackRiverValidator.java | 146 +++++- .../pregenerator/cache/PregenCacheImpl.java | 2 - .../methods/AsyncPregenMethod.java | 8 +- .../iris/core/project/IrisCodeWorkspace.java | 1 - .../core/project/IrisPackageCompiler.java | 1 - .../arcane/iris/core/project/IrisProject.java | 6 +- .../iris/core/project/SchemaBuilder.java | 1 - .../project/StudioOpenProgressReporter.java | 2 - .../protocol/CursorInfoRequestHandler.java | 24 + .../protocol/IrisCursorRequestService.java | 205 ++++++++ .../core/protocol/IrisProtocolServer.java | 32 ++ .../protocol/IrisVisionRequestService.java | 177 ++++++- .../iris/core/runtime/GoldenHashEngine.java | 2 +- .../core/runtime/StudioOpenCoordinator.java | 485 +++--------------- .../runtime/WorldRuntimeControlService.java | 215 ++++---- .../iris/core/service/ExternalDataSVC.java | 15 +- .../core/service/JigsawStudioService.java | 8 +- .../core/service/ObjectStudioSaveService.java | 10 +- .../iris/core/service/PreservationSVC.java | 6 +- .../arcane/iris/core/service/StudioSVC.java | 22 +- .../structure/StructureCaptureImporter.java | 1 - .../core/structure/StructureImporter.java | 9 - .../iris/core/structure/VillageImporter.java | 1 - .../arcane/iris/core/tools/IrisConverter.java | 3 +- .../iris/core/tools/IrisPackBenchmarking.java | 8 +- .../tools/WorldCreationProgressReporter.java | 202 ++------ .../arcane/iris/engine/EngineDataStore.java | 3 - .../arcane/iris/engine/EngineHotloader.java | 12 +- .../iris/engine/EngineRuntimeBuilder.java | 3 - .../iris/engine/EngineShutdownSequence.java | 1 - .../iris/engine/EngineTickRegistry.java | 1 - .../art/arcane/iris/engine/IrisEngine.java | 2 - .../arcane/iris/engine/IrisEngineEffects.java | 21 +- .../arcane/iris/engine/IrisEngineMantle.java | 2 - .../arcane/iris/engine/IrisWorldManager.java | 27 +- .../iris/engine/WorldChunkMaintenance.java | 42 +- .../iris/engine/WorldEntitySpawner.java | 145 +----- .../iris/engine/data/cache/AtomicCache.java | 2 +- .../structure/StructureGraphCatalog.java | 1 - .../engine/mantle/MantleSliceRetention.java | 2 +- .../components/GoldenDebugObjectPlacer.java | 8 +- .../components/IrisStructureComponent.java | 4 +- .../components/MantleObjectComponent.java | 50 +- .../components/MantleRiverCaveVoxelView.java | 4 - .../MantleRiverHydrologyComponent.java | 97 +++- .../engine/modifier/IrisCarveModifier.java | 138 ++++- .../engine/modifier/IrisCarveScratch.java | 33 +- .../IrisFloatingChildBiomeModifier.java | 1 - .../object/IrisBiomeLayerGenerator.java | 4 - .../arcane/iris/engine/object/IrisCompat.java | 1 - .../object/IrisDimensionRuntimeContract.java | 35 ++ .../arcane/iris/engine/object/IrisEntity.java | 6 +- .../iris/engine/object/IrisEntitySpawn.java | 1 - .../iris/engine/object/IrisExpression.java | 6 +- .../engine/object/IrisProceduralTree.java | 2 +- .../iris/engine/object/IrisRiverCaves.java | 5 + .../iris/engine/object/IrisRiverTerrain.java | 30 +- .../iris/engine/object/IrisRiverTopology.java | 30 ++ .../engine/platform/BukkitChunkGenerator.java | 160 +----- .../iris/engine/platform/EngineBukkitOps.java | 1 - .../generators/ObjectStudioGenerator.java | 2 +- .../iris/engine/river/RiverNetwork.java | 420 ++++++++++++++- .../engine/river/RiverNetworkOptions.java | 56 ++ .../arcane/iris/engine/river/RiverReach.java | 9 + .../engine/river/RiverTerrainSampler.java | 9 + .../arcane/iris/engine/river/RiverTile.java | 406 +++++++++------ .../engine/river/RiverTopologyComplexity.java | 65 +++ .../iris/engine/river/RiverWidthProfile.java | 89 ++++ .../cave/RiverCaveContainmentPlanner.java | 43 +- .../river/runtime/IrisRiverRuntime.java | 229 ++++++++- .../director/DirectorContextHandler.java | 1 - .../arcane/iris/util/common/misc/SlimJar.java | 22 +- .../iris/util/common/misc/getHardware.java | 22 +- .../iris/util/common/nbt/mca/NBTWorld.java | 5 +- .../arcane/iris/util/common/scheduling/J.java | 32 +- .../common/scheduling/jobs/DownloadJob.java | 3 +- .../project/matter/IrisMatterSupport.java | 5 +- .../arcane/iris/util/project/noise/CNG.java | 2 +- .../util/project/stream/ProceduralStream.java | 1 - core/src/main/resources/languages/de_DE.json | 30 +- core/src/main/resources/languages/es_ES.json | 30 +- core/src/main/resources/languages/fi_FI.json | 30 +- core/src/main/resources/languages/fr_FR.json | 30 +- core/src/main/resources/languages/he_IL.json | 30 +- core/src/main/resources/languages/it_IT.json | 30 +- core/src/main/resources/languages/ja-JP.json | 30 +- core/src/main/resources/languages/ko_KR.json | 30 +- core/src/main/resources/languages/lt_LT.json | 30 +- core/src/main/resources/languages/nl_NL.json | 30 +- core/src/main/resources/languages/pl_PL.json | 30 +- core/src/main/resources/languages/pt_PT.json | 30 +- core/src/main/resources/languages/ru_RU.json | 30 +- core/src/main/resources/languages/tr_TR.json | 30 +- core/src/main/resources/languages/vi_VI.json | 30 +- core/src/main/resources/languages/zh_CN.json | 30 +- core/src/main/resources/languages/zh_TW.json | 30 +- .../core/DiagnosticSeverityPolicyTest.java | 39 +- .../iris/core/IrisSettingsDefaultsTest.java | 18 +- .../iris/core/ProgressBossBarToggleTest.java | 8 +- .../ServerConfiguratorStartupRestartTest.java | 61 ++- .../iris/core/SettingsHotloadWatchTest.java | 2 +- .../iris/core/gui/VisionRenderTypeTest.java | 7 + .../WorldLifecycleSelectionTest.java | 6 +- .../WorldLifecycleUnloadAsyncTest.java | 100 ++++ .../core/pack/PackRiverValidatorTest.java | 90 +++- .../IrisCursorRequestServiceTest.java | 140 +++++ .../core/protocol/IrisProtocolServerTest.java | 22 + .../IrisVisionRequestServiceTest.java | 114 ++++ .../runtime/StudioEntitySpawningTest.java | 8 +- ...tudioOpenCoordinatorCloseSequenceTest.java | 128 +---- .../StudioOpenCoordinatorOpenKindTest.java | 116 ++--- ...enCoordinatorSpawnStuckRegressionTest.java | 28 +- ...rldRuntimeControlServiceSafeEntryTest.java | 129 ----- ...orldRuntimeControlServiceTeleportTest.java | 100 ++++ ...tudioSVCWorldPackFeedbackContractTest.java | 4 +- .../core/tools/IrisCreatorTeleportTest.java | 27 +- .../WorldCreationProgressReporterTest.java | 32 +- .../engine/IrisWorldManagerMarkerTest.java | 62 ++- .../MantleRiverCaveVoxelViewTest.java | 34 ++ .../MantleRiverHydrologyComponentTest.java | 99 +++- .../IrisCarveModifierBoundarySupportTest.java | 44 ++ ...isCarveModifierInferenceIsolationTest.java | 2 +- .../engine/modifier/IrisCarveScratchTest.java | 12 +- .../IrisDimensionRuntimeContractTest.java | 73 +++ .../iris/engine/river/RiverNetworkTest.java | 404 ++++++++++++++- .../iris/engine/river/RiverTileCacheTest.java | 1 + .../cave/RiverCaveContainmentPlannerTest.java | 44 +- .../river/runtime/IrisRiverRuntimeTest.java | 159 ++++++ .../art/arcane/iris/spi/IrisLoggingTest.java | 32 ++ .../JRepeatingTaskLifecycleTest.java | 43 ++ .../java/art/arcane/iris/spi/IrisLogging.java | 21 +- .../art/arcane/iris/spi/IrisPlatform.java | 1 + 228 files changed, 5716 insertions(+), 3577 deletions(-) delete mode 100644 adapters/bukkit/nms/v26_2_R1/src/main/java/art/arcane/iris/core/nms/v26_2_R1/CurrentPaperWorldDataWriter.java delete mode 100644 adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/core/nms/v26_2_R1/NMSBindingCurrentPaperWorldDataContractTest.java create mode 100644 adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/gui/BukkitVisionOverlayFoliaContractTest.java create mode 100644 adapters/modded-common/src/test/java/art/arcane/iris/client/IrisClientHotloadTest.java create mode 100644 core/src/main/java/art/arcane/iris/core/protocol/CursorInfoRequestHandler.java create mode 100644 core/src/main/java/art/arcane/iris/core/protocol/IrisCursorRequestService.java create mode 100644 core/src/main/java/art/arcane/iris/engine/river/RiverWidthProfile.java create mode 100644 core/src/test/java/art/arcane/iris/core/protocol/IrisCursorRequestServiceTest.java create mode 100644 core/src/test/java/art/arcane/iris/core/runtime/WorldRuntimeControlServiceTeleportTest.java create mode 100644 core/src/test/java/art/arcane/iris/util/common/scheduling/JRepeatingTaskLifecycleTest.java diff --git a/adapters/bukkit/nms/v26_2_R1/src/main/java/art/arcane/iris/core/nms/v26_2_R1/CurrentPaperWorldDataWriter.java b/adapters/bukkit/nms/v26_2_R1/src/main/java/art/arcane/iris/core/nms/v26_2_R1/CurrentPaperWorldDataWriter.java deleted file mode 100644 index 1b58d0b95..000000000 --- a/adapters/bukkit/nms/v26_2_R1/src/main/java/art/arcane/iris/core/nms/v26_2_R1/CurrentPaperWorldDataWriter.java +++ /dev/null @@ -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 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 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); - } -} diff --git a/adapters/bukkit/nms/v26_2_R1/src/main/java/art/arcane/iris/core/nms/v26_2_R1/NMSBinding.java b/adapters/bukkit/nms/v26_2_R1/src/main/java/art/arcane/iris/core/nms/v26_2_R1/NMSBinding.java index e5a80cdb5..dd45728a3 100644 --- a/adapters/bukkit/nms/v26_2_R1/src/main/java/art/arcane/iris/core/nms/v26_2_R1/NMSBinding.java +++ b/adapters/bukkit/nms/v26_2_R1/src/main/java/art/arcane/iris/core/nms/v26_2_R1/NMSBinding.java @@ -154,7 +154,6 @@ import org.jetbrains.annotations.NotNull; import java.awt.Color; import java.io.File; -import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -1572,8 +1571,7 @@ public class NMSBinding implements INMSBinding { injected.set(true); return true; } catch (Throwable e) { - IrisLogging.error(C.RED + "Failed to inject Bukkit"); - e.printStackTrace(); + IrisLogging.reportError(C.RED + "Failed to inject Bukkit", e); ResettableClassFileTransformer partialServerLevel = serverLevelTransformer; ResettableClassFileTransformer partialStorageAccess = levelStorageAccessTransformer; serverLevelTransformer = null; @@ -1627,22 +1625,12 @@ public class NMSBinding implements INMSBinding { try { transformer.reset(Agent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION); } catch (Throwable e) { - IrisLogging.error(C.RED + "Failed to remove Bukkit world lifecycle injection"); - e.printStackTrace(); + IrisLogging.reportError(C.RED + "Failed to remove Bukkit world lifecycle injection", e); } } } } - @Override - public void writeCurrentPaperWorldData( - Path sourceWorldDirectory, - Path targetWorldDirectory, - long seed - ) throws IOException { - CurrentPaperWorldDataWriter.write(sourceWorldDirectory, targetWorldDirectory, seed); - } - @Override public boolean awaitServerShutdownBoundary(long timeout, TimeUnit unit) { MinecraftServer server = ((CraftServer) Bukkit.getServer()).getHandle().getServer(); diff --git a/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/core/nms/v26_2_R1/NMSBindingCurrentPaperWorldDataContractTest.java b/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/core/nms/v26_2_R1/NMSBindingCurrentPaperWorldDataContractTest.java deleted file mode 100644 index f86afcf2f..000000000 --- a/adapters/bukkit/nms/v26_2_R1/src/test/java/art/arcane/iris/core/nms/v26_2_R1/NMSBindingCurrentPaperWorldDataContractTest.java +++ /dev/null @@ -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); - } -} diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/Iris.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/Iris.java index 67416824a..b2f1749c1 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/Iris.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/Iris.java @@ -172,9 +172,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware { try { InstanceState.updateInstanceId(); } catch (Throwable ex) { - System.err.println("[Iris] Failed to update instance id: " + ex.getClass().getSimpleName() - + (ex.getMessage() == null ? "" : " - " + ex.getMessage())); - ex.printStackTrace(); + IrisLogging.reportError("Failed to update the Iris instance id.", ex); } } @@ -350,7 +348,6 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware { try { object.run(); } catch (Throwable e) { - e.printStackTrace(); Iris.reportError(e); } }, RNG.r.i(100, 1200)); @@ -490,7 +487,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware { pw.close(); Iris.info("DUMPED! See " + fi.getAbsolutePath()); } 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); IrisServices.register(WorldDeletionQueue.class, pendingWorldDeletes); IrisServices.register(ManagedWorldLoader.class, (ManagedWorldLoader) this::loadManagedWorld); - SettingsHotloadWatch watch = new SettingsHotloadWatch(getDataFile("settings.json")); + SettingsHotloadWatch watch = new SettingsHotloadWatch(getDataFile("iris.json")); settingsHotloadWatch = watch; // 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 @@ -774,7 +771,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware { private void autoStartStudio() { if (IrisSettings.get().getStudio().isAutoStartDefaultStudio()) { - Iris.info("Starting up auto Studio!"); + Iris.debug("Starting up auto Studio!"); try { Player r = new KList<>(getServer().getOnlinePlayers()).getRandom(); 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(); for (Player i : getServer().getOnlinePlayers()) { final Runnable playerTask = () -> { - i.setGameMode(GameMode.CREATIVE); + i.setGameMode(GameMode.SPECTATOR); BukkitPlatform.teleportAsync(i, spawn); }; if (!J.runEntity(i, playerTask)) { @@ -802,10 +799,9 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware { audiences = new Bindings.Adventure(this); BukkitPlatform.hostAudiences(audiences); } catch (Throwable e) { - e.printStackTrace(); IrisSettings.get().getGeneral().setUseConsoleCustomColors(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()) { quiesceRuntimeForServerShutdown("pre-unload:" + reason); 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; } if (alreadyDrained.get()) { - Iris.info("Pre-unload hook skipped; Iris already drained."); + Iris.debug("Pre-unload hook skipped; Iris already drained."); 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); } @@ -1088,7 +1084,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware { } } if (generators.isEmpty()) { - Iris.info("No Iris worlds to freeze."); + Iris.debug("No Iris worlds to freeze."); return; } @@ -1112,7 +1108,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware { try { CompletableFuture.allOf(closes.toArray(new CompletableFuture[0])) .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) { Iris.warn("Iris generator drain timed out after " + timeoutSeconds + "s; unload proceeding anyway."); } catch (InterruptedException e) { @@ -1201,7 +1197,6 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware { try { Iris.syncJobs.next().run(); } catch (Throwable e) { - e.printStackTrace(); Iris.reportError(e); } } @@ -1209,7 +1204,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware { } private void bstats() { - if (IrisSettings.get().getGeneral().isPluginMetrics()) { + if (IrisSettings.get().getGeneral().isMetrics()) { Bindings.setupBstats(this); } } diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandDeveloper.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandDeveloper.java index 36b5e9ffe..d821b1acc 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandDeveloper.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandDeveloper.java @@ -242,7 +242,7 @@ public class CommandDeveloper implements DirectorExecutor { try (CountingDataInputStream in = CountingDataInputStream.wrap(new BufferedInputStream(new FileInputStream(base)))) { TectonicPlate.read(1088, in, true, IrisEngineMantle.createRuntimeDataAdapter(activeEngine.getData()), IrisEngineMantle.createRuntimeHooks()); } catch (Throwable e) { - e.printStackTrace(); + Iris.reportError("Failed to inspect the Iris tectonic plate.", e); } } else { Matter.read(section); @@ -282,7 +282,7 @@ public class CommandDeveloper implements DirectorExecutor { MCAFile MCARegion = MCAUtil.read(mca); } } 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) { - e.printStackTrace(); + Iris.reportError("Failed to inspect server network interfaces.", e); } } diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java index f7bc3e3ae..b0377f2da 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java @@ -22,22 +22,16 @@ import art.arcane.iris.Iris; import art.arcane.iris.core.BukkitWorldReconciler; import art.arcane.iris.platform.bukkit.BukkitPlatform; 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.IrisWorlds; import art.arcane.iris.core.PendingWorldReplacementManager; 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.LifecycleOperationCoordinator; import art.arcane.iris.core.lifecycle.WorldLifecycleService; 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.PackDirectoryResolver; -import art.arcane.iris.core.pack.PackValidationRegistry; import art.arcane.iris.core.service.StudioSVC; import art.arcane.iris.core.tools.IrisToolbelt; import art.arcane.iris.engine.platform.PlatformChunkGenerator; @@ -62,9 +56,7 @@ import org.bukkit.World; import org.bukkit.entity.Player; import java.io.File; -import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; @@ -160,16 +152,6 @@ public class CommandIris implements DirectorExecutor { 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 { IrisToolbelt.createWorld() .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) { Throwable current = failure; while (current != null) { diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandObject.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandObject.java index 4fa13657d..d1a57aab5 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandObject.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandObject.java @@ -24,6 +24,7 @@ import art.arcane.iris.core.link.WorldEditLink; import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.pack.PackDirectoryResolver; 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.service.ObjectSVC; import art.arcane.iris.core.service.StudioSVC; @@ -171,24 +172,29 @@ public class CommandObject implements DirectorExecutor { IrisDimension finalHost = hostDimension; try { - Iris.service(StudioSVC.class).open(commandSender, seed, hostDimension.getLoadKey(), 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); - } + Iris.service(StudioSVC.class).open( + commandSender, + seed, + hostDimension.getLoadKey(), + StudioOpenCoordinator.StudioOpenKind.OBJECT, + 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()) { - Player p = commandSender.player(); - if (p != null) { - Location target = new Location(world, 0.5D, 66D, 0.5D); - J.runEntity(p, () -> { - BukkitPlatform.teleportAsync(p, target).thenRun(() -> p.setGameMode(GameMode.CREATIVE)); - }); - } - } - }); + if (commandSender.isPlayer()) { + Player p = commandSender.player(); + if (p != null) { + Location target = new Location(world, 0.5D, 66D, 0.5D); + J.runEntity(p, () -> { + BukkitPlatform.teleportAsync(p, target).thenRun(() -> p.setGameMode(GameMode.CREATIVE)); + }); + } + } + }); } catch (Throwable 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())))); @@ -360,7 +366,7 @@ public class CommandObject implements DirectorExecutor { o.write(o.getLoadFile()); } 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())))); - e.printStackTrace(); + Iris.reportError("Failed to save object " + o.getLoadFile() + ".", e); } } @@ -426,7 +432,7 @@ public class CommandObject implements DirectorExecutor { try { IrisConverter.convertSchematics(sender()); } catch (Exception e) { - e.printStackTrace(); + Iris.reportError("Failed to convert schematics to Iris objects.", e); } } diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandPregen.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandPregen.java index 51e0a654e..b5a1e0986 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandPregen.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandPregen.java @@ -89,7 +89,6 @@ public class CommandPregen implements DirectorExecutor { } catch (Throwable e) { sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_PREGEN_FAILED_START_PREGENERATION_SEE_CONSOLE_DETAILS)); Iris.reportError(e); - e.printStackTrace(); } } diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandStudio.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandStudio.java index 894457603..0c01d33dc 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandStudio.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandStudio.java @@ -77,7 +77,6 @@ import art.arcane.volmlib.util.scheduling.PrecisionStopwatch; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.FluidCollisionMode; -import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; @@ -573,7 +572,6 @@ public class CommandStudio implements DirectorExecutor { IO.writeAll(report, fileText.toString("\n")); } catch (IOException e) { Iris.reportError(e); - e.printStackTrace(); } 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); 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()))); } catch (Throwable e) { - e.printStackTrace(); Iris.reportError(e); } } diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/gui/BukkitVisionOverlay.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/gui/BukkitVisionOverlay.java index 16ef55fff..946f35dc3 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/gui/BukkitVisionOverlay.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/gui/BukkitVisionOverlay.java @@ -18,14 +18,17 @@ package art.arcane.iris.core.gui; -import art.arcane.iris.platform.bukkit.BukkitPlatform; -import art.arcane.iris.platform.bukkit.BukkitWorldBinding; +import art.arcane.iris.core.runtime.WorldRuntimeControlService; import art.arcane.iris.engine.IrisComplex; import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.render.RenderType; 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.volmlib.util.format.Form; +import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.LivingEntity; @@ -36,15 +39,21 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import static art.arcane.iris.util.common.data.registry.Attributes.MAX_HEALTH; public final class BukkitVisionOverlay implements GuiOverlay { private final Engine engine; + private final AtomicBoolean nativeTeleportActive = new AtomicBoolean(); private final AtomicBoolean playerRefreshQueued = new AtomicBoolean(); + private final AtomicLong teleportSequence = new AtomicLong(); + private final AtomicReference latestTeleport = new AtomicReference<>(); private volatile List playerMarkers = List.of(); public BukkitVisionOverlay(Engine engine) { @@ -144,25 +153,198 @@ public final class BukkitVisionOverlay implements GuiOverlay { @Override public void teleport(double worldX, double worldZ) { - IrisWorld target = engine.getWorld(); - if (!target.hasPlatformWorld()) { + VisionTeleportRequest request = new VisionTeleportRequest( + 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; } - 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 players = BukkitWorldBinding.players(target); if (players.isEmpty()) { + finish(request); return; } Player player = players.get(0); - World world = player.getWorld(); - 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)); - }); + requestTeleportChunk(request, target, player, world); }); + 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 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 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 @@ -179,4 +361,18 @@ public final class BukkitVisionOverlay implements GuiOverlay { }; 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); + } + } } diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/IrisEngineSVC.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/IrisEngineSVC.java index 3f10b882d..f33697204 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/IrisEngineSVC.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/IrisEngineSVC.java @@ -605,7 +605,6 @@ public final class IrisEngineSVC implements IrisService { private static void reportFailure(String message, Throwable exception) { IrisLogging.reportError(exception); IrisLogging.error("EngineSVC: " + message); - exception.printStackTrace(); } private final class Registered { diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/IrisProtocolService.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/IrisProtocolService.java index af6687e74..ebaa91640 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/IrisProtocolService.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/IrisProtocolService.java @@ -20,6 +20,7 @@ package art.arcane.iris.core.service; import art.arcane.iris.Iris; 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.IrisServerTransport; import art.arcane.iris.core.protocol.IrisSession; @@ -55,6 +56,7 @@ public class IrisProtocolService implements IrisService, PluginMessageListener, private IrisSessionRegistry registry; private IrisProtocolServer protocolServer; + private IrisCursorRequestService cursorService; private IrisVisionRequestService visionService; @Override @@ -66,6 +68,8 @@ public class IrisProtocolService implements IrisService, PluginMessageListener, protocolServer = new IrisProtocolServer(registry, SERVER_CAPABILITIES, brand(), true); EngineResolver engineResolver = IrisProtocolService::resolveEngine; protocolServer.setEngineResolver(engineResolver); + cursorService = IrisCursorRequestService.create(engineResolver, registry); + protocolServer.setCursorInfoHandler(cursorService); visionService = IrisVisionRequestService.create(engineResolver, registry); protocolServer.setVisionTileHandler(visionService); IrisServices.register(IrisProtocolServer.class, protocolServer); @@ -81,13 +85,22 @@ public class IrisProtocolService implements IrisService, PluginMessageListener, messenger.unregisterIncomingPluginChannel(Iris.instance, IrisProtocol.CHANNEL, this); messenger.unregisterOutgoingPluginChannel(Iris.instance, IrisProtocol.CHANNEL); IrisSessionRegistry current = registry; + IrisCursorRequestService cursor = cursorService; + IrisVisionRequestService vision = visionService; if (current != null) { for (IrisSession session : current.all()) { current.unregister(session.id()); + if (cursor != null) { + cursor.clearSession(session.id()); + } + if (vision != null) { + vision.clearSession(session.id()); + } } } registry = null; protocolServer = null; + cursorService = null; visionService = null; } @@ -117,6 +130,10 @@ public class IrisProtocolService implements IrisService, PluginMessageListener, } String sessionId = event.getPlayer().getUniqueId().toString(); current.unregister(sessionId); + IrisCursorRequestService cursor = cursorService; + if (cursor != null) { + cursor.clearSession(sessionId); + } IrisVisionRequestService vision = visionService; if (vision != null) { vision.clearSession(sessionId); diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/WandSVC.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/WandSVC.java index 5afa2c8f6..3f17536ab 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/WandSVC.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/WandSVC.java @@ -51,6 +51,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemFlag; @@ -61,18 +62,30 @@ import org.bukkit.util.BlockVector; import org.bukkit.util.Vector; import java.awt.Color; +import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; 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.REDSTONE; 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 PLAYER_RESCAN_INTERVAL_TICKS = 100; private static ItemStack dust; private static ItemStack wand; + private final Map 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) { s.place(at); } @@ -174,7 +187,6 @@ public class WandSVC implements IrisService { return s; } catch (Throwable e) { - e.printStackTrace(); Iris.reportError(e); } @@ -196,7 +208,6 @@ public class WandSVC implements IrisService { return WorldMatter.createMatter(p.getName(), f[0], f[1]); } catch (Throwable e) { - e.printStackTrace(); Iris.reportError(e); } @@ -307,8 +318,18 @@ public class WandSVC implements IrisService { } public static Location[] getCuboidFromItem(ItemStack is) { + if (is == null) { + return new Location[]{null, null}; + } 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 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) { @@ -343,30 +364,47 @@ public class WandSVC implements IrisService { * @return True if it is */ public static boolean isWand(ItemStack is) { - if (is == null || is.getItemMeta() == null) { + if (is == null) { return false; } - Byte marker = is.getItemMeta().getPersistentDataContainer().get(wandKey(), PersistentDataType.BYTE); - if (marker != null && marker == (byte) 1) { + ItemMeta meta = is.getItemMeta(); + if (meta == null) { + return false; + } + Byte marker = meta.getPersistentDataContainer().get(wandKey(), PersistentDataType.BYTE); + if (marker != null && marker.byteValue() == 1) { return true; } - return is.getType().equals(wand.getType()) && - is.getItemMeta().getDisplayName().equals(wand.getItemMeta().getDisplayName()) && - is.getItemMeta().getEnchants().equals(wand.getItemMeta().getEnchants()) && - is.getItemMeta().getItemFlags().equals(wand.getItemMeta().getItemFlags()); + ItemStack template = wand; + if (template == null || !is.getType().equals(template.getType())) { + return false; + } + ItemMeta templateMeta = template.getItemMeta(); + return templateMeta != null + && Objects.equals(meta.getDisplayName(), templateMeta.getDisplayName()) + && meta.getEnchants().equals(templateMeta.getEnchants()) + && meta.getItemFlags().equals(templateMeta.getItemFlags()); } @Override public void onEnable() { wand = createWand(); dust = createDust(); - - J.ar(this::tickAll, 0); + enabled = true; + activePlayers.clear(); + ticksUntilPlayerRescan = 0; + taskId = J.ar(this::tickAll, 1); } @Override 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() { try { - J.runGlobal(() -> { - for (Player p : Bukkit.getOnlinePlayers()) { - J.runEntity(p, () -> tick(p)); - } - }); + if (!enabled) { + return; + } + if (ticksUntilPlayerRescan-- <= 0) { + ticksUntilPlayerRescan = PLAYER_RESCAN_INTERVAL_TICKS; + rescanPlayers(); + } + for (Player player : activePlayers.values()) { + J.runEntity(player, () -> tick(player)); + } } catch (Throwable e) { Iris.reportError(e); } @@ -387,20 +430,53 @@ public class WandSVC implements IrisService { public void tick(Player p) { try { - try { - if ((IrisSettings.get().getWorld().worldEditWandCUI && isHoldingWand(p)) || isWand(p.getInventory().getItemInMainHand())) { - Location[] d = getCuboid(p); - 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); + if (!p.isOnline()) { + activePlayers.remove(p.getUniqueId(), p); + return; } + 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) { - 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 * @@ -492,6 +568,7 @@ public class WandSVC implements IrisService { return; try { if (isHoldingIrisWand(e.getPlayer())) { + activePlayers.put(e.getPlayer().getUniqueId(), e.getPlayer()); if (e.getAction().equals(Action.LEFT_CLICK_BLOCK)) { e.setCancelled(true); 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? * diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/wand/WandSelection.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/wand/WandSelection.java index 3808a2f31..35ef6b7f5 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/wand/WandSelection.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/wand/WandSelection.java @@ -23,16 +23,18 @@ import art.arcane.volmlib.util.math.M; import org.bukkit.Location; import org.bukkit.Particle; import org.bukkit.entity.Player; -import org.bukkit.util.Vector; import java.awt.Color; import static art.arcane.iris.util.common.data.registry.Particles.REDSTONE; 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 Player p; - private static final double STEP = 0.10; public WandSelection(Cuboid c, Player p) { this.c = c; @@ -45,57 +47,91 @@ public class WandSelection { return; } - double maxDistanceSquared = 256 * 256; - int particleCount = 0; - // cube! - Location[][] edges = { - {c.getLowerNE(), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ())}, - {c.getLowerNE(), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ())}, - {c.getLowerNE(), new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1)}, - {new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ())}, - {new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1)}, - {new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getUpperX() + 1, c.getUpperY() + 1, c.getLowerZ())}, - {new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getLowerZ()), new Location(c.getWorld(), c.getLowerX(), c.getUpperY() + 1, c.getUpperZ() + 1)}, - {new Location(c.getWorld(), c.getLowerX(), c.getLowerY(), c.getUpperZ() + 1), new Location(c.getWorld(), c.getUpperX() + 1, c.getLowerY(), c.getUpperZ() + 1)}, - {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)} - }; + double minX = c.getLowerX(); + double minY = c.getLowerY(); + double minZ = c.getLowerZ(); + double maxX = c.getUpperX() + 1D; + double maxY = c.getUpperY() + 1D; + double maxZ = c.getUpperZ() + 1D; + double playerX = playerLoc.getX(); + double playerY = playerLoc.getY(); + double playerZ = playerLoc.getZ(); - for (Location[] edge : edges) { - Vector direction = edge[1].toVector().subtract(edge[0].toVector()); - double length = direction.length(); - direction.normalize(); + drawX(minX, maxX, minY, minZ, playerX, playerY, playerZ); + drawX(minX, maxX, maxY, minZ, playerX, playerY, playerZ); + drawX(minX, maxX, minY, maxZ, playerX, playerY, playerZ); + 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) { - Location particleLoc = edge[0].clone().add(direction.clone().multiply(d)); + private void drawX(double start, double end, double y, double z, double playerX, double playerY, double playerZ) { + 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) { - continue; - } + private void drawY(double start, double end, double x, double z, double playerX, double playerY, double playerZ) { + 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); - particleCount++; - } + private void drawZ(double start, double end, double x, double y, double playerX, double playerY, double playerZ) { + 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) { - double accuracy = M.lerpInverse(0, 64 * 64, playerLoc.distanceSquared(particleLoc)); + private void spawnParticle(double x, double y, double z, double distanceSquared) { + double accuracy = M.lerpInverse(0, 64 * 64, distanceSquared); double dist = M.lerp(0.125, 3.5, accuracy); if (M.r(Math.min(dist * 5, 0.9D) * 0.995)) { 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); - p.spawnParticle(REDSTONE, particleLoc, + p.spawnParticle(REDSTONE, x, y, z, 0, 0, 0, 0, 1, new Particle.DustOptions(org.bukkit.Color.fromRGB(color.getRed(), color.getGreen(), color.getBlue()), (float) dist * 3f)); } + + private static double square(double value) { + return value * value; + } + + @FunctionalInterface + private interface AxisParticle { + void spawn(double coordinate, double distanceSquared); + } } diff --git a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/commands/CommandIrisFoliaCreateContractTest.java b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/commands/CommandIrisFoliaCreateContractTest.java index adf249d9c..73433b18b 100644 --- a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/commands/CommandIrisFoliaCreateContractTest.java +++ b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/commands/CommandIrisFoliaCreateContractTest.java @@ -5,44 +5,31 @@ import org.junit.Test; import java.nio.file.Files; import java.nio.file.Path; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class CommandIrisFoliaCreateContractTest { @Test - public void ordinaryFoliaCreateRestartsOnlyAfterSuccessfulStagingAndFeedback() throws Exception { + public void ordinaryFoliaCreateUsesTheSharedRuntimeCreationPath() throws Exception { String source = Files.readString(Path.of(System.getProperty("iris.commandIrisSource"))); - String foliaCreate = source.substring( - source.indexOf("if (J.isFolia()) {"), - source.indexOf(" try {", source.indexOf("if (J.isFolia()) {")) + String create = source.substring( + source.indexOf(" public void create("), + source.indexOf(" @Director(", source.indexOf(" public void create(")) ); - int stage = foliaCreate.indexOf("stageFoliaWorldCreation(worldName, dimension, seed)"); - int failureExit = foliaCreate.indexOf("if (!staged)"); - int feedback = foliaCreate.indexOf("COMMAND_IRIS_WORLD_STAGING_COMPLETED_RESTARTING_SERVER_GENERATE_LOAD"); - int restart = foliaCreate.indexOf("ServerConfigurator.restart(\"Iris staged Folia world"); - - assertTrue(stage >= 0); - assertTrue(stage < failureExit); - assertTrue(failureExit < feedback); - assertTrue(feedback < restart); + assertTrue(create.contains("IrisToolbelt.createWorld()")); + assertTrue(create.contains(".studio(false)")); + assertTrue(create.contains(".create();")); + assertFalse(create.contains("J.isFolia()")); + assertFalse(create.contains("ServerConfigurator.restart(")); } @Test - public void foliaStagePublishesCurrentPaperDataBeforeRegisteringStartupAlias() throws Exception { + public void obsoleteFoliaStagingSurfaceIsRemoved() throws Exception { 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("); - int pack = staging.indexOf("installIntoWorld("); - int publication = staging.indexOf("AtomicDirectoryPublisher.publishAbsent("); - int registration = staging.indexOf("registerWorldInBukkitYml(worldKey"); - - assertTrue(currentPaperData >= 0); - assertTrue(currentPaperData < pack); - assertTrue(pack < publication); - assertTrue(publication < registration); - assertTrue(source.contains("IrisWorldStorage.configuredWorldName(")); + assertFalse(source.contains("stageFoliaWorldCreation")); + assertFalse(source.contains("writeCurrentPaperWorldData")); + assertFalse(source.contains("COMMAND_IRIS_RUNTIME_WORLD_CREATION_IS_DISABLED_ON_FOLIA")); } } diff --git a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/gui/BukkitVisionOverlayFoliaContractTest.java b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/gui/BukkitVisionOverlayFoliaContractTest.java new file mode 100644 index 000000000..055b6e83e --- /dev/null +++ b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/gui/BukkitVisionOverlayFoliaContractTest.java @@ -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 firstChunk = new CompletableFuture<>(); + CompletableFuture 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 firstTeleport = new CompletableFuture<>(); + CompletableFuture 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 scheduling; + private final MockedStatic binding; + private final MockedStatic runtimeAccess; + private final MockedStatic platform; + private final List destinations; + private final List> 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 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(); + } + } +} diff --git a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/runtime/StudioPlayerModeContractTest.java b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/runtime/StudioPlayerModeContractTest.java index 9a19118c1..0e7eeba9d 100644 --- a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/runtime/StudioPlayerModeContractTest.java +++ b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/runtime/StudioPlayerModeContractTest.java @@ -11,15 +11,14 @@ import static org.junit.Assert.assertTrue; public class StudioPlayerModeContractTest { @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 commands = Files.readString(Path.of( "src/main/java/art/arcane/iris/core/commands/CommandStudio.java")).replace("\r\n", "\n"); - assertFalse(plugin.contains("GameMode.SPECTATOR")); - assertFalse(commands.contains("GameMode.SPECTATOR")); - assertTrue(plugin.contains("GameMode.CREATIVE")); - assertTrue(commands.contains("GameMode.CREATIVE")); + assertFalse(plugin.contains("GameMode.CREATIVE")); + assertFalse(commands.contains("GameMode.CREATIVE")); + assertTrue(plugin.contains("GameMode.SPECTATOR")); } @Test @@ -32,8 +31,23 @@ public class StudioPlayerModeContractTest { assertTrue(method.contains("StudioSVC studioService = Iris.service(StudioSVC.class)")); assertTrue(method.contains("studioService.teleportToActiveProject(player)")); + assertFalse(method.contains("setGameMode(")); assertFalse(method.contains("getActiveProject()")); assertFalse(method.contains("BukkitPlatform.teleportAsync")); 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")); + } } diff --git a/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClient.java b/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClient.java index eb7c3168e..7774f2fb7 100644 --- a/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClient.java +++ b/adapters/client-common/src/main/java/art/arcane/iris/client/IrisClient.java @@ -146,7 +146,7 @@ public final class IrisClient { case IrisMessage.VisionTile visionTile -> TILES.onVisionTile(visionTile); case IrisMessage.VisionMarkers visionMarkers -> MARKERS.onMarkers(visionMarkers); 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()); default -> { } @@ -167,4 +167,29 @@ public final class IrisClient { 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()); + } } diff --git a/adapters/fabric/src/main/java/art/arcane/iris/fabric/mixin/PackRepositoryMixin.java b/adapters/fabric/src/main/java/art/arcane/iris/fabric/mixin/PackRepositoryMixin.java index 1b2669521..8a4e1566d 100644 --- a/adapters/fabric/src/main/java/art/arcane/iris/fabric/mixin/PackRepositoryMixin.java +++ b/adapters/fabric/src/main/java/art/arcane/iris/fabric/mixin/PackRepositoryMixin.java @@ -18,6 +18,7 @@ package art.arcane.iris.fabric.mixin; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.fabric.FabricForcedDatapackSources; import net.minecraft.server.packs.repository.PackRepository; 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.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.slf4j.LoggerFactory; import java.util.Arrays; @@ -42,7 +42,7 @@ public class PackRepositoryMixin { } // Client resource-pack repositories legitimately have no ServerPacksSource; a missing server-data // 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) {}", sources.length, Arrays.toString(sources)); } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java index c93b07ceb..7bfd790d8 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/IrisModdedChunkGenerator.java @@ -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.StructureStart; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.util.Arrays; @@ -98,7 +96,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.IntBinaryOperator; 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, // 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. @@ -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 // the same reason as repointAndBind: this method owns the generator monitor. 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) { @@ -601,7 +598,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator { try { heightMetadata = configuredPack().metadata(); } 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()); } } @@ -726,7 +723,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator { Engine generationEngine = engine(); ChunkPos pos = chunk.getPos(); 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(); @@ -744,7 +741,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator { try (GenerationSessionLease lease = generationEngine.acquireGenerationLease("modded_chunk_pipeline"); IrisContext.Scope ignored = IrisContext.open(generationEngine, lease.sessionId(), null)) { 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()); } int dimMinY = generationEngine.getMinHeight(); @@ -763,14 +760,14 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator { return chunk; } catch (GenerationSessionException e) { 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( "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); } 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); } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/MainWorldService.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/MainWorldService.java index 1f07f0c31..6dc063562 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/MainWorldService.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/MainWorldService.java @@ -18,8 +18,6 @@ package art.arcane.iris.modded; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -32,7 +30,6 @@ import java.util.Optional; import java.util.UUID; public final class MainWorldService { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final String MARKER_NAME = "mainworld.pending"; private static final String PROPERTIES_NAME = "server.properties"; /** @@ -80,12 +77,12 @@ public final class MainWorldService { if (!target.equals(currentType)) { writeLevelProperties(properties, target, config.mainWorldSeed()); 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); 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); - 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); 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 // reason to refuse startup. 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); return; } Path recovery = quarantineVanillaDimensions(worldRoot); 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); } catch (Throwable e) { - LOGGER.error("Iris main world reconciliation failed", e); + ModdedIrisLog.error("Iris main world reconciliation failed", e); throw new IllegalStateException( "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) { 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; } Path instanceRoot = verifiedInstanceRoot("stage the Iris main world"); @@ -131,7 +128,7 @@ public final class MainWorldService { markPending(); return true; } 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; } } @@ -140,7 +137,7 @@ public final class MainWorldService { try { clearPending(); } 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))) { return workingDirectory; } - LOGGER.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 refuses to {}: no {} in the server working directory {}", operation, PROPERTIES_NAME, workingDirectory); + 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; } @@ -305,7 +302,7 @@ public final class MainWorldService { return List.of(arguments.get()); } } 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 // --universe or --world value containing spaces cannot be recovered from it. Deliberately not parsed diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBiomeWriter.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBiomeWriter.java index ed0914a2b..ffe05cfb2 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBiomeWriter.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBiomeWriter.java @@ -29,8 +29,6 @@ import net.minecraft.core.registries.Registries; import net.minecraft.resources.Identifier; import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.biome.Biome; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; @@ -39,7 +37,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; 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 int MAX_CACHED_IDS = 4096; /** 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; } 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); } @@ -217,7 +214,7 @@ public final class ModdedBiomeWriter implements PlatformBiomeWriter { private void reportMissingServer(String operation, String fallback) { 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); } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBlockBreakHandler.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBlockBreakHandler.java index 4d344d94a..21c1d5faf 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBlockBreakHandler.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedBlockBreakHandler.java @@ -43,14 +43,11 @@ import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkGenerator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.List; import java.util.concurrent.ConcurrentHashMap; public final class ModdedBlockBreakHandler { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final ConcurrentHashMap PENDING = new ConcurrentHashMap<>(); private ModdedBlockBreakHandler() { @@ -72,7 +69,7 @@ public final class ModdedBlockBreakHandler { if (scheduler == null) { // 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. - 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()); return; } @@ -150,7 +147,7 @@ public final class ModdedBlockBreakHandler { try { return evaluate(level, position, pending); } 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); return Result.empty(); } @@ -182,7 +179,7 @@ public final class ModdedBlockBreakHandler { try { return evaluateDrops(level, position, brokenState, engine); } 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); return Result.empty(); } @@ -315,7 +312,7 @@ public final class ModdedBlockBreakHandler { try { return irisGenerator.commandEngine(); } 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; } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionManager.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionManager.java index 322084ea6..8065d6d15 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionManager.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionManager.java @@ -46,8 +46,6 @@ import net.minecraft.world.level.storage.DerivedLevelData; import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraft.world.level.storage.ServerLevelData; import net.minecraft.world.level.storage.WorldData; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.util.ArrayList; @@ -60,7 +58,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; public final class ModdedDimensionManager { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final Object LOCK = new Object(); private static final ConcurrentHashMap HANDLES = new ConcurrentHashMap<>(); 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)) { 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); Handle handle = new Handle(dimensionId, pack, packDimensionKey, seed, present, generator); HANDLES.put(dimensionId, handle); @@ -147,10 +144,10 @@ public final class ModdedDimensionManager { try { Handle handle = inject(server, serverAccess, dimensionId, key, pack, packDimensionKey, seed); 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; } 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); } } @@ -233,11 +230,11 @@ public final class ModdedDimensionManager { if (wipeStorage) { ModdedDimensionStorage.wipe(server, key); } - LOGGER.info("Iris removed runtime dimension '{}'", dimensionId); + ModdedIrisLog.info("Iris removed runtime dimension '{}'", dimensionId); return true; } catch (Throwable 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); } } @@ -256,7 +253,7 @@ public final class ModdedDimensionManager { if (rollbackFailure != failure) { 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); } } @@ -282,7 +279,7 @@ public final class ModdedDimensionManager { .whenComplete((Object result, Throwable error) -> server.execute(() -> { level.getChunkSource().removeTicketWithRadius(TELEPORT_WARM_TICKET, chunkPos, 1); 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); if (target == null) { @@ -325,7 +322,7 @@ public final class ModdedDimensionManager { } return dimension; } 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); if (e instanceof Error fatalError) { throw fatalError; @@ -401,19 +398,19 @@ public final class ModdedDimensionManager { } } catch (Throwable 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 { generator.unbindEngine(level); } catch (Throwable 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 { level.close(); } catch (Throwable 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); } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionRegistryStore.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionRegistryStore.java index 91c2cd579..f4441600e 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionRegistryStore.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionRegistryStore.java @@ -22,8 +22,6 @@ import art.arcane.volmlib.util.json.JSONArray; import art.arcane.volmlib.util.json.JSONObject; import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.storage.LevelResource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.channels.FileChannel; @@ -41,7 +39,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; public final class ModdedDimensionRegistryStore { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final String FILE_NAME = "iris-dimensions.json"; private static final Pattern ID_FIELD = Pattern.compile("\"id\"\\s*:\\s*\"([^\"]+)\""); @@ -68,13 +65,13 @@ public final class ModdedDimensionRegistryStore { try { return load(file); } 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); List lostIds = salvageIds(file); 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 { - 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)); } quarantine(file); @@ -93,7 +90,7 @@ public final class ModdedDimensionRegistryStore { } } } 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; } @@ -102,9 +99,9 @@ public final class ModdedDimensionRegistryStore { Path broken = file.resolveSibling(FILE_NAME + ".broken-" + System.currentTimeMillis()); try { 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) { - 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); } } @@ -134,14 +131,14 @@ public final class ModdedDimensionRegistryStore { PersistentDimension previous = deduplicated.putIfAbsent( id, new PersistentDimension(id, pack, dimension, entry.getLong("seed"))); 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); } } catch (RuntimeException invalidEntry) { if (raw != null) { 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); } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionStorage.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionStorage.java index 58f4d28bc..3650e022d 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionStorage.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedDimensionStorage.java @@ -23,8 +23,6 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.Level; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.storage.LevelResource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -35,7 +33,6 @@ import java.util.List; import java.util.stream.Stream; public final class ModdedDimensionStorage { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final List CHUNK_DATA_FOLDERS = List.of("region", "entities", "poi", "mantle"); private ModdedDimensionStorage() { @@ -56,7 +53,7 @@ public final class ModdedDimensionStorage { "Iris failed to completely wipe dimension storage at " + 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 { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java index edf174067..c00959c7c 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java @@ -55,13 +55,10 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.storage.LevelData; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ArrayDeque; public final class ModdedEngineBootstrap { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final String[] CORE_SELF_TEST_CLASSES = { "art.arcane.iris.engine.IrisEngine", "art.arcane.iris.util.common.data.B", @@ -156,7 +153,7 @@ public final class ModdedEngineBootstrap { try { generator.unbindEngine(level); } 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) { throw runtimeException; } @@ -207,7 +204,7 @@ public final class ModdedEngineBootstrap { if (failure != null) { // 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. - 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(); return failure; } catch (Throwable stageFailure) { - LOGGER.error("Iris modded shutdown stage '{}' failed", stage, stageFailure); + ModdedIrisLog.error("Iris modded shutdown stage '{}' failed", stage, stageFailure); if (failure == null) { return stageFailure; } @@ -255,7 +252,7 @@ public final class ModdedEngineBootstrap { BlockPos position = reconciledSpawnPosition(surfaceY, level.getMinY(), level.getHeight()); server.setRespawnData(LevelData.RespawnData.of( 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()); } @@ -321,7 +318,7 @@ public final class ModdedEngineBootstrap { Class.forName(className, true, classLoader); loadedClasses++; } 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); } catch (Throwable splashFailure) { // 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(); runtime = new BoundRuntime(created, createdServices); @@ -413,7 +410,7 @@ public final class ModdedEngineBootstrap { } catch (Throwable failure) { createdServices.rollback(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) { throw runtimeException; } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java index d1de90c32..93f845ee2 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedForcedDatapack.java @@ -39,8 +39,6 @@ import net.minecraft.server.packs.PathPackResources; import net.minecraft.server.packs.repository.Pack; import net.minecraft.server.packs.repository.PackSource; import net.minecraft.server.packs.repository.RepositorySource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -67,7 +65,6 @@ import java.util.function.Consumer; import java.util.stream.Stream; 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_FOLDER = "iris"; private static final String HASH_FILE_NAME = "packs.hash"; @@ -111,7 +108,7 @@ public final class ModdedForcedDatapack { return requireReadablePack(current.directory()); } catch (RuntimeException unreadable) { 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); } } @@ -125,19 +122,19 @@ public final class ModdedForcedDatapack { reason = "stale cache (hash changed)"; } else { 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()); } try { return requireReadablePack(state.directory()); } catch (RuntimeException unreadable) { 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); } 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(); } } @@ -151,11 +148,11 @@ public final class ModdedForcedDatapack { if (packs.isEmpty()) { return; } - LOGGER.error("==============================================================="); - LOGGER.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); - LOGGER.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("==============================================================="); + ModdedIrisLog.error("Iris forced datapack '{}' was never loaded by this server.", PACK_ID); + ModdedIrisLog.error("{} installed pack(s) at {} contributed no dimension types or custom biomes.", packs.size(), packsRoot); + ModdedIrisLog.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("==============================================================="); } public static Path datapackRoot() { @@ -172,7 +169,7 @@ public final class ModdedForcedDatapack { } catch (RuntimeException | Error generationFailure) { Path lastKnownGood = packDirectory(); 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); return requireReadablePack(lastKnownGood); } @@ -201,7 +198,7 @@ public final class ModdedForcedDatapack { try { return write(); } 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) { throw runtimeException; } @@ -222,10 +219,10 @@ public final class ModdedForcedDatapack { String currentHash = packsHashOrEmpty(); PublishedState state = publishedState(); 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; } - LOGGER.info("Iris regenerating the forced datapack ({})", reason); + ModdedIrisLog.info("Iris regenerating the forced datapack ({})", reason); regenerate(); return true; } @@ -240,7 +237,7 @@ public final class ModdedForcedDatapack { try { regenerateIfStale(reason); } 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(); @@ -301,7 +298,7 @@ public final class ModdedForcedDatapack { try { return Files.readString(hashFile, StandardCharsets.UTF_8).trim(); } 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 ""; } } @@ -332,7 +329,7 @@ public final class ModdedForcedDatapack { try { hash = packsHash(); } 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 = ""; } packsHashMemo = new HashMemo(hash, now); @@ -412,9 +409,9 @@ public final class ModdedForcedDatapack { if (!presetIds.isEmpty()) { 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) { - 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=, 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=, then restart before creating an Iris world."); } } @@ -425,13 +422,13 @@ public final class ModdedForcedDatapack { try { validation = PackValidator.validateForDatapackBootstrap(sourcePack); } 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); rethrowIfUnrecoverable(validationFailure); return false; } 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(), validation.getBlockingErrors().getFirst()); return false; @@ -447,7 +444,7 @@ public final class ModdedForcedDatapack { try { installed = installPack(sourcePack, fixer, packFolders, packBiomes, packPresetIds); } 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); rethrowIfUnrecoverable(installationFailure); installed = false; @@ -465,7 +462,7 @@ public final class ModdedForcedDatapack { try { clean(packStagingDirectory); } 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); } } @@ -728,7 +725,7 @@ public final class ModdedForcedDatapack { try { clean(backupDirectory); } 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); } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedGenPool.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedGenPool.java index c8d612ed3..7900d980e 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedGenPool.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedGenPool.java @@ -18,8 +18,6 @@ package art.arcane.iris.modded; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.lang.reflect.Field; 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. */ public final class ModdedGenPool { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final long SHUTDOWN_DRAIN_MILLIS = 2_000L; private static final String[] C2ME_MARKERS = { "com.ishland.c2me.base.ModProperties", @@ -116,7 +113,7 @@ public final class ModdedGenPool { if (pool.awaitTermination(SHUTDOWN_DRAIN_MILLIS, TimeUnit.MILLISECONDS)) { 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) { Thread.currentThread().interrupt(); } @@ -131,7 +128,7 @@ public final class ModdedGenPool { if (detected == null) { detected = new ChunkSystem(false, "vanilla"); } - LOGGER.info("Iris chunk system: {} (parallel={}, generation on {})", + ModdedIrisLog.info("Iris chunk system: {} (parallel={}, generation on {})", detected.description(), detected.parallel() ? "yes" : "no", detected.parallel() ? "loader threads" : "Iris gen pool"); @@ -182,7 +179,7 @@ public final class ModdedGenPool { try { value = field.get(null); } 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; } if (value instanceof Boolean flag) { @@ -209,7 +206,7 @@ public final class ModdedGenPool { return flag; } } 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); @@ -219,7 +216,7 @@ public final class ModdedGenPool { return flag; } } 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(); } } 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 { pool = field.get(null); } 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; } if (pool == null) { @@ -281,7 +278,7 @@ public final class ModdedGenPool { return number.intValue(); } } 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; @@ -305,7 +302,7 @@ public final class ModdedGenPool { } catch (NoSuchFieldException e) { return null; } 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; } } @@ -318,7 +315,7 @@ public final class ModdedGenPool { } catch (NoSuchMethodException e) { return null; } 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; } } @@ -336,7 +333,7 @@ public final class ModdedGenPool { try { return Class.forName(name, false, ModdedGenPool.class.getClassLoader()); } 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; } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedImportedFeatureStage.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedImportedFeatureStage.java index b75edfd28..9c4af5637 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedImportedFeatureStage.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedImportedFeatureStage.java @@ -46,8 +46,6 @@ import net.minecraft.world.level.levelgen.RandomSupport; import net.minecraft.world.level.levelgen.WorldgenRandom; import net.minecraft.world.level.levelgen.XoroshiroRandomSource; import net.minecraft.world.level.levelgen.placement.PlacedFeature; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; @@ -76,7 +74,6 @@ import java.util.concurrent.locks.ReentrantLock; * {@link #generationSettings} answers exactly what vanilla's default getter answers. */ final class ModdedImportedFeatureStage { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final String CYCLE_MARKER = "Feature order cycle found"; private static final long NO_GENERATION = Long.MIN_VALUE; @@ -179,7 +176,7 @@ final class ModdedImportedFeatureStage { try { control = NativeFeatureGenerationPolicy.control(engine); } 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()); markInert(generation); return; @@ -193,7 +190,7 @@ final class ModdedImportedFeatureStage { IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) { built = buildTable(engine, control, generation); } 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()); markInert(generation); return; @@ -207,7 +204,7 @@ final class ModdedImportedFeatureStage { // 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. 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(), 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. List> biomes = biomeSource.orderedPossibleBiomes(); 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)); return null; } @@ -245,7 +242,7 @@ final class ModdedImportedFeatureStage { if (message == null || !message.contains(CYCLE_MARKER)) { 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" + " importedFeatures.enabled false.", dimensionKey(engine), message); @@ -276,7 +273,7 @@ final class ModdedImportedFeatureStage { derivative = biomeSource.registeredBiome(derivativeKey); } 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", derivativeKey, irisBiome.getLoadKey()); continue; diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedIrisLog.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedIrisLog.java index 31b2ef6d0..7fcf1193b 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedIrisLog.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedIrisLog.java @@ -54,18 +54,55 @@ public final class ModdedIrisLog { 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) { 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) { 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) { 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) { if (error == null) { error(message); @@ -79,6 +116,34 @@ public final class ModdedIrisLog { 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() { try { IrisSettings settings = IrisSettings.settings != null ? IrisSettings.settings : IrisSettings.get(); @@ -97,4 +162,7 @@ public final class ModdedIrisLog { DEBUG_SETTING_WARNING_LOGGED = true; LOGGER.warn("Iris debug logging setting could not be read", error); } + + record RenderedLog(String message, Throwable error) { + } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedMixinAudit.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedMixinAudit.java index f88bdb479..9aa48318d 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedMixinAudit.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedMixinAudit.java @@ -18,8 +18,6 @@ package art.arcane.iris.modded; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.lang.reflect.Method; import java.util.ArrayList; @@ -40,7 +38,6 @@ import java.util.function.BooleanSupplier; * references and is safe on a dedicated server. */ public final class ModdedMixinAudit { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final AtomicBoolean AUDITED = new AtomicBoolean(false); private static final List EXPECTED = List.of( @@ -95,20 +92,20 @@ public final class ModdedMixinAudit { } } 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)); return; } - LOGGER.error("==============================================================="); - LOGGER.error("Iris mixin audit FAILED on {} ({} dist): {} of {} expected mixin(s) were not applied.", + ModdedIrisLog.error("==============================================================="); + ModdedIrisLog.error("Iris mixin audit FAILED on {} ({} dist): {} of {} expected mixin(s) were not applied.", platform, clientEnvironment ? "client" : "server", missing.size(), missing.size() + applied.size()); 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)."); - LOGGER.error("Entity persistence, custom mob loot, parallel structure safety, or Iris world-type labels are disabled until this is fixed."); - LOGGER.error("==============================================================="); + ModdedIrisLog.error("The mixin config was not registered for this loader (fabric.mod.json mixins, neoforge.mods.toml [[mixins]], forge MixinConfigs manifest attribute)."); + ModdedIrisLog.error("Entity persistence, custom mob loot, parallel structure safety, or Iris world-type labels are disabled until this is fixed."); + ModdedIrisLog.error("==============================================================="); } private static boolean isApplied(ExpectedMixin expected) { @@ -126,7 +123,7 @@ public final class ModdedMixinAudit { } return false; } 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; } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedModConfig.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedModConfig.java index 9387cad8a..dabbf3d7f 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedModConfig.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedModConfig.java @@ -24,11 +24,8 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public final class ModdedModConfig { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final Object LOCK = new Object(); private static volatile ModdedModConfig instance; @@ -128,7 +125,7 @@ public final class ModdedModConfig { json.optLong("mainWorldSeed", defaults.mainWorldSeed), json.optBoolean("mainWorldAutoRestart", defaults.mainWorldAutoRestart)); } 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; } } @@ -145,7 +142,7 @@ public final class ModdedModConfig { Files.createDirectories(file.getParent()); Files.writeString(file, json.toString(4), StandardCharsets.UTF_8); } 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); } } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedParityProbe.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedParityProbe.java index d02e404ac..f99c7a0e3 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedParityProbe.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedParityProbe.java @@ -29,8 +29,6 @@ import art.arcane.iris.spi.PlatformBiome; import art.arcane.iris.spi.PlatformBlockState; import art.arcane.iris.util.project.hunk.Hunk; import net.minecraft.server.MinecraftServer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.nio.charset.StandardCharsets; @@ -48,7 +46,6 @@ import java.util.Map; import java.util.TreeMap; public final class ModdedParityProbe { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final String DIMENSION_KEY = "overworld"; private static final long SEED = 1337L; private static final int BIOME_STEP = 4; @@ -82,7 +79,7 @@ public final class ModdedParityProbe { } 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; } @@ -90,10 +87,10 @@ public final class ModdedParityProbe { try { match = run(server, config); } 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); } @@ -112,7 +109,7 @@ public final class ModdedParityProbe { File packSource = new File(packPath); if (!packSource.isDirectory()) { - LOGGER.error("[parity] pack folder not found: {}", packSource.getAbsolutePath()); + ModdedIrisLog.error("[parity] pack folder not found: {}", packSource.getAbsolutePath()); return false; } @@ -121,14 +118,14 @@ public final class ModdedParityProbe { File workRoot = Files.createTempDirectory("iris-parity").toFile(); File pack = clonePack(packSource, workRoot); - LOGGER.info("[parity] pack: {}", packSource.getAbsolutePath()); - LOGGER.info("[parity] work copy: {}", pack.getAbsolutePath()); - LOGGER.info("[parity] radius: {} ({} chunks)", radius, (2 * radius + 1) * (2 * radius + 1)); + ModdedIrisLog.info("[parity] pack: {}", packSource.getAbsolutePath()); + ModdedIrisLog.info("[parity] work copy: {}", pack.getAbsolutePath()); + ModdedIrisLog.info("[parity] radius: {} ({} chunks)", radius, (2 * radius + 1) * (2 * radius + 1)); IrisData data = IrisData.get(pack); IrisDimension dimension = data.getDimensionLoader().load(DIMENSION_KEY); 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; } @@ -147,7 +144,7 @@ public final class ModdedParityProbe { int minY = dimension.getMinHeight(); int maxY = dimension.getMaxHeight(); 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 goldenChunks = new HashMap<>(); String goldenCombined = null; @@ -173,7 +170,7 @@ public final class ModdedParityProbe { 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(); @@ -198,9 +195,9 @@ public final class ModdedParityProbe { if (!failures.isEmpty()) { 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) { - LOGGER.error("[parity] chunk {},{} error", cx, cz, failure); + ModdedIrisLog.error("[parity] chunk {},{} error", cx, cz, failure); } continue; } @@ -212,9 +209,9 @@ public final class ModdedParityProbe { String golden = goldenChunks.get(key); if (golden != null && !golden.equals(line)) { mismatches.add(key); - LOGGER.warn("[parity] chunk {} MISMATCH", key); - LOGGER.warn("[parity] golden: {}", golden); - LOGGER.warn("[parity] actual: {}", line); + ModdedIrisLog.warn("[parity] chunk {} MISMATCH", key); + ModdedIrisLog.warn("[parity] golden: {}", golden); + ModdedIrisLog.warn("[parity] actual: {}", line); if (mismatches.size() == 1) { diffDeep(cx, cz, blocks, height, minY); } @@ -231,9 +228,9 @@ public final class ModdedParityProbe { boolean match = goldenChunks.isEmpty() ? combinedMatch : (chunkMatch && (radius != 8 || combinedMatch)); 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()); return match; } @@ -246,7 +243,7 @@ public final class ModdedParityProbe { try { Path goldenDump = Path.of(deepDir, cx + "_" + cz + ".txt"); 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; } List golden = Files.readAllLines(goldenDump, StandardCharsets.UTF_8); @@ -267,15 +264,15 @@ public final class ModdedParityProbe { String g = i < golden.size() ? golden.get(i) : ""; String a = i < actual.size() ? actual.get(i) : ""; 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++; } } File out = new File(IrisPlatforms.get().dataFolder("parity"), "deep-" + cx + "_" + cz + ".txt"); 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) { - LOGGER.warn("[parity] deep diff failed", e); + ModdedIrisLog.warn("[parity] deep diff failed", e); } } @@ -367,7 +364,7 @@ public final class ModdedParityProbe { } } else { 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(); } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPrimaryWorldRouter.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPrimaryWorldRouter.java index 3d73650a2..f824f247e 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPrimaryWorldRouter.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedPrimaryWorldRouter.java @@ -21,8 +21,6 @@ package art.arcane.iris.modded; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; @@ -31,7 +29,6 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; public final class ModdedPrimaryWorldRouter { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final int TICK_INTERVAL = 20; private static final Set routed = ConcurrentHashMap.newKeySet(); @@ -96,7 +93,7 @@ public final class ModdedPrimaryWorldRouter { ModdedDimensionManager.teleport(player, server, primary, player.getX(), Double.MIN_VALUE, player.getZ()); routed.add(id); } 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); } } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedProtocolHandler.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedProtocolHandler.java index 297b825cb..f5ba0229e 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedProtocolHandler.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedProtocolHandler.java @@ -19,6 +19,7 @@ package art.arcane.iris.modded; 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.IrisSession; 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.ServerPlayer; import net.minecraft.world.level.chunk.ChunkGenerator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.Objects; import java.util.UUID; @@ -43,7 +42,6 @@ public final class ModdedProtocolHandler { | IrisProtocol.CAPABILITY_CURSOR | IrisProtocol.CAPABILITY_STUDIO; private static final int DIMENSION_SYNC_INTERVAL_TICKS = 5; - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final ConcurrentHashMap SESSION_ENGINES = new ConcurrentHashMap<>(); private static final ConcurrentHashMap SESSION_LEVELS = new ConcurrentHashMap<>(); @@ -53,6 +51,7 @@ public final class ModdedProtocolHandler { private static volatile IrisSessionRegistry registry; private static volatile IrisProtocolServer protocolServer; private static volatile ModdedProtocolTransport transport; + private static volatile IrisCursorRequestService cursorRequests; private static volatile IrisVisionRequestService visionRequests; private static int dimensionSyncTicks; @@ -80,11 +79,14 @@ public final class ModdedProtocolHandler { return engine == null || engine.isClosed() ? null : engine; }; protocol.setEngineResolver(engineResolver); + IrisCursorRequestService cursorService = IrisCursorRequestService.create(engineResolver, sessionRegistry); + protocol.setCursorInfoHandler(cursorService); IrisVisionRequestService visionService = IrisVisionRequestService.create(engineResolver, sessionRegistry); protocol.setVisionTileHandler(visionService); registry = sessionRegistry; transport = serverTransport; protocolServer = protocol; + cursorRequests = cursorService; visionRequests = visionService; IrisServices.register(IrisProtocolServer.class, protocol); if (server.getPlayerList() == null) { @@ -98,10 +100,14 @@ public final class ModdedProtocolHandler { public static void stop() { IrisServices.remove(IrisProtocolServer.class); IrisSessionRegistry current = registry; + IrisCursorRequestService cursor = cursorRequests; IrisVisionRequestService vision = visionRequests; if (current != null) { for (IrisSession session : current.all()) { current.unregister(session.id()); + if (cursor != null) { + cursor.clearSession(session.id()); + } if (vision != null) { vision.clearSession(session.id()); } @@ -114,6 +120,7 @@ public final class ModdedProtocolHandler { registry = null; protocolServer = null; transport = null; + cursorRequests = null; visionRequests = null; } @@ -147,6 +154,10 @@ public final class ModdedProtocolHandler { if (current != null) { current.unregister(sessionId); } + IrisCursorRequestService cursor = cursorRequests; + if (cursor != null) { + cursor.clearSession(sessionId); + } IrisVisionRequestService vision = visionRequests; if (vision != null) { vision.clearSession(sessionId); @@ -229,7 +240,7 @@ public final class ModdedProtocolHandler { Engine engine = generator.engineIfBound(); return engine == null || engine.isClosed() ? null : engine; } 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; } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedScheduler.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedScheduler.java index c3f46d4c6..76ffee8e9 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedScheduler.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedScheduler.java @@ -21,8 +21,6 @@ package art.arcane.iris.modded; import art.arcane.iris.spi.PlatformScheduler; import art.arcane.iris.spi.PlatformWorld; import net.minecraft.server.MinecraftServer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; @@ -41,7 +39,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; 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 long ASYNC_KEEP_ALIVE_SECONDS = 30L; private static final int ASYNC_BACKLOG_WARN = 8192; @@ -85,10 +82,10 @@ public final class ModdedScheduler implements PlatformScheduler { rejectionAwareTask.reject(); } if (executor.isShutdown()) { - LOGGER.debug("Iris async task dropped: scheduler is shut down"); + ModdedIrisLog.debug("Iris async task dropped: scheduler is shut down"); 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()); }; } @@ -217,7 +214,7 @@ public final class ModdedScheduler implements PlatformScheduler { if (now - last < ASYNC_BACKLOG_WARN_INTERVAL_MILLIS || !lastBacklogWarnAt.compareAndSet(last, now)) { 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() { @@ -262,7 +259,7 @@ public final class ModdedScheduler implements PlatformScheduler { try { task.run(); } catch (Throwable error) { - LOGGER.error("Iris scheduled task failed", error); + ModdedIrisLog.error("Iris scheduled task failed", error); } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedServerLevels.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedServerLevels.java index 576f2ce54..3e0680af0 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedServerLevels.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedServerLevels.java @@ -23,8 +23,6 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; import net.minecraft.world.level.storage.LevelStorageSource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ConcurrentModificationException; import java.util.LinkedHashMap; @@ -35,7 +33,6 @@ import java.util.concurrent.Executor; import java.util.function.Consumer; public final class ModdedServerLevels implements ModdedServerAccess { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final int CAPTURE_ATTEMPTS = 16; private static volatile Snapshot snapshot; @@ -125,7 +122,7 @@ public final class ModdedServerLevels implements ModdedServerAccess { 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; return current != null && current.server() == server ? current : null; } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedServiceManager.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedServiceManager.java index 835a612f3..222f1d2ca 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedServiceManager.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedServiceManager.java @@ -21,14 +21,11 @@ package art.arcane.iris.modded; import art.arcane.iris.modded.service.ModdedService; import art.arcane.iris.modded.service.ModdedTickableService; import net.minecraft.server.MinecraftServer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.LinkedHashMap; import java.util.Map; public final class ModdedServiceManager { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private final Map, ModdedService> services = new LinkedHashMap<>(); private boolean enabled = false; @@ -103,7 +100,7 @@ public final class ModdedServiceManager { service.onDisable(); } catch (Throwable serviceFailure) { 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) { failure = serviceFailure; } else if (serviceFailure != failure) { @@ -113,7 +110,7 @@ public final class ModdedServiceManager { } enabled = false; 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 { service.onServerTick(server); } 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) { 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) { - 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) { return runtimeException; } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedStartup.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedStartup.java index bc60b372f..13194ef1a 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedStartup.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedStartup.java @@ -29,8 +29,6 @@ import net.minecraft.commands.Commands; import net.minecraft.network.chat.Component; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -43,7 +41,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; public final class ModdedStartup { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final AtomicBoolean PREPARED = new AtomicBoolean(false); private static final AtomicBoolean STARTED = new AtomicBoolean(false); @@ -76,7 +73,7 @@ public final class ModdedStartup { } if (!PackDirectoryResolver.listVisiblePackDirectories(legacy).isEmpty()) { 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()); return; } @@ -85,7 +82,7 @@ public final class ModdedStartup { legacy.delete(); } } 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 { ModdedForcedDatapack.regenerateIfStale("boot"); } 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 packDirs = PackDirectoryResolver.listVisiblePackDirectories(packsRoot); PackValidationRegistry.clear(); 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=", + ModdedIrisLog.info("Iris found no packs to validate under {}; install one with /iris download pack=overworld, /iris download pack=underworld, or /iris download link=", packsRoot.getAbsolutePath()); return; } @@ -142,19 +139,19 @@ public final class ModdedStartup { PackValidationResult result = PackValidator.validate(packDir); PackValidationRegistry.publish(result); 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.getBlockingErrors().getFirst()); } 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()) { - LOGGER.warn(" [{}] {}", result.getPackName(), warning); + ModdedIrisLog.warn(" [{}] {}", result.getPackName(), warning); } } else { - LOGGER.info("Iris pack '{}' validated.", result.getPackName()); + ModdedIrisLog.info("Iris pack '{}' validated.", result.getPackName()); } } 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(); if (detail == null || detail.isBlank()) { detail = e.getClass().getSimpleName(); @@ -190,7 +187,7 @@ public final class ModdedStartup { } catch (BrokenPackException e) { throw 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(); if (detail == null || detail.isBlank()) { detail = e.getClass().getSimpleName(); @@ -220,17 +217,17 @@ public final class ModdedStartup { try { ModdedDimensionManager.create(server, dimension.id(), dimension.pack(), dimension.dimension(), dimension.seed()); 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(), System.currentTimeMillis() - dimensionStartedAt); } 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) { 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); } @@ -245,7 +242,7 @@ public final class ModdedStartup { } } } 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 newest; diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldCheck.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldCheck.java index 896402805..3e17f4790 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldCheck.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldCheck.java @@ -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.LevelChunkSection; import net.minecraft.world.level.levelgen.Heightmap; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.nio.charset.StandardCharsets; 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_INTERVAL_MILLIS = 250L; 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 // 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; @@ -100,7 +97,7 @@ public final class ModdedWorldCheck { } 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; } @@ -115,15 +112,15 @@ public final class ModdedWorldCheck { } )).get(SERVER_TASK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { - LOGGER.error("[worldcheck] coordinator interrupted", e); + ModdedIrisLog.error("[worldcheck] coordinator interrupted", e); Thread.currentThread().interrupt(); } 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) { - LOGGER.error("[worldcheck] check failed", e); + ModdedIrisLog.error("[worldcheck] check failed", e); } finally { 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; if (serverRef != null && stopRequested.get()) { awaitStopAndExit(() -> serverRef.halt(true), resultCode, processExit); @@ -138,12 +135,12 @@ public final class ModdedWorldCheck { try { exitCode = check.getAsBoolean() ? EXIT_PASS : EXIT_FAILURE; } catch (Throwable e) { - LOGGER.error("[worldcheck] check failed", e); + ModdedIrisLog.error("[worldcheck] check failed", e); } try { requestStop.run(); } catch (Throwable e) { - LOGGER.error("[worldcheck] server stop request failed", e); + ModdedIrisLog.error("[worldcheck] server stop request failed", e); return EXIT_FAILURE; } return exitCode; @@ -159,7 +156,7 @@ public final class ModdedWorldCheck { awaitStop.run(); } catch (Throwable e) { exitCode = EXIT_FAILURE; - LOGGER.error("[worldcheck] waiting for server shutdown failed", e); + ModdedIrisLog.error("[worldcheck] waiting for server shutdown failed", e); } finally { if (interrupted) { Thread.currentThread().interrupt(); @@ -171,25 +168,25 @@ public final class ModdedWorldCheck { private static WorldCheckPreparation run(MinecraftServer server) { ServerLevel level = targetLevel(server); 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, new NativeStructureGate(false, 0, false, null)); } String levelId = level.dimension().identifier().toString(); 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 ? iris : null; boolean irisGenerator = generator != null; if (!irisGenerator) { - LOGGER.error("[worldcheck] {} is NOT using IrisModdedChunkGenerator", levelId); + ModdedIrisLog.error("[worldcheck] {} is NOT using IrisModdedChunkGenerator", levelId); } boolean dimensionTypeOk = generator != null && WorldCheckDimensionContract.checkDimensionType(level, generator); 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(); List samples = new ArrayList<>(); @@ -210,9 +207,9 @@ public final class ModdedWorldCheck { } 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); ChunkAccess zeroChunk = level.getChunk(0, 0); @@ -230,16 +227,16 @@ public final class ModdedWorldCheck { 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); boolean sectionsOk = nonEmptySections >= 4; boolean varietyOk = columnKeys.size() >= 2 || surfaceKeys.size() >= 2; 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) { - 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); @@ -262,7 +259,7 @@ public final class ModdedWorldCheck { WorldCheckPredicates.qaEvent("village_poi_metric", "village", poiOk, "inBounds=" + poi.inBounds() + ",outOfBounds=" + poi.outOfBounds()); if (!poiOk) { - LOGGER.error("[worldcheck] village POI audit failed: inBounds={} outOfBounds={}", + ModdedIrisLog.error("[worldcheck] village POI audit failed: inBounds={} outOfBounds={}", poi.inBounds(), poi.outOfBounds()); } } else { @@ -271,12 +268,12 @@ public final class ModdedWorldCheck { int passed = structureGate.nonVillagePassed() + (structureGate.villagePassBeforePoi() && poiOk ? 1 : 0); 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()); WorldCheckPredicates.qaEvent("structure_aggregate", "all", structurePass, "passed=" + passed + ",total=" + WorldCheckStructureAudit.STRUCTURE_CHECKS.size()); boolean pass = preparation.nonStructurePass() && structurePass; - LOGGER.info("[worldcheck] {}", pass ? "PASS" : "FAIL"); + ModdedIrisLog.info("[worldcheck] {}", pass ? "PASS" : "FAIL"); WorldCheckPredicates.qaEvent("worldcheck_final", "all", pass, "structures=" + WorldCheckStructureAudit.STRUCTURE_CHECKS.size() + ",terrain=" + preparation.terrainOk() @@ -295,7 +292,7 @@ public final class ModdedWorldCheck { if (requested != null) { return requested; } - LOGGER.error("[worldcheck] requested dimension '{}' is not loaded", target); + ModdedIrisLog.error("[worldcheck] requested dimension '{}' is not loaded", target); return null; } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldEngines.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldEngines.java index 56e7935db..e35c80da0 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldEngines.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedWorldEngines.java @@ -31,8 +31,6 @@ import art.arcane.iris.modded.command.ModdedGuiHost; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.storage.LevelResource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.util.ArrayList; @@ -42,7 +40,6 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; public final class ModdedWorldEngines { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final ConcurrentHashMap ENGINES = new ConcurrentHashMap<>(); private ModdedWorldEngines() { @@ -64,7 +61,7 @@ public final class ModdedWorldEngines { try { evictOrThrow(level); } 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. 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) { @@ -111,7 +108,7 @@ public final class ModdedWorldEngines { IrisData data = IrisData.openRuntime(packDir); IrisDimension dimension = data.getDimensionLoader().load(dimensionKey); 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); throw new IllegalStateException("Iris dimension '" + dimensionKey + "' missing from pack " + packDir.getAbsolutePath()); } @@ -142,7 +139,7 @@ public final class ModdedWorldEngines { 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()); return engine; } @@ -182,11 +179,11 @@ public final class ModdedWorldEngines { return packDir; } - LOGGER.error("==============================================================="); - LOGGER.error("Iris pack '{}' is not installed.", pack); - LOGGER.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); - LOGGER.error("==============================================================="); + ModdedIrisLog.error("==============================================================="); + ModdedIrisLog.error("Iris pack '{}' is not installed.", pack); + ModdedIrisLog.error("Expected a pack folder at: {}", packDir.getAbsolutePath()); + ModdedIrisLog.error("Install an Iris pack there (the folder must contain dimensions/{}.json) and restart the server.", dimensionKey); + ModdedIrisLog.error("==============================================================="); throw new IllegalStateException("Iris pack not installed: " + packDir.getAbsolutePath()); } @@ -215,9 +212,9 @@ public final class ModdedWorldEngines { + level.dimension().identifier()); } } - LOGGER.info("Iris engine closed for {}", level.dimension().identifier()); + ModdedIrisLog.info("Iris engine closed for {}", level.dimension().identifier()); } 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) { failure = e; } else if (e != failure) { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckDimensionContract.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckDimensionContract.java index 22af5f586..89bcbbf84 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckDimensionContract.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckDimensionContract.java @@ -25,11 +25,8 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.Items; import net.minecraft.world.level.dimension.DimensionType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; final class WorldCheckDimensionContract { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private WorldCheckDimensionContract() { } @@ -44,13 +41,13 @@ final class WorldCheckDimensionContract { + ",levelMinY=" + level.getMinY() + ",levelHeight=" + level.getHeight(); WorldCheckPredicates.qaEvent("dimension_type", dimension.getLoadKey(), pass, detail); if (!pass) { - LOGGER.error("[worldcheck] dimension type mismatch for {}: {}", dimension.getLoadKey(), detail); + ModdedIrisLog.error("[worldcheck] dimension type mismatch for {}: {}", dimension.getLoadKey(), detail); } else { - LOGGER.info("[worldcheck] dimension type contract: {}", detail); + ModdedIrisLog.info("[worldcheck] dimension type contract: {}", detail); } return pass; } 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, "validationError=" + error.getClass().getSimpleName() + ":" + error.getMessage()); return false; @@ -102,7 +99,7 @@ final class WorldCheckDimensionContract { WorldCheckPredicates.qaEvent("entity_mixin", "persistence", pass, "vanilla=" + vanillaSave + ",suppressed=" + suppressed + ",restored=" + restored); 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; } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckPredicates.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckPredicates.java index 12acad76b..afce7142d 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckPredicates.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckPredicates.java @@ -20,14 +20,11 @@ package art.arcane.iris.modded; import art.arcane.iris.engine.framework.StructureVerticalBounds; import art.arcane.iris.modded.WorldCheckStructureAudit.StructureCheck; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; final class WorldCheckPredicates { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private WorldCheckPredicates() { } @@ -39,7 +36,7 @@ final class WorldCheckPredicates { } 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) { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckStructureAudit.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckStructureAudit.java index dcd7630e5..a8341cf5f 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckStructureAudit.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/WorldCheckStructureAudit.java @@ -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.RandomSpreadStructurePlacement; import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; @@ -77,7 +75,6 @@ final class WorldCheckStructureAudit { private static final int MAX_FOOTPRINT_CHUNKS = 96; private static final int MAX_START_REFERENCE_CHUNKS = 16; private static final int MAX_STRUCTURE_CANDIDATES = 1024; - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private WorldCheckStructureAudit() { } @@ -123,13 +120,13 @@ final class WorldCheckStructureAudit { } } 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); WorldCheckPredicates.qaEvent("structure_registry", check.label(), registryOk, "resolved=" + registered.size() + ",expected=" + check.registryKeys().size() + ",keys=" + String.join("|", registeredKeys)); 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", "structure_start_reference", "structure_footprint", "structure_material", "structure_block_entity"); @@ -149,12 +146,12 @@ final class WorldCheckStructureAudit { } } 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, "reachable=" + reachable.size() + ",registered=" + registered.size() + ",keys=" + String.join("|", reachableKeys)); 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", "structure_footprint", "structure_material", "structure_block_entity"); return new StructureCheckResult(false, null); @@ -170,7 +167,7 @@ final class WorldCheckStructureAudit { "method=placement_candidates,millis=" + locateMillis + ",radius=" + check.locateRadius() + ",result=" + (foundKey == null ? "none" : foundKey)); 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); WorldCheckPredicates.emitSkipped(check, "locate", "structure_start_reference", "structure_footprint", "structure_material", "structure_block_entity"); @@ -178,11 +175,11 @@ final class WorldCheckStructureAudit { } 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.locateRadius(), foundKey); 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", "structure_material", "structure_block_entity"); return new StructureCheckResult(false, null); @@ -196,13 +193,13 @@ final class WorldCheckStructureAudit { boolean validStart = start != null && start.isValid(); int references = targetChunk.getReferencesForStructure(structure).size(); 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); WorldCheckPredicates.qaEvent("structure_start_reference", check.label(), startReferenceOk, "chunk=" + chunkX + "," + chunkZ + ",validStart=" + validStart + ",references=" + references); 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); WorldCheckPredicates.emitSkipped(check, "start_reference", "structure_footprint", "structure_material", "structure_block_entity"); @@ -221,7 +218,7 @@ final class WorldCheckStructureAudit { "configured=" + decision.yShift() + ",applied=" + (appliedShift == null ? "unrecorded" : appliedShift)); 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); } @@ -229,7 +226,7 @@ final class WorldCheckStructureAudit { boolean footprintOk = footprint.inspectedChunks() > 0 && footprint.evidenceChunks() == footprint.inspectedChunks() && footprint.coveredPieces() == footprint.totalPieces(); - LOGGER.info("[worldcheck] {} footprint: chunks={}/{} evidence={} pieces={}/{}", + ModdedIrisLog.info("[worldcheck] {} footprint: chunks={}/{} evidence={} pieces={}/{}", check.label(), footprint.inspectedChunks(), footprint.availableChunks(), footprint.evidenceChunks(), footprint.coveredPieces(), footprint.totalPieces()); WorldCheckPredicates.qaEvent("structure_footprint", check.label(), footprintOk, @@ -239,7 +236,7 @@ final class WorldCheckStructureAudit { boolean materialOk = WorldCheckPredicates.hasCharacteristicMaterialEvidence(footprint.characteristicBlocks(), footprint.characteristicChunks(), footprint.materialScannedChunks()); - LOGGER.info("[worldcheck] {} material: blocks={} chunks={}/{}", + ModdedIrisLog.info("[worldcheck] {} material: blocks={} chunks={}/{}", check.label(), footprint.characteristicBlocks(), footprint.characteristicChunks(), footprint.materialScannedChunks()); WorldCheckPredicates.qaEvent("structure_material", check.label(), materialOk, @@ -250,7 +247,7 @@ final class WorldCheckStructureAudit { if (check.label().equals("mansion")) { boolean overlap = footprint.vegetationBlocks() > 0; 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); WorldCheckPredicates.qaEvent("mansion_vegetation_metric", check.label(), vegetationOk, "remainingLogsOrLeaves=" + footprint.vegetationBlocks() + ",columns=" @@ -261,7 +258,7 @@ final class WorldCheckStructureAudit { PendingVillagePoi pendingPoi = null; if (check.label().equals("village")) { 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.foundationColumns(), footprint.foundationGapColumns()); WorldCheckPredicates.qaEvent("village_foundation_metric", check.label(), foundationOk, @@ -272,26 +269,26 @@ final class WorldCheckStructureAudit { } 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(), footprint.blockEntityStates() - footprint.blockEntitiesPresent()); WorldCheckPredicates.qaEvent("structure_block_entity", check.label(), blockEntityOk, "states=" + footprint.blockEntityStates() + ",present=" + footprint.blockEntitiesPresent() + ",missing=" + (footprint.blockEntityStates() - footprint.blockEntitiesPresent())); if (!footprintOk) { - LOGGER.error("[worldcheck] {} structure footprint is incomplete", check.label()); + ModdedIrisLog.error("[worldcheck] {} structure footprint is incomplete", check.label()); } 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) { - 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) { - LOGGER.error("[worldcheck] mansion vegetation still intersects the generated structure footprint"); + ModdedIrisLog.error("[worldcheck] mansion vegetation still intersects the generated structure footprint"); } 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 && vegetationOk && foundationOk; diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/api/ModdedCustomContentRegistry.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/api/ModdedCustomContentRegistry.java index 61e8a2890..e0338846c 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/api/ModdedCustomContentRegistry.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/api/ModdedCustomContentRegistry.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.api; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.modded.ModdedBlockResolution; import net.minecraft.core.BlockPos; @@ -25,8 +26,6 @@ import net.minecraft.resources.Identifier; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.block.state.BlockState; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collection; @@ -57,7 +56,6 @@ import java.util.concurrent.CopyOnWriteArrayList; * next provider. */ public final class ModdedCustomContentRegistry { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final List PROVIDERS = new CopyOnWriteArrayList<>(); private static final Map CUSTOM_BLOCKS = new ConcurrentHashMap<>(); private static volatile boolean scanned = false; @@ -77,14 +75,14 @@ public final class ModdedCustomContentRegistry { } Identifier identifier = Identifier.tryParse(namespace + ":" + key); 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; } BlockState parsed; try { parsed = ModdedBlockResolution.strictParse(state).handle(); } 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; } DiscoveryBatch activeBatch = discoveryBatch; @@ -93,7 +91,7 @@ public final class ModdedCustomContentRegistry { } else { 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) { 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; } } @@ -120,9 +118,9 @@ public final class ModdedCustomContentRegistry { try { provider.init(); } 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); scanned = true; 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, previousDiscoveryComplete, true); @@ -171,7 +169,7 @@ public final class ModdedCustomContentRegistry { failure.addSuppressed(rollbackFailure); } } - LOGGER.warn("Iris custom content provider discovery failed at {}", + ModdedIrisLog.warn("Iris custom content provider discovery failed at {}", providerIdentity(failingProvider), failure); if (failure instanceof RuntimeException runtimeException) { throw runtimeException; @@ -242,7 +240,7 @@ public final class ModdedCustomContentRegistry { try { types = provider.getTypes(type); } 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; } if (types == null) { @@ -288,7 +286,7 @@ public final class ModdedCustomContentRegistry { return resolved; } } 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; @@ -302,7 +300,7 @@ public final class ModdedCustomContentRegistry { public static void processBlockPlacement(Engine engine, ServerLevel level, BlockPos position, String key) { Identifier base = parseIdentifier(key); 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; } Map state = parseState(key); @@ -314,11 +312,11 @@ public final class ModdedCustomContentRegistry { provider.processBlockPlacement(new ModdedBlockPlacementContext( engine, level, position.immutable(), base, state, level.getBlockState(position))); } 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; } - 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; } } 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; @@ -439,7 +437,7 @@ public final class ModdedCustomContentRegistry { "Iris custom content provider returned a null mod id"); for (ModdedDataProvider existing : providers) { 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; } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java index 17cb8ded6..92cf23f3e 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/IrisModdedCommands.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.localization.IrisLanguage; 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.ServerPlayer; import net.minecraft.world.level.chunk.ChunkGenerator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -67,7 +66,6 @@ import java.util.concurrent.TimeUnit; import art.arcane.volmlib.util.localization.MessageArgument; public final class IrisModdedCommands { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final long DOWNLOAD_SHUTDOWN_POLL_SECONDS = 15L; private static final Object DOWNLOAD_MONITOR = new Object(); @@ -82,7 +80,7 @@ public final class IrisModdedCommands { LiteralCommandNode root = dispatcher.register(ModdedCommandTree.rootTree()); dispatcher.register(Commands.literal("ir").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() { @@ -108,7 +106,7 @@ public final class IrisModdedCommands { try { if (!execution.await(DOWNLOAD_SHUTDOWN_POLL_SECONDS, TimeUnit.SECONDS) && !warned) { warned = true; - LOGGER.warn(execution.isPublishing() + ModdedIrisLog.warn(execution.isPublishing() ? "Waiting for atomic pack publication to finish 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); } catch (Throwable error) { 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( ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE, MessageArgument.untrusted("pack", target), @@ -363,7 +361,7 @@ public final class IrisModdedCommands { } if (!accepted) { 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( ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE, MessageArgument.untrusted("pack", target), @@ -416,7 +414,7 @@ public final class IrisModdedCommands { dispatchDownloadFeedback(source, () -> fail(source, error.getMessage())); return; } 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( ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE, @@ -512,7 +510,7 @@ public final class IrisModdedCommands { try { return irisGenerator.commandEngine(); } 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; } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandSuggestions.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandSuggestions.java index ed5200a3d..1c4a79d2a 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandSuggestions.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandSuggestions.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.spi.IrisPlatforms; import art.arcane.iris.core.pack.PackDirectoryResolver; 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.server.level.ServerLevel; import net.minecraft.world.level.levelgen.structure.Structure; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.util.ArrayList; @@ -66,7 +65,6 @@ final class ModdedCommandSuggestions { static final SuggestionProvider PACK_NAMES = (CommandContext context, SuggestionsBuilder builder) -> suggestPackNames(context, builder); static final SuggestionProvider DIMENSION_NAMES = (CommandContext 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 Set REPORTED_TAB_FAILURES = ConcurrentHashMap.newKeySet(); 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) { 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) { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDatapackCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDatapackCommands.java index ad3b7d92f..43fe5d559 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDatapackCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDatapackCommands.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.nms.datapack.DataVersion; 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.world.level.dimension.DimensionType; import net.minecraft.world.level.storage.LevelResource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; 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.volmlib.util.localization.MessageArgument; public final class ModdedDatapackCommands { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final Predicate GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); private static final String WORLD_PACK_NAME = "iris"; @@ -170,7 +168,7 @@ public final class ModdedDatapackCommands { try { json = dimension.getDimensionType().toJson(DataVersion.getLatest().get()); } 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())))); continue; } @@ -180,7 +178,7 @@ public final class ModdedDatapackCommands { Files.writeString(output.toPath(), json, StandardCharsets.UTF_8); written.add(output.getPath()); } 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())))); } } @@ -204,7 +202,7 @@ public final class ModdedDatapackCommands { Files.writeString(mcmeta.toPath(), meta, StandardCharsets.UTF_8); written.add(mcmeta.getPath()); } 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())))); return 0; } @@ -237,7 +235,7 @@ public final class ModdedDatapackCommands { } } } 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); } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDeveloperCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDeveloperCommands.java index d0ec4e459..2c6ccd6e8 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDeveloperCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDeveloperCommands.java @@ -18,13 +18,12 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.spi.IrisPlatforms; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.net.InetAddress; 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.volmlib.util.localization.MessageArgument; final class ModdedDeveloperCommands { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final Predicate GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); private ModdedDeveloperCommands() { @@ -71,7 +69,7 @@ final class ModdedDeveloperCommands { } return 1; } 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()))); return 0; } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDustRevealer.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDustRevealer.java index e1857c67f..5a6ccd212 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDustRevealer.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDustRevealer.java @@ -18,6 +18,7 @@ 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.RuntimeUiMessages; 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.SoundSource; import net.minecraft.world.level.biome.Biome; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ArrayDeque; import java.util.ArrayList; @@ -62,7 +61,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; public final class ModdedDustRevealer { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final int MAX_HITS = 2_048; private static final int PARTICLE_BATCH_SIZE = 64; 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) { - 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(() -> { if (ACTIVE_RUNS.remove(run.playerId(), run)) { run.player().sendSystemMessage(Component.literal( @@ -415,7 +413,7 @@ public final class ModdedDustRevealer { } } } 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); } return null; @@ -458,7 +456,7 @@ public final class ModdedDustRevealer { try { return supplier.get(); } catch (Throwable error) { - LOGGER.error("Iris dust {} failed", operation, error); + ModdedIrisLog.error("Iris dust {} failed", operation, error); return null; } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedEditCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedEditCommands.java index fbcfa1876..39dcd2d30 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedEditCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedEditCommands.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.core.gui.GuiHost; import art.arcane.iris.core.loader.IrisRegistrant; 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.core.BlockPos; import net.minecraft.server.level.ServerPlayer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.awt.Desktop; import java.io.File; final class ModdedEditCommands { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private ModdedEditCommands() { } @@ -123,7 +121,7 @@ final class ModdedEditCommands { try { Desktop.getDesktop().open(file); } 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()))); return 0; } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedGoldenHash.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedGoldenHash.java index 6b439b2ef..45e19e25a 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedGoldenHash.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedGoldenHash.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.core.runtime.GoldenHashEngine; import art.arcane.iris.engine.framework.Engine; 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.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.util.concurrent.atomic.AtomicBoolean; @@ -47,7 +46,6 @@ public final class ModdedGoldenHash { VERIFY } - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final AtomicBoolean ACTIVE = new AtomicBoolean(false); private final CommandSourceStack source; @@ -96,7 +94,7 @@ public final class ModdedGoldenHash { MessageArgument.trusted("threads", Math.max(1, threads)), 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()); Thread thread = new Thread(() -> { try { @@ -182,7 +180,7 @@ public final class ModdedGoldenHash { @Override 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( RuntimeProgressMessages.GOLDEN_CHUNK_FAILED, MessageArgument.trusted("x", chunkX), diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedLocateCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedLocateCommands.java index 2eaada62f..5a808b624 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedLocateCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedLocateCommands.java @@ -18,6 +18,7 @@ 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.ModdedCommandMessages; 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.levelgen.Heightmap; import net.minecraft.world.level.levelgen.structure.Structure; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.Optional; import java.util.Set; @@ -64,7 +63,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; final class ModdedLocateCommands { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final long LOCATE_TIMEOUT_MS = 120000L; private static final int NATIVE_STRUCTURE_LOCATE_RADIUS = 100; private static final ConcurrentHashMap> ACTIVE_LOCATE_REQUESTS = new ConcurrentHashMap<>(); @@ -260,7 +258,7 @@ final class ModdedLocateCommands { server.execute(() -> teleportToStructure(source, level, player, targetX, targetY, targetZ, "Iris-placed structure " + key)); } 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())))); } }, "Iris Structure Locator"); @@ -302,7 +300,7 @@ final class ModdedLocateCommands { teleportToStructure(source, level, player, targetX, targetY, targetZ, "native structure " + target.key()); } 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()))); } } @@ -518,7 +516,7 @@ final class ModdedLocateCommands { return; } if (failure != null) { - LOGGER.error("Iris locate failed for {}", label, failure); + ModdedIrisLog.error("Iris locate failed for {}", label, failure); server.execute(() -> { if (ACTIVE_LOCATE_REQUESTS.remove(playerId, search)) { IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCH_FAILED_2, MessageArgument.untrusted("failure", failure))); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java index 0e2607b62..1c3792707 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.tools.TreePlausibilizeBatch; 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.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; 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.volmlib.util.localization.MessageArgument; public final class ModdedObjectCommands { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final Predicate GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); private static final long MAX_SAVE_VOLUME = 500000L; private static final long MAX_AUTOSELECT_VOLUME = 100000L; @@ -313,7 +311,7 @@ public final class ModdedObjectCommands { try { object.write(file); } 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) { // Never leave a 0-byte claim file permanently blocking non-overwrite saves. file.delete(); @@ -332,7 +330,7 @@ public final class ModdedObjectCommands { 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)))); - 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; } @@ -378,7 +376,7 @@ public final class ModdedObjectCommands { String blockKey = BuiltInRegistries.BLOCK.getKey(state.getBlock()).toString(); return ModdedTileData.capture(blockKey, snbt); } 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; } } @@ -391,7 +389,7 @@ public final class ModdedObjectCommands { try { object = IrisData.loadAnyObject(key, engine == null ? null : engine.getData()); } 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) { 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 { object.place(target.getX(), target.getY() + object.getCenter().getY(), target.getZ(), placer, placement, new RNG(), null); } 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()); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_PASTE_FAILED_PARTIAL_CHANGES_RECORDED_UNDO, MessageArgument.untrusted("value", e.getClass().getSimpleName()))); return 0; @@ -428,7 +426,7 @@ public final class ModdedObjectCommands { ModdedObjectUndo.record(owner, level, placer.undoSnapshot()); 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))); - 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()); return placer.writes() > 0 ? 1 : 0; } @@ -608,7 +606,7 @@ public final class ModdedObjectCommands { try { object = IrisData.loadAnyObject(key, engine == null ? null : engine.getData()); } catch (Throwable e) { - LOGGER.error("Iris object load failed for {}", key, e); + ModdedIrisLog.error("Iris object load failed for {}", key, e); } if (object == null) { IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_UNKNOWN_OBJECT, MessageArgument.untrusted("key", key))); @@ -648,7 +646,7 @@ public final class ModdedObjectCommands { try { object = IrisData.loadAnyObject(key, engine == null ? null : engine.getData()); } catch (Throwable e) { - LOGGER.error("Iris object load failed for {}", key, e); + ModdedIrisLog.error("Iris object load failed for {}", key, e); } if (object == null) { 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 { object.write(file); } 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())))); return 0; } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectPlacer.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectPlacer.java index cf070912a..8e320183f 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectPlacer.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectPlacer.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.engine.framework.Engine; 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.levelgen.Heightmap; import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; final class ModdedObjectPlacer implements IObjectPlacer { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final int DEFAULT_FLUID_HEIGHT = 63; private final ServerLevel level; @@ -199,7 +197,7 @@ final class ModdedObjectPlacer implements IObjectPlacer { } restoredTiles++; } 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++; } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectUndo.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectUndo.java index 236f503ca..c3f5426ee 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectUndo.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectUndo.java @@ -18,13 +18,12 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.modded.ModdedIrisLog; import net.minecraft.core.BlockPos; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ArrayDeque; import java.util.Deque; @@ -34,7 +33,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; public final class ModdedObjectUndo { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final int MAX_ENTRIES_PER_OWNER = 32; private static final ConcurrentHashMap> UNDOS = new ConcurrentHashMap<>(); private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false); @@ -48,7 +46,7 @@ public final class ModdedObjectUndo { public static void init() { 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. MinecraftServer server = entry.level().getServer(); 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()); continue; } @@ -103,10 +101,10 @@ public final class ModdedObjectUndo { entry.level().setBlock(block.getKey(), block.getValue(), Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE); writes++; } 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++; } return reverted; diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPackCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPackCommands.java index 614fa74de..10a7dd589 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPackCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPackCommands.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.spi.IrisPlatforms; import art.arcane.iris.core.pack.PackDirectoryResolver; 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.Commands; import net.minecraft.server.MinecraftServer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; 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.volmlib.util.localization.MessageArgument; public final class ModdedPackCommands { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final Predicate GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); private ModdedPackCommands() { @@ -139,7 +137,7 @@ public final class ModdedPackCommands { } server.execute(() -> report(source, result)); } 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()))))); broken++; } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java index 2d3930a0c..ffaf14f4c 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPregenMethod.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.pregenerator.PregenListener; 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.TicketType; import net.minecraft.world.level.ChunkPos; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -48,7 +47,6 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; 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 int ADAPTIVE_TIMEOUT_STEP = 3; private static final long ADAPTIVE_RECOVERY_INTERVAL = 64L; @@ -106,7 +104,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod { @Override public void init() { 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(), sync ? "sync" : "async", sync ? 1 : maxInFlight, @@ -114,7 +112,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod { describeWorkerPool(), ModdedGenPool.describeChunkSystem()); 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(); } } - 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()); if (deferFinalSaveIfRequested()) { return; @@ -245,7 +243,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod { } long remainingNanos = deadline - System.nanoTime(); 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; } long waitMillis = Math.max(1L, Math.min(FINAL_SAVE_POLL_MILLIS, @@ -260,7 +258,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod { continue; } catch (ExecutionException e) { 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 " + level.dimension().identifier(), cause); } @@ -337,7 +335,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod { try { Object result = loadFuture.get(timeoutSeconds, TimeUnit.SECONDS); 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); 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. if (level.getServer().isStopped() || !level.getServer().isRunning()) { 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); ModdedPregenJob.stop(); @@ -401,7 +399,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod { return; } 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); return; } @@ -425,10 +423,10 @@ public final class ModdedPregenMethod implements PregeneratorMethod { private void logChunkFailure(int x, int z, Throwable failure) { Throwable cause = unwrap(failure); 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; } - LOGGER.warn("Iris pregen chunk {},{} failed: {}", x, z, cause.toString()); + ModdedIrisLog.warn("Iris pregen chunk {},{} failed: {}", x, z, cause.toString()); } private void markFinished() { @@ -500,7 +498,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod { try { engine.getMantle().forceCleanupChunk(x, z); } 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 { current = dedicated.pauseWhenEmptySeconds(); } 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; } if (current <= 0) { @@ -591,7 +589,7 @@ public final class ModdedPregenMethod implements PregeneratorMethod { } suspendedFrom.set(current); 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() { @@ -606,9 +604,9 @@ public final class ModdedPregenMethod implements PregeneratorMethod { } try { 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) { - 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); } } @@ -655,11 +653,11 @@ public final class ModdedPregenMethod implements PregeneratorMethod { if (!pauseStillArmed()) { 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) { - 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); } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedRegen.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedRegen.java index 8028d4d59..6947fd595 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedRegen.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedRegen.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.core.tools.WorldMaintenance; import art.arcane.iris.engine.framework.Engine; 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.levelgen.Heightmap; import net.minecraft.world.phys.AABB; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ArrayList; 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.ModdedCommandMessages; public final class ModdedRegen { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final int APPLY_AHEAD = 8; private static final long CHUNK_SLOT_TIMEOUT_MILLIS = 120000L; 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); 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."); - 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); Thread thread = new Thread(job::run, "Iris Regenerate"); thread.setDaemon(true); @@ -117,11 +115,11 @@ public final class ModdedRegen { List targets = ChunkSpiral.centerOut(centerX, centerZ, radius); int applied = regenerate(targets); 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) { Thread.currentThread().interrupt(); } catch (Throwable e) { - LOGGER.error("Iris regen failed", e); + ModdedIrisLog.error("Iris regen failed", e); fail("Regen failed: " + e); } finally { WorldMaintenance.endWorldMaintenance(worldIdentity, "regen"); @@ -155,7 +153,7 @@ public final class ModdedRegen { int chunkZ = target[1]; if (!inFlight.tryAcquire(CHUNK_SLOT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { 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); fail("Regen aborted: apply pipeline stalled at " + completed.get() + "/" + total + " chunk(s)"); break; @@ -173,7 +171,7 @@ public final class ModdedRegen { try { engine.generate(chunkX << 4, chunkZ << 4, blocks, biomes, false); } 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()); completed.incrementAndGet(); inFlight.release(); @@ -190,7 +188,7 @@ public final class ModdedRegen { success = true; applied.incrementAndGet(); } 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()); } finally { int done = completed.incrementAndGet(); @@ -212,7 +210,7 @@ public final class ModdedRegen { if (!allApplied.await(FINAL_APPLY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { aborted.set(true); 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); fail("Regen aborted: " + outstanding + " of " + total + " chunk(s) never finished"); } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStructureCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStructureCommands.java index 603d894d9..116f3ba2f 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStructureCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStructureCommands.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.structure.StructureIndexService; import art.arcane.iris.engine.framework.Engine; @@ -40,8 +41,6 @@ import net.minecraft.commands.Commands; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; 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.volmlib.util.localization.MessageArgument; public final class ModdedStructureCommands { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final Predicate GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); private static final SuggestionProvider IRIS_STRUCTURE_KEYS = (CommandContext 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); } } 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()); IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_PLACE_FAILED_PARTIAL_CHANGES_RECORDED_UNDO, MessageArgument.untrusted("value", e.getClass().getSimpleName()))); return 0; diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStudioCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStudioCommands.java index 5b654c209..2936a5e17 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStudioCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStudioCommands.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.gui.GuiHost; 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.ServerPlayer; import net.minecraft.world.entity.Relative; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.zeroturnaround.zip.ZipUtil; 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.volmlib.util.localization.MessageArgument; public final class ModdedStudioCommands { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final Predicate GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); private static final Pattern PROJECT_NAME = 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 { workspace = ModdedWorkspaceGenerator.writeWorkspace(IrisData.get(folder), folder, open); } 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())))); return 0; } @@ -299,7 +297,7 @@ public final class ModdedStudioCommands { try { Desktop.getDesktop().open(workspace); } 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()))); return 0; } @@ -380,8 +378,9 @@ public final class ModdedStudioCommands { try { File packFolder = new File(ModdedPackCommands.packsRoot(), pack); if (!new File(packFolder, "dimensions/" + pack + ".json").isFile()) { - server.execute(() -> IrisModdedCommands.fail(source, "Pack '" + pack - + "' is not installed. Use /iris download pack=overworld or pack=underworld, then restart.")); + server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain( + ModdedCommandMessages.MODDED_STUDIO_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART, + MessageArgument.untrusted("pack", pack)))); return; } IrisData data = IrisData.get(packFolder); @@ -393,7 +392,7 @@ public final class ModdedStudioCommands { try { ModdedWorkspaceGenerator.writeWorkspace(data, packFolder, true); } 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( ModdedCommandMessages.MODDED_STUDIO_COMMANDS_FAILED_WRITE_WORKSPACE, MessageArgument.untrusted("value", packFolder.getAbsolutePath()), @@ -407,7 +406,7 @@ public final class ModdedStudioCommands { } }); } 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))))); } } @@ -417,7 +416,7 @@ public final class ModdedStudioCommands { try { handle = ModdedDimensionManager.create(server, dimensionId, pack, pack, seed); } 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)))); return; } @@ -430,7 +429,7 @@ public final class ModdedStudioCommands { surface = engine.getMinHeight() + engine.getHeight(8, 8, false) + 2; } } 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_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 { handle = ModdedDimensionManager.create(server, dimensionId, pack, pack, seed); } 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)))); return; } @@ -460,7 +459,7 @@ public final class ModdedStudioCommands { surface = engine.getMinHeight() + engine.getHeight(8, 8, false) + 2; } } 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.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))); @@ -480,7 +479,7 @@ public final class ModdedStudioCommands { try { ModdedDimensionManager.remove(server, dimensionId, true); } 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)))); return 0; } @@ -553,7 +552,7 @@ public final class ModdedStudioCommands { surface = engine.getMinHeight() + engine.getHeight(8, 8, false) + 2; } } 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.of(), player.getYRot(), player.getXRot(), false); 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 { File templateFolder = new File(packsRoot, template); if (!new File(templateFolder, "dimensions/" + template + ".json").isFile()) { - server.execute(() -> IrisModdedCommands.fail(source, "Template pack '" + template - + "' is not installed. Install its zip with /iris download link=, then restart.")); + server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain( + ModdedCommandMessages.MODDED_STUDIO_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART, + MessageArgument.untrusted("pack", template)))); return; } IrisProjectCopier.copyProject(templateFolder, target, template, name); try { ModdedWorkspaceGenerator.writeWorkspace(IrisData.get(target), target); } 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(() -> { 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))); }); } 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))))); } }, "Iris Studio Create"); @@ -630,7 +630,7 @@ public final class ModdedStudioCommands { File result = compilePackage(folder, dimKey); server.execute(() -> IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACKAGE_COMPILED, MessageArgument.untrusted("value", result.getAbsolutePath())))); } 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))))); } }, "Iris Studio Package"); @@ -728,7 +728,7 @@ public final class ModdedStudioCommands { IO.copyFile(objectFile, new File(folder, "objects/" + objectKey + ".iob")); hashes.append(IO.hash(objectFile)); } 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); return IO.hash(json); } catch (Throwable e) { - LOGGER.error("Iris package failed to write {}/{}", category, key, e); + ModdedIrisLog.error("Iris package failed to write {}/{}", category, key, e); 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)))); })); } 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())))); } }, "Iris Region Sampler"); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedUnregisteredStructures.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedUnregisteredStructures.java index 584caff0d..d13c72263 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedUnregisteredStructures.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedUnregisteredStructures.java @@ -18,6 +18,7 @@ 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.IrisStructureLocator; 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.server.level.ServerLevel; import net.minecraft.world.level.levelgen.structure.Structure; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.HashSet; @@ -44,7 +43,6 @@ import java.util.TreeMap; import java.util.function.Predicate; final class ModdedUnregisteredStructures { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private ModdedUnregisteredStructures() { } @@ -67,13 +65,13 @@ final class ModdedUnregisteredStructures { .filter((ExcludedStructure entry) -> entry.status() == ReportStatus.UNPLACED) .count(); 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); for (ExcludedStructure entry : excluded) { - LOGGER.info("[Iris goto unregistered] [{}] {} - {}", + ModdedIrisLog.info("[Iris goto unregistered] [{}] {} - {}", 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 " + "eligibility analysis and performs no chunk search; absent unmanaged datapack resources " + "cannot be inferred after registry loading."); @@ -82,7 +80,7 @@ final class ModdedUnregisteredStructures { + unregistered + " unregistered, " + unplaced + " unplaced)."); return 1; } 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); IrisModdedCommands.fail(source, "Iris could not build the excluded structure report; see the server console."); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWandService.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWandService.java index a430491f1..8e1d16552 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWandService.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWandService.java @@ -18,6 +18,7 @@ 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.RuntimeUiMessages; 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.TooltipDisplay; import net.minecraft.world.level.Level; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.awt.Color; import java.util.List; @@ -52,7 +51,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadLocalRandom; public final class ModdedWandService { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final ConcurrentHashMap SELECTIONS = new ConcurrentHashMap<>(); private static final String WAND_TAG = "iris_wand"; private static final String DUST_TAG = "iris_dust"; @@ -213,7 +211,7 @@ public final class ModdedWandService { draw(player.level(), player, selection); } } catch (Throwable e) { - LOGGER.error("Iris wand selection draw failed", e); + ModdedIrisLog.error("Iris wand selection draw failed", e); } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWhatCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWhatCommands.java index b480fb44a..e87c12958 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWhatCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWhatCommands.java @@ -18,6 +18,7 @@ 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.IrisMessages; 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.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; @@ -77,7 +76,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; public final class ModdedWhatCommands { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final Predicate GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); private static final SuggestionProvider MARKER_TYPES = @@ -343,7 +341,7 @@ public final class ModdedWhatCommands { MessageArgument.untrusted("object", object))); } } 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); } } @@ -486,7 +484,7 @@ public final class ModdedWhatCommands { private static void markerFailure(CommandSourceStack source, 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(() -> { if (ACTIVE_MARKER_RUNS.remove(run.playerId(), run)) { IrisModdedCommands.fail(source, IrisLanguage.plain( @@ -514,7 +512,7 @@ public final class ModdedWhatCommands { private static void logLookupFailure(CommandSourceStack source, String operation, Throwable error, TextKey message) { - LOGGER.error("Iris /what {} lookup failed in {}", operation, + ModdedIrisLog.error("Iris /what {} lookup failed in {}", operation, source.getLevel().dimension().identifier(), error); IrisModdedCommands.fail(source, IrisLanguage.plain( message, diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java index 2150f9942..b2bf115a1 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.command; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.pack.BrokenPackException; 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.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; 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.volmlib.util.localization.MessageArgument; public final class ModdedWorldCommands { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final Predicate GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS); private static final String DEFAULT_NAMESPACE = "irisworldgen"; private static final long DEFAULT_SEED = 1337L; @@ -174,8 +172,9 @@ public final class ModdedWorldCommands { if (packFolder.isDirectory()) { return enableInstalled(source, server, dimensionId, pack, packDimension, seed); } - IrisModdedCommands.fail(source, "Pack '" + pack - + "' is not installed. Use /iris download pack=overworld or pack=underworld, then restart."); + IrisModdedCommands.fail(source, IrisLanguage.plain( + ModdedCommandMessages.MODDED_WORLD_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART, + MessageArgument.untrusted("pack", pack))); return 0; } @@ -189,7 +188,7 @@ public final class ModdedWorldCommands { try { ModdedDimensionManager.createPersistent(server, dimensionId, pack, packDimension, seed); } 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)))); return 0; } @@ -220,7 +219,7 @@ public final class ModdedWorldCommands { try { ModdedDimensionManager.createPersistent(server, dimensionId, pack, packDimension, seed); } 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)))); return 0; } @@ -265,8 +264,9 @@ public final class ModdedWorldCommands { if (packFolder.isDirectory()) { return applyMainWorld(source, pack, packDimension, packRaw, seed); } - IrisModdedCommands.fail(source, "Pack '" + pack - + "' is not installed. Use /iris download pack=overworld or pack=underworld, then restart."); + IrisModdedCommands.fail(source, IrisLanguage.plain( + ModdedCommandMessages.MODDED_WORLD_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART, + MessageArgument.untrusted("pack", pack))); return 0; } @@ -279,7 +279,7 @@ public final class ModdedWorldCommands { return 0; } } 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) { 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; @@ -384,7 +384,7 @@ public final class ModdedWorldCommands { try { removed = ModdedDimensionManager.removePersistent(server, dimensionId, wipeStorage); } 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)))); return 0; } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedEngineMaintenanceService.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedEngineMaintenanceService.java index af2ab2e4e..464b9afb9 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedEngineMaintenanceService.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedEngineMaintenanceService.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.service; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.service.EngineMaintenance; 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.util.project.context.IrisContext; import net.minecraft.server.MinecraftServer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.Collections; @@ -42,7 +41,6 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; 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 SAVE_PERIOD_MILLIS = 60_000L; private static final long SHUTDOWN_TIMEOUT_SECONDS = 30L; @@ -120,7 +118,7 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi } catch (RejectedExecutionException exception) { inFlight.remove(engine); 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); 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)); } } catch (GenerationSessionException exception) { @@ -156,13 +154,13 @@ public final class ModdedEngineMaintenanceService implements ModdedTickableServi return; } 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) { if (EngineMaintenance.isMantleClosed(exception)) { return; } 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; } 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( "Iris engine maintenance workers did not stop after shutdownNow"); 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; } catch (InterruptedException exception) { active.shutdownNow(); Thread.currentThread().interrupt(); IrisLogging.reportError(exception); - LOGGER.error("Interrupted while draining Iris engine maintenance", exception); + ModdedIrisLog.error("Interrupted while draining Iris engine maintenance", exception); return false; } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedPreservationService.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedPreservationService.java index 7a0de0e40..6867c8c8a 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedPreservationService.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedPreservationService.java @@ -18,11 +18,10 @@ package art.arcane.iris.modded.service; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.engine.framework.MeteredCache; import art.arcane.iris.engine.framework.PreservationRegistry; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.lang.ref.WeakReference; import java.util.List; @@ -31,7 +30,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; public final class ModdedPreservationService implements ModdedService, PreservationRegistry { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final long DEREFERENCE_INTERVAL_MILLIS = 60000L; private final List threads = new CopyOnWriteArrayList<>(); @@ -107,17 +105,17 @@ public final class ModdedPreservationService implements ModdedService, Preservat } try { thread.interrupt(); - LOGGER.info("Iris preservation interrupted thread {}", thread.getName()); + ModdedIrisLog.info("Iris preservation interrupted thread {}", thread.getName()); } 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) { try { service.shutdownNow(); - LOGGER.info("Iris preservation shut down executor {}", service); + ModdedIrisLog.info("Iris preservation shut down executor {}", service); } 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); } } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedSettingsHotloadService.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedSettingsHotloadService.java index 1dbc2a679..6fe6aad57 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedSettingsHotloadService.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedSettingsHotloadService.java @@ -60,6 +60,6 @@ public final class ModdedSettingsHotloadService implements ModdedTickableService } private static File settingsFile() { - return IrisPlatforms.get().dataFile("settings.json"); + return IrisPlatforms.get().dataFile("iris.json"); } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedStudioHotloadService.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedStudioHotloadService.java index 3d0676d4c..e652033a4 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedStudioHotloadService.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedStudioHotloadService.java @@ -18,6 +18,7 @@ package art.arcane.iris.modded.service; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.core.gui.PregeneratorJob; import art.arcane.iris.core.loader.IrisData; 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.scheduling.ChronoLatch; import net.minecraft.server.MinecraftServer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.util.HashSet; @@ -52,7 +51,6 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicBoolean; 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 long POLL_MILLIS = 250L; private static final long CHECK_LATCH_MILLIS = 1_000L; @@ -237,7 +235,7 @@ public final class ModdedStudioHotloadService implements ModdedTickableService, folder.check(); } } 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 { engine.hotloadSilently(); 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) { - 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); } } @@ -275,7 +273,7 @@ public final class ModdedStudioHotloadService implements ModdedTickableService, IrisData data = engine.getData(); ModdedWorkspaceGenerator.writeWorkspace(data, data.getDataFolder()); } catch (Throwable e) { - LOGGER.error("Iris {} failed for {}", operation, engine.getDimension().getLoadKey(), e); + ModdedIrisLog.error("Iris {} failed for {}", operation, engine.getDimension().getLoadKey(), e); } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedTreeFellerPresentation.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedTreeFellerPresentation.java index 3fe5cb669..5b13a2a45 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedTreeFellerPresentation.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedTreeFellerPresentation.java @@ -1,5 +1,6 @@ package art.arcane.iris.modded.service; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.modded.ModdedEngineBootstrap; import art.arcane.iris.modded.ModdedScheduler; 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.item.ItemStack; import net.minecraft.world.level.block.state.BlockState; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collection; @@ -21,7 +20,6 @@ import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; final class ModdedTreeFellerPresentation { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final int MIN_BLOCKS_PER_PULSE = 4; private static final int MAX_BLOCKS_PER_PULSE = 64; private static final int TARGET_EROSION_PULSES = 60; @@ -215,13 +213,13 @@ final class ModdedTreeFellerPresentation { private void reportEffectFailure(Throwable error) { 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) { 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); } } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedTreeFellerService.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedTreeFellerService.java index 00ef52ec7..c49ba5755 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedTreeFellerService.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/service/ModdedTreeFellerService.java @@ -1,5 +1,6 @@ package art.arcane.iris.modded.service; +import art.arcane.iris.modded.ModdedIrisLog; import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.service.tree.TreeDefinitionIndex; 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.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.Collections; import java.util.List; @@ -38,7 +37,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BooleanSupplier; public final class ModdedTreeFellerService implements ModdedTickableService { - private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); private static final ThreadLocal BREAK_PROBE_DEPTH = ThreadLocal.withInitial(() -> 0); 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) ); } 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); } TreeMarkerTraversal.Discovery resolved = discovery; diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/client/IrisClientHotloadTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/client/IrisClientHotloadTest.java new file mode 100644 index 000000000..73468845f --- /dev/null +++ b/adapters/modded-common/src/test/java/art/arcane/iris/client/IrisClientHotloadTest.java @@ -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)); + } +} diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedIrisLogLevelCoverageTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedIrisLogLevelCoverageTest.java index 19a317138..d9c208f21 100644 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedIrisLogLevelCoverageTest.java +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedIrisLogLevelCoverageTest.java @@ -5,7 +5,10 @@ import org.junit.Test; import java.io.IOException; import java.nio.file.Files; 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; /** @@ -24,4 +27,49 @@ public class ModdedIrisLogLevelCoverageTest { String body = source.substring(router, source.indexOf("public static void debug(", router)); 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 files = Files.walk(root)) { + List 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()); + } + } } diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPlatformPathsTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPlatformPathsTest.java index fe42157df..ffd0cba5f 100644 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPlatformPathsTest.java +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/ModdedPlatformPathsTest.java @@ -91,7 +91,7 @@ public class ModdedPlatformPathsTest { File iris = new File(temporaryFolder.getRoot(), "iris"); 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")); } diff --git a/core/src/main/java/art/arcane/iris/core/IrisSettings.java b/core/src/main/java/art/arcane/iris/core/IrisSettings.java index 721bd7f09..a3996e6da 100644 --- a/core/src/main/java/art/arcane/iris/core/IrisSettings.java +++ b/core/src/main/java/art/arcane/iris/core/IrisSettings.java @@ -85,13 +85,12 @@ public class IrisSettings { private static IrisSettings read() { IrisSettings loaded = new IrisSettings(); - File s = IrisPlatforms.get().dataFile("settings.json"); + File s = IrisPlatforms.get().dataFile("iris.json"); if (!s.exists()) { try { IO.writeAll(s, new JSONObject(new Gson().toJson(loaded)).toString(4)); } catch (JSONException | IOException e) { - e.printStackTrace(); IrisLogging.reportError(e); } @@ -106,32 +105,18 @@ public class IrisSettings { loaded = parsed; } - migrateLegacyKeys(loaded, ss); - try { IO.writeAll(s, new JSONObject(new Gson().toJson(loaded)).toString(4)); } catch (IOException e) { - e.printStackTrace(); } } catch (Throwable ee) { // 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; } - 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() { synchronized (SETTINGS_LOCK) { settings = null; @@ -169,7 +154,6 @@ public class IrisSettings { if (parsed == null) { throw new IllegalArgumentException("Iris settings snapshot did not contain an object"); } - migrateLegacyKeys(parsed, rawJson); } catch (RuntimeException failure) { throw new IllegalArgumentException("Iris settings snapshot is invalid", failure); } @@ -179,12 +163,11 @@ public class IrisSettings { } public void forceSave() { - File s = IrisPlatforms.get().dataFile("settings.json"); + File s = IrisPlatforms.get().dataFile("iris.json"); try { IO.writeAll(s, new JSONObject(new Gson().toJson(this)).toString(4)); } catch (JSONException | IOException e) { - e.printStackTrace(); IrisLogging.reportError(e); } } @@ -321,17 +304,18 @@ public class IrisSettings { @Data public static class IrisSettingsGeneral { public String language = "en_US"; + public boolean metrics = true; public boolean commandSounds = true; public boolean debug = false; public boolean dumpMantleOnError = false; public boolean disableNMS = false; - public boolean pluginMetrics = true; public boolean splashLogoStartup = true; public boolean useConsoleCustomColors = true; public boolean useCustomColorsIngame = true; /** - * Boss bar progress loaders for jobs, studio opens, world creation, chunk jobs and pack - * downloads. Turning this off keeps the action bar progress line; only the bar goes away. + * Boss bars for jobs, Studio opens, chunk jobs, and pack downloads. Ordinary + * 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 adjustVanillaHeight = false; diff --git a/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java b/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java index e697e6f3a..aad8c12ec 100644 --- a/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java +++ b/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java @@ -56,6 +56,8 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.nio.channels.FileChannel; import java.nio.file.AtomicMoveNotSupportedException; @@ -281,11 +283,7 @@ public class ServerConfigurator { IrisLogging.error("Unable to install datapacks, fixer is null!"); return DatapackInstallResult.failedResult(); } - if (fullInstall) { - IrisLogging.info("Checking Data Packs..."); - } else { - IrisLogging.debug("Checking Data Packs..."); - } + IrisLogging.debug("Checking Data Packs..."); List packRoots; List bindings; try { @@ -349,11 +347,7 @@ public class ServerConfigurator { } } } - if (fullInstall) { - IrisLogging.info("Data Packs Setup!"); - } else { - IrisLogging.debug("Data Packs Setup!"); - } + IrisLogging.debug("Data Packs Setup!"); boolean verifiedRestartRequired = fullInstall && verifyDataPacksPost(); boolean restartRequired = fullInstall && (reapply.changed() || verifiedRestartRequired); @@ -1026,12 +1020,21 @@ public class ServerConfigurator { ? "Iris startup validation requires a restart." : reason.trim(); IrisLogging.warn(restartReason + " Restarting server before default worlds are loaded."); + boolean restartInvoked = false; try { - Bukkit.restart(); - } catch (Throwable failure) { - IrisLogging.reportError("Unable to restart the server at the Iris startup boundary.", failure); + restartInvoked = invokeImmediateRestartIfSupported(Bukkit.class); + } catch (ReflectiveOperationException | RuntimeException | LinkageError 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 { Bukkit.shutdown(); } 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) { KSet keys = new KSet<>(); boolean warn = false; @@ -1056,11 +1070,9 @@ public class ServerConfigurator { if (!INMS.get().supportsDataPacks()) { if (!keys.isEmpty()) { - IrisLogging.warn("==================================================================================="); IrisLogging.warn("Pack " + key + " has " + keys.size() + " custom biome(s). "); 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("===================================================================================="); } return true; diff --git a/core/src/main/java/art/arcane/iris/core/SettingsHotloadWatch.java b/core/src/main/java/art/arcane/iris/core/SettingsHotloadWatch.java index 721fdb759..5a345c783 100644 --- a/core/src/main/java/art/arcane/iris/core/SettingsHotloadWatch.java +++ b/core/src/main/java/art/arcane/iris/core/SettingsHotloadWatch.java @@ -92,7 +92,6 @@ public final class SettingsHotloadWatch implements AutoCloseable { } catch (RuntimeException failure) { IrisLogging.error("Iris settings and locale hotload watcher failed: " + failureDetail(failure)); IrisLogging.reportError(failure); - failure.printStackTrace(); } } } @@ -113,7 +112,7 @@ public final class SettingsHotloadWatch implements AutoCloseable { boolean missing = "missing".equals(snapshot.signature()); if (isSettingsFile(file)) { 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; } if (snapshot.normalizedContent() == null) { @@ -210,7 +209,6 @@ public final class SettingsHotloadWatch implements AutoCloseable { } catch (RuntimeException failure) { IrisLogging.error("Rejected invalid settings hotload from " + file.getAbsolutePath() + ": " + failureDetail(failure)); IrisLogging.reportError(failure); - failure.printStackTrace(); return false; } } @@ -221,7 +219,6 @@ public final class SettingsHotloadWatch implements AutoCloseable { } catch (RuntimeException failure) { IrisLogging.error("Rejected invalid locale hotload from " + file.getAbsolutePath() + ": " + failureDetail(failure)); IrisLogging.reportError(failure); - failure.printStackTrace(); return false; } } @@ -251,7 +248,6 @@ public final class SettingsHotloadWatch implements AutoCloseable { } IrisLogging.error("Failed to read watched Iris file " + path + ": " + failureDetail(failure)); IrisLogging.reportError(failure); - failure.printStackTrace(); } private void clearCaptureFailure(File file) { @@ -262,12 +258,12 @@ public final class SettingsHotloadWatch implements AutoCloseable { File file = delta.file(); if (isSettingsFile(file)) { if (delta.after() != null) { - IrisLogging.info("Hotloaded settings.json"); + IrisLogging.debug("Hotloaded iris.json"); } return; } if (IrisLanguage.isActiveOverrideFile(file)) { - IrisLogging.info("Hotloaded locale override " + file.getName()); + IrisLogging.debug("Hotloaded locale override " + file.getName()); } } diff --git a/core/src/main/java/art/arcane/iris/core/gui/VisionGUI.java b/core/src/main/java/art/arcane/iris/core/gui/VisionGUI.java index 43d470249..644fd06f7 100644 --- a/core/src/main/java/art/arcane/iris/core/gui/VisionGUI.java +++ b/core/src/main/java/art/arcane/iris/core/gui/VisionGUI.java @@ -983,8 +983,8 @@ public final class VisionGUI extends JPanel implements MouseWheelListener, KeyLi notifyUser(IrisLanguage.plain(DesktopUiMessages.VISION_NO_PLAYER)); return; } - int worldX = (int) screenToWorldX(point.x); - int worldZ = (int) screenToWorldZ(point.y); + int worldX = floorWorldCoordinate(screenToWorldX(point.x)); + int worldZ = floorWorldCoordinate(screenToWorldZ(point.y)); overlay.teleport(worldX, worldZ); notifyUser(IrisLanguage.plain( DesktopUiMessages.VISION_TELEPORTING, @@ -1009,6 +1009,10 @@ public final class VisionGUI extends JPanel implements MouseWheelListener, KeyLi controller.close(); } + static int floorWorldCoordinate(double coordinate) { + return (int) StrictMath.floor(coordinate); + } + private static String modeName(RenderType type) { return IrisLanguage.plain(modeKey(type)); } diff --git a/core/src/main/java/art/arcane/iris/core/lifecycle/IrisWorldRemovalService.java b/core/src/main/java/art/arcane/iris/core/lifecycle/IrisWorldRemovalService.java index ace825999..c45782a9a 100644 --- a/core/src/main/java/art/arcane/iris/core/lifecycle/IrisWorldRemovalService.java +++ b/core/src/main/java/art/arcane/iris/core/lifecycle/IrisWorldRemovalService.java @@ -944,7 +944,7 @@ public final class IrisWorldRemovalService { PlatformChunkGenerator generator = null; if (world != null) { 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); } boolean registryManaged = IrisWorlds.get().getWorlds().containsKey(target.worldKey().toString()); diff --git a/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleService.java b/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleService.java index 6a4add872..1f389a66f 100644 --- a/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleService.java +++ b/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleService.java @@ -208,9 +208,11 @@ public final class WorldLifecycleService { 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)) { - 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()); } return paperLikeRuntimeBackend; diff --git a/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleSupport.java b/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleSupport.java index 33bc592ac..64e0593d6 100644 --- a/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleSupport.java +++ b/core/src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleSupport.java @@ -10,7 +10,6 @@ import art.arcane.iris.util.common.scheduling.J; import art.arcane.volmlib.util.bukkit.WorldIdentity; import art.arcane.volmlib.util.scheduling.FoliaScheduler; import org.bukkit.Bukkit; -import org.bukkit.Location; import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.configuration.ConfigurationSection; @@ -472,12 +471,8 @@ final class WorldLifecycleSupport { ) { String worldName = world.getName(); try { - try { + if (capabilities.minecraftServer() == null || capabilities.removeLevelMethod() == null) { return CompletableFuture.completedFuture(Bukkit.unloadWorld(world, save)); - } catch (UnsupportedOperationException unsupported) { - if (capabilities.minecraftServer() == null || capabilities.removeLevelMethod() == null) { - return CompletableFuture.failedFuture(unsupported); - } } if (!announceManualWorldUnload(world)) { @@ -489,8 +484,9 @@ final class WorldLifecycleSupport { } Method getHandleMethod = world.getClass().getMethod("getHandle"); Object serverLevel = getHandleMethod.invoke(world); - CompletableFuture operation = closeServerLevelAsync(world, serverLevel) - .thenCompose(unused -> detachServerLevelAsync(capabilities, serverLevel, world)) + CompletableFuture operation = detachServerLevelAsync(capabilities, serverLevel, world) + .thenCompose(unused -> drainChunkTasksAsync(world, serverLevel)) + .thenCompose(unused -> closeServerLevelAsync(world, serverLevel)) .thenApply(unused -> WorldIdentity.resolve(WorldIdentity.key(world)).isEmpty()); return contextualizeUnloadFailure(worldName, operation); } catch (Throwable e) { @@ -527,7 +523,13 @@ final class WorldLifecycleSupport { boolean save ) { CompletableFuture callbackFuture = new CompletableFuture<>(); - Consumer callback = unloaded -> callbackFuture.complete(Boolean.TRUE.equals(unloaded)); + Consumer callback = unloaded -> { + try { + callbackFuture.complete(asyncUnloadSucceeded(unloaded)); + } catch (Throwable failure) { + callbackFuture.completeExceptionally(unwrap(failure)); + } + }; try { unloadWorldAsyncMethod.invoke(bukkitServer, world, save, callback); } catch (Throwable e) { @@ -536,6 +538,17 @@ final class WorldLifecycleSupport { 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 contextualizeUnloadFailure( String worldName, CompletableFuture operation @@ -569,33 +582,59 @@ final class WorldLifecycleSupport { return CompletableFuture.completedFuture(null); } - if (!J.isFolia()) { + Runnable closeTask = () -> { try { closeMethod.invoke(serverLevel); - return CompletableFuture.completedFuture(null); } catch (Throwable e) { - return CompletableFuture.failedFuture(unwrap(e)); + throw new RuntimeException(unwrap(e)); } + }; + return runGlobalAsync(closeTask).orTimeout(90L, TimeUnit.SECONDS); + } + + private static CompletableFuture 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(); - int chunkX = spawn == null ? 0 : spawn.getBlockX() >> 4; - int chunkZ = spawn == null ? 0 : spawn.getBlockZ() >> 4; - CompletableFuture closeFuture = new CompletableFuture<>(); - boolean scheduled = J.runRegion(world, chunkX, chunkZ, () -> { + return J.afut(() -> { try { - closeMethod.invoke(serverLevel); - closeFuture.complete(null); + Object scheduler = schedulerMethod.invoke(serverLevel); + 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) { - closeFuture.completeExceptionally(unwrap(e)); + throw new RuntimeException(unwrap(e)); } - }); - if (!scheduled) { - return CompletableFuture.failedFuture(new IllegalStateException( - "Failed to schedule region close task for world \"" + world.getName() + "\"." - )); - } - return closeFuture.orTimeout(90L, TimeUnit.SECONDS); + }).orTimeout(90L, TimeUnit.SECONDS); } @SuppressWarnings({"rawtypes", "unchecked"}) diff --git a/core/src/main/java/art/arcane/iris/core/link/WorldEditLink.java b/core/src/main/java/art/arcane/iris/core/link/WorldEditLink.java index f9c3deee8..dda67dac8 100644 --- a/core/src/main/java/art/arcane/iris/core/link/WorldEditLink.java +++ b/core/src/main/java/art/arcane/iris/core/link/WorldEditLink.java @@ -47,7 +47,6 @@ public class WorldEditLink { } catch (Throwable e) { if (errorThrottle.flip()) { IrisLogging.error("Could not get selection"); - e.printStackTrace(); IrisLogging.reportError(e); } invalidate(); diff --git a/core/src/main/java/art/arcane/iris/core/link/data/NexoDataProvider.java b/core/src/main/java/art/arcane/iris/core/link/data/NexoDataProvider.java index c65c7bd60..e8bccf17a 100644 --- a/core/src/main/java/art/arcane/iris/core/link/data/NexoDataProvider.java +++ b/core/src/main/java/art/arcane/iris/core/link/data/NexoDataProvider.java @@ -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.service.ExternalDataSVC; import art.arcane.iris.engine.framework.Engine; +import art.arcane.iris.spi.IrisLogging; import art.arcane.volmlib.util.collection.KMap; import art.arcane.iris.util.common.data.IrisCustomData; import org.bukkit.block.Block; @@ -82,7 +83,7 @@ public class NexoDataProvider extends ExternalDataProvider { try { return builder.build(); } 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()); } } diff --git a/core/src/main/java/art/arcane/iris/core/loader/IrisData.java b/core/src/main/java/art/arcane/iris/core/loader/IrisData.java index 9cfe48269..4c5dfba9d 100644 --- a/core/src/main/java/art/arcane/iris/core/loader/IrisData.java +++ b/core/src/main/java/art/arcane/iris/core/loader/IrisData.java @@ -278,7 +278,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { } } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); } return null; @@ -450,7 +449,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { return r; } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); IrisLogging.error("Failed to create loader! " + registrant.getCanonicalName()); } @@ -561,7 +559,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { )); } IrisLogging.reportError(failure); - failure.printStackTrace(); throw failure; } @@ -730,7 +727,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { .map(s -> s.split("\\Q.\\E")[0]) .forEach(s -> l.add("snippet/" + s)); } catch (Throwable e) { - e.printStackTrace(); + IrisLogging.reportError("Failed to scan Iris snippets in " + snippetFolder + ".", e); } return l; diff --git a/core/src/main/java/art/arcane/iris/core/localization/BukkitCommandMessagesExtended.java b/core/src/main/java/art/arcane/iris/core/localization/BukkitCommandMessagesExtended.java index 356d1ee3d..dd61ac09f 100644 --- a/core/src/main/java/art/arcane/iris/core/localization/BukkitCommandMessagesExtended.java +++ b/core/src/main/java/art/arcane/iris/core/localization/BukkitCommandMessagesExtended.java @@ -183,10 +183,6 @@ public final class BukkitCommandMessagesExtended { "iris.bukkit.commandiris.install_pack_and_restart", 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( "iris.bukkit.commandiris.exception_raised_during_creation_see_console_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", 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( "iris.bukkit.commandiris.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", 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( "iris.bukkit.commandiris.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_DIMENSION_NOT_FOUND, 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_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_IRIS_V_BY_VOLMIT_SOFTWARE, COMMAND_IRIS_TO, @@ -910,7 +876,6 @@ public final class BukkitCommandMessagesExtended { COMMAND_IRIS_IS_NOT_IRIS_WORLD, COMMAND_IRIS_COULD_NOT_DETERMINE_IRIS_DIMENSION, COMMAND_IRIS_LOADING_WORLD, - COMMAND_IRIS_FOLIA_CANNOT_LOAD_NEW_WORLDS_AT_RUNTIME_RESTART_SERVER_LOAD, COMMAND_IRIS_LOADED_SUCCESSFULLY, COMMAND_IRIS_THIS_IS_NOT_IRIS_WORLD_IRIS_WORLDS_3, COMMAND_IRIS_EVACUATING_WORLD, diff --git a/core/src/main/java/art/arcane/iris/core/localization/BukkitRuntimeMessages.java b/core/src/main/java/art/arcane/iris/core/localization/BukkitRuntimeMessages.java index bb5cbdace..a73e5cb73 100644 --- a/core/src/main/java/art/arcane/iris/core/localization/BukkitRuntimeMessages.java +++ b/core/src/main/java/art/arcane/iris/core/localization/BukkitRuntimeMessages.java @@ -406,10 +406,6 @@ public final class BukkitRuntimeMessages { "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_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( "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_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_CONVERTED_3, 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_INSTALL_FAILED, STUDIO_S_V_C_LOOKING_PACKAGE, diff --git a/core/src/main/java/art/arcane/iris/core/localization/IrisLanguage.java b/core/src/main/java/art/arcane/iris/core/localization/IrisLanguage.java index 6e29a8b08..4fab77ac0 100644 --- a/core/src/main/java/art/arcane/iris/core/localization/IrisLanguage.java +++ b/core/src/main/java/art/arcane/iris/core/localization/IrisLanguage.java @@ -130,7 +130,6 @@ public final class IrisLanguage { dataFolder = resolvedRoot; IrisLogging.error("Rejected locale setting '" + locale + "'; continuing with " + activeLocale + "."); IrisLogging.reportError(exception); - exception.printStackTrace(); return false; } @@ -159,7 +158,6 @@ public final class IrisLanguage { } catch (RuntimeException exception) { IrisLogging.error("Rejected locale setting '" + configured + "'; continuing with " + activeLocale + "."); IrisLogging.reportError(exception); - exception.printStackTrace(); return false; } @@ -214,7 +212,7 @@ public final class IrisLanguage { activeLocale = requestedLocale; 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") + "."); return true; } @@ -228,7 +226,6 @@ public final class IrisLanguage { + failure.getClass().getSimpleName() + (failure.getMessage() == null ? "" : " - " + failure.getMessage())); IrisLogging.reportError(failure); - failure.printStackTrace(); } } } @@ -641,7 +638,6 @@ public final class IrisLanguage { } if (result.failure() != null) { IrisLogging.reportError(result.failure()); - result.failure().printStackTrace(); } } diff --git a/core/src/main/java/art/arcane/iris/core/localization/ModdedCommandMessages.java b/core/src/main/java/art/arcane/iris/core/localization/ModdedCommandMessages.java index 5ad8c3a2e..79554d09a 100644 --- a/core/src/main/java/art/arcane/iris/core/localization/ModdedCommandMessages.java +++ b/core/src/main/java/art/arcane/iris/core/localization/ModdedCommandMessages.java @@ -1043,13 +1043,9 @@ public final class ModdedCommandMessages { "iris.modded.moddedstudiocommands.creating_project_from_template", "Creating project '" + "{name}" + "' from template '" + "{template}" + "'..." ); - public static final TextKey MODDED_STUDIO_COMMANDS_TEMPLATE_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS = TextKey.of( - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions", - "Template '" + "{template}" + "' is not installed; downloading IrisDimensions/" + "{template2}" + "..." - ); - 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_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART = TextKey.of( + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart", + "Required pack '" + "{pack}" + "' is not installed. Install it with /iris download, then restart." ); public static final TextKey MODDED_STUDIO_COMMANDS_CREATED_PROJECT_AT = TextKey.of( "iris.modded.moddedstudiocommands.created_project_at", @@ -1095,13 +1091,9 @@ public final class ModdedCommandMessages { "iris.modded.moddedstudiocommands.region_sampling_failed", "Region sampling failed: " + "{value}" ); - public static final TextKey MODDED_WORLD_COMMANDS_PACK_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS = TextKey.of( - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions", - "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 = 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_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART = TextKey.of( + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart", + "Required pack '" + "{pack}" + "' is not installed. Install it with /iris download, then restart." ); public static final TextKey MODDED_WORLD_COMMANDS_FAILED_INJECT_IRIS_WORLD = TextKey.of( "iris.modded.moddedworldcommands.failed_inject_iris_world", @@ -1139,14 +1131,6 @@ public final class ModdedCommandMessages { "iris.modded.moddedworldcommands.invalid_seed_use_number_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( "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." @@ -1512,8 +1496,7 @@ public final class ModdedCommandMessages { MODDED_STUDIO_COMMANDS_INVALID_PROJECT_NAME_ALLOWED_Z_0_9, MODDED_STUDIO_COMMANDS_PACK_ALREADY_EXISTS_AT, MODDED_STUDIO_COMMANDS_CREATING_PROJECT_FROM_TEMPLATE, - MODDED_STUDIO_COMMANDS_TEMPLATE_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS, - MODDED_STUDIO_COMMANDS_TEMPLATE_COULD_NOT_BE_DOWNLOADED_INSTALL_PACK_WITH_DIMENSIONS_JSON, + MODDED_STUDIO_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART, MODDED_STUDIO_COMMANDS_CREATED_PROJECT_AT, MODDED_STUDIO_COMMANDS_EDIT_DIMENSIONS_JSON_REST_PACK_VSCODE_WORKSPACE_WITH_JSON_SCHEMA, 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_RARITY, MODDED_STUDIO_COMMANDS_REGION_SAMPLING_FAILED, - MODDED_WORLD_COMMANDS_PACK_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS, - MODDED_WORLD_COMMANDS_PACK_COULD_NOT_BE_DOWNLOADED_CHECK_NAME_INSTALL_IT_WITH, + MODDED_WORLD_COMMANDS_REQUIRED_PACK_IS_NOT_INSTALLED_INSTALL_THEN_RESTART, MODDED_WORLD_COMMANDS_FAILED_INJECT_IRIS_WORLD, MODDED_WORLD_COMMANDS_CREATED_IRIS_WORLD_FROM_PACK_DIMENSION_SEED, 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_IRIS_MAIN_WORLD_OVERRIDE_CLEARED_OVERWORLD_KEEPS_ITS_CURRENT_GENERATOR, 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_FAILED_WRITE_SERVER_PROPERTIES_CHECK_FILE_PERMISSIONS_SET_LEVEL_TYPE, MODDED_WORLD_COMMANDS_IRIS_MAIN_WORLD_SET_PRESET_SEED, diff --git a/core/src/main/java/art/arcane/iris/core/localization/ModdedHelpMessages.java b/core/src/main/java/art/arcane/iris/core/localization/ModdedHelpMessages.java index 6b2843015..efd1762cc 100644 --- a/core/src/main/java/art/arcane/iris/core/localization/ModdedHelpMessages.java +++ b/core/src/main/java/art/arcane/iris/core/localization/ModdedHelpMessages.java @@ -36,11 +36,11 @@ public final class ModdedHelpMessages { ); public static final TextKey COMMAND_DEBUG_TOGGLE_IRIS_DEBUG_LOGGING_AND_SAVE_SETTINGS_JSON = TextKey.of( "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( "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( "iris.modded.help.entry.command.download", diff --git a/core/src/main/java/art/arcane/iris/core/localization/RuntimeProgressMessages.java b/core/src/main/java/art/arcane/iris/core/localization/RuntimeProgressMessages.java index c88443071..57864139f 100644 --- a/core/src/main/java/art/arcane/iris/core/localization/RuntimeProgressMessages.java +++ b/core/src/main/java/art/arcane/iris/core/localization/RuntimeProgressMessages.java @@ -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_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_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_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 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_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}"); @@ -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_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_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_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"); @@ -124,16 +118,10 @@ public final class RuntimeProgressMessages { STUDIO_STAGE_CREATE_WORLD, STUDIO_STAGE_APPLY_WORLD_RULES, STUDIO_STAGE_PREPARE_GENERATOR, - STUDIO_STAGE_REQUEST_ENTRY_CHUNK, - STUDIO_STAGE_RESOLVE_SAFE_ENTRY, STUDIO_STAGE_TELEPORT_PLAYER, STUDIO_STAGE_FINALIZE_OPEN, STUDIO_STAGE_CLEANUP, 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_FAILED, WORLD_CREATE_LIFECYCLE_ACTION_READY, diff --git a/core/src/main/java/art/arcane/iris/core/nms/INMS.java b/core/src/main/java/art/arcane/iris/core/nms/INMS.java index 15a627d02..3fce9b724 100644 --- a/core/src/main/java/art/arcane/iris/core/nms/INMS.java +++ b/core/src/main/java/art/arcane/iris/core/nms/INMS.java @@ -70,7 +70,7 @@ public class INMS { private static INMSBinding bind() { boolean disableNms = IrisSettings.get().getGeneral().isDisableNMS(); 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."); return new NMSBinding1X(); } @@ -87,7 +87,6 @@ public class INMS { } catch (Throwable e) { IrisLogging.reportError(e); IrisLogging.error("Failed to determine server minecraft version!"); - e.printStackTrace(); if (e instanceof IllegalStateException illegalStateException) { throw illegalStateException; } @@ -96,19 +95,18 @@ public class INMS { } private static INMSBinding bindExact(String code) { - IrisLogging.info("Locating exact NMS Binding for " + code); + IrisLogging.debug("Locating exact NMS Binding for " + code); try { Class clazz = Class.forName("art.arcane.iris.core.nms." + code + ".NMSBinding"); Object candidate = clazz.getConstructor().newInstance(); 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; } throw new IllegalStateException("Exact NMS binding class for " + code + " does not implement " + INMSBinding.class.getName()); } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); if (e instanceof IllegalStateException illegalStateException) { throw illegalStateException; } diff --git a/core/src/main/java/art/arcane/iris/core/nms/INMSBinding.java b/core/src/main/java/art/arcane/iris/core/nms/INMSBinding.java index 2a9f975f9..6148d35de 100644 --- a/core/src/main/java/art/arcane/iris/core/nms/INMSBinding.java +++ b/core/src/main/java/art/arcane/iris/core/nms/INMSBinding.java @@ -54,8 +54,6 @@ import org.bukkit.generator.ChunkGenerator; import org.bukkit.inventory.ItemStack; import java.awt.Color; -import java.io.IOException; -import java.nio.file.Path; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -272,14 +270,6 @@ public interface INMSBinding { 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) { return true; } diff --git a/core/src/main/java/art/arcane/iris/core/pack/ContentKeyValidator.java b/core/src/main/java/art/arcane/iris/core/pack/ContentKeyValidator.java index 7d34f66c9..707860655 100644 --- a/core/src/main/java/art/arcane/iris/core/pack/ContentKeyValidator.java +++ b/core/src/main/java/art/arcane/iris/core/pack/ContentKeyValidator.java @@ -251,7 +251,7 @@ public final class ContentKeyValidator { /** * 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() { String property = System.getProperty(STRICT_PROPERTY); diff --git a/core/src/main/java/art/arcane/iris/core/pack/IrisPack.java b/core/src/main/java/art/arcane/iris/core/pack/IrisPack.java index 875b33f69..175e7baf4 100644 --- a/core/src/main/java/art/arcane/iris/core/pack/IrisPack.java +++ b/core/src/main/java/art/arcane/iris/core/pack/IrisPack.java @@ -223,7 +223,6 @@ public class IrisPack { IO.writeAll(ws, generateWorkspaceConfig()); } catch (IOException e1) { IrisLogging.reportError(e1); - e1.printStackTrace(); } } diff --git a/core/src/main/java/art/arcane/iris/core/pack/IrisPackRepository.java b/core/src/main/java/art/arcane/iris/core/pack/IrisPackRepository.java index 37b1b00e7..bc7efa6fb 100644 --- a/core/src/main/java/art/arcane/iris/core/pack/IrisPackRepository.java +++ b/core/src/main/java/art/arcane/iris/core/pack/IrisPackRepository.java @@ -20,6 +20,7 @@ package art.arcane.iris.core.pack; import art.arcane.iris.core.localization.IrisLanguage; import art.arcane.iris.core.localization.RuntimeUiMessages; +import art.arcane.iris.spi.IrisLogging; import art.arcane.iris.spi.IrisPlatforms; import art.arcane.iris.core.service.StudioSVC; import art.arcane.volmlib.util.format.Form; @@ -126,7 +127,7 @@ public class IrisPackRepository { try { FileUtils.copyDirectory(work.listFiles()[0], pack); } catch (IOException e) { - e.printStackTrace(); + IrisLogging.reportError("Failed to install Iris pack into " + pack + ".", e); } })).execute(sender, whenComplete); } else { diff --git a/core/src/main/java/art/arcane/iris/core/pack/PackRiverValidator.java b/core/src/main/java/art/arcane/iris/core/pack/PackRiverValidator.java index 82847fb26..86191acb3 100644 --- a/core/src/main/java/art/arcane/iris/core/pack/PackRiverValidator.java +++ b/core/src/main/java/art/arcane/iris/core/pack/PackRiverValidator.java @@ -94,7 +94,15 @@ final class PackRiverValidator { validateWater(path + ".water", water, errors); } 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 && "SINKHOLE_GROTTO".equals(stringValue(terrain, "terminalMode", "DRY_CHANNEL")); @@ -108,7 +116,7 @@ final class PackRiverValidator { if (Double.isFinite(meanderStrength) && meanderStrength > cellSize) { 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) { validateSinkholeCapability( @@ -130,8 +138,14 @@ final class PackRiverValidator { PackJsonFieldChecks.validateOptionalIntegerRange(path, topology, "minimumSourcesPerTile", 0, 64, errors); PackJsonFieldChecks.validateOptionalIntegerRange(path, topology, "sinkSearchReaches", 0, 7, 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, "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, "terrainSlopeWeight", 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, "bankWidth", path, 0D, 2048D, 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, "maxBankWidth", 0D, 2048D, 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.validateOptionalIntegerRange(path, terrain, "maxIncision", 0, 512, 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.validateOptionalIntegerRange(path, terrain, "meanderSubdivisions", 1, 64, 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.validateOptionalDoubleRange(path, terrain, "dryContinuationChance", 0D, 1D, 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, "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 errors) { @@ -188,6 +230,7 @@ final class PackRiverValidator { } private static void validateTopologyComplexity( + File packFolder, String path, JSONObject topology, JSONObject terrain, @@ -201,6 +244,14 @@ final class PackRiverValidator { int meanderSubdivisions = integerValue(terrain, "meanderSubdivisions", 8); double maximumChannelWidth = doubleValue(terrain, "maxChannelWidth", 10D); 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 || tileCells < 1 || tileCells > 64 || !Double.isFinite(siteJitter) || siteJitter < 0D || siteJitter > 0.49D @@ -208,10 +259,16 @@ final class PackRiverValidator { || !Double.isFinite(meanderStrength) || meanderStrength < 0D || meanderStrength > 1024D || meanderSubdivisions < 1 || meanderSubdivisions > 64 || !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; } - 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( cellSize, tileCells, @@ -239,6 +296,7 @@ final class PackRiverValidator { PackJsonFieldChecks.validateOptionalIntegerRange(path, caves, "grottoHorizontalRadius", 2, 128, errors); PackJsonFieldChecks.validateOptionalIntegerRange(path, caves, "grottoVerticalRadius", 2, 128, 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, "maxFloodDepth", 4, 256, 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, "caveEntryMultiplier", 0D, 16D, errors); PackJsonFieldChecks.validateOptionalEnum(path, override, "terminalMode", TERMINAL_MODES, errors); - validateBiomePool(packFolder, path, override, "channelBiomes", RiverBiomeRole.CHANNEL, true, errors, warnings); - validateBiomePool(packFolder, path, override, "bankBiomes", RiverBiomeRole.BANK, true, errors, warnings); - validateBiomePool(packFolder, path, override, "mouthBiomes", RiverBiomeRole.MOUTH, true, errors, warnings); - validateBiomePool(packFolder, path, override, "dryBiomes", RiverBiomeRole.DRY, true, errors, warnings); + boolean validateSuitability = reachesOverworldNativeStructureRoles( + packFolder, resourceKey, resourceType, contexts); + validateBiomePool(packFolder, path, override, "channelBiomes", RiverBiomeRole.CHANNEL, true, + 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, - errors, warnings); + validateSuitability, errors, warnings); if ("SINKHOLE_GROTTO".equals(stringValue(override, "terminalMode", null))) { validateOverrideSinkhole( packFolder, @@ -545,18 +609,24 @@ final class PackRiverValidator { } private static void validateBiomePools(File packFolder, String path, JSONObject biomes, boolean allowNull, + boolean validateSuitability, List errors, List warnings) { validateStyle(packFolder, biomes, "selectionStyle", path, errors); - validateBiomePool(packFolder, path, biomes, "channel", RiverBiomeRole.CHANNEL, allowNull, errors, warnings); - validateBiomePool(packFolder, path, biomes, "bank", RiverBiomeRole.BANK, allowNull, errors, warnings); - validateBiomePool(packFolder, path, biomes, "mouth", RiverBiomeRole.MOUTH, allowNull, errors, warnings); - validateBiomePool(packFolder, path, biomes, "dry", RiverBiomeRole.DRY, allowNull, errors, warnings); + validateBiomePool(packFolder, path, biomes, "channel", RiverBiomeRole.CHANNEL, allowNull, + validateSuitability, errors, warnings); + validateBiomePool(packFolder, path, biomes, "bank", RiverBiomeRole.BANK, allowNull, + 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, - errors, warnings); + validateSuitability, errors, warnings); } private static void validateBiomePool(File packFolder, String path, JSONObject owner, String field, RiverBiomeRole role, boolean allowNull, + boolean validateSuitability, List errors, List warnings) { if (!owner.has(field)) { return; @@ -588,10 +658,36 @@ final class PackRiverValidator { errors.add(entryPath + " references missing biome '" + key + "'."); 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 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, JSONObject biome, List warnings) { if (biome == null || role == RiverBiomeRole.DRY || role == RiverBiomeRole.FLOODED_CAVE) { @@ -661,6 +757,26 @@ final class PackRiverValidator { 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, List errors) { if (!owner.has(field)) { diff --git a/core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java b/core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java index f89b9ec23..5c1409493 100644 --- a/core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java +++ b/core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java @@ -173,7 +173,6 @@ public class PregenCacheImpl implements PregenCache { return readPlate(x, z, input); } catch (IOException e) { IrisLogging.error("Failed to read pregen cache " + file); - e.printStackTrace(); IrisLogging.reportError(e); } @@ -195,7 +194,6 @@ public class PregenCacheImpl implements PregenCache { plate.dirty = false; } catch (Throwable e) { IrisLogging.error("Failed to write pregen cache " + (file != null ? file : "c." + plate.x + "." + plate.z)); - e.printStackTrace(); IrisLogging.reportError(e); } } diff --git a/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java b/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java index a56aa24a7..6ae69c815 100644 --- a/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java +++ b/core/src/main/java/art/arcane/iris/core/pregenerator/methods/AsyncPregenMethod.java @@ -464,7 +464,6 @@ public class AsyncPregenMethod implements PregeneratorMethod { IrisLogging.reportError(throwable); } catch (Throwable e) { - e.printStackTrace(); } return null; @@ -489,7 +488,6 @@ public class AsyncPregenMethod implements PregeneratorMethod { } } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); } finally { try { markFinished(success); @@ -520,7 +518,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { String suppressedText = suppressed <= 0 ? "" : " suppressed=" + suppressed; // 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. - IrisLogging.info("Async pregen chunk load at " + x + "," + z + IrisLogging.debug("Async pregen chunk load at " + x + "," + z + " is still pending after " + slowRequestWarningSeconds + "s." + " adaptiveLimit=" + adaptiveInFlightLimit.get() + suppressedText + " " + metricsSnapshot()); @@ -583,7 +581,7 @@ public class AsyncPregenMethod implements PregeneratorMethod { } 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"); if (e instanceof InvocationTargetException) { IrisLogging.reportError(e); - e.printStackTrace(); } } return 0; @@ -1023,7 +1020,6 @@ public class AsyncPregenMethod implements PregeneratorMethod { } catch (Throwable e) { IrisLogging.reportError(e); IrisLogging.error("Failed to reset worker threads"); - e.printStackTrace(); } return i; }); diff --git a/core/src/main/java/art/arcane/iris/core/project/IrisCodeWorkspace.java b/core/src/main/java/art/arcane/iris/core/project/IrisCodeWorkspace.java index f2527319a..debbcc9cd 100644 --- a/core/src/main/java/art/arcane/iris/core/project/IrisCodeWorkspace.java +++ b/core/src/main/java/art/arcane/iris/core/project/IrisCodeWorkspace.java @@ -119,7 +119,6 @@ public class IrisCodeWorkspace { IO.writeAll(ws, rendered); } catch (Throwable e1) { IrisLogging.reportError(e1); - e1.printStackTrace(); } } diff --git a/core/src/main/java/art/arcane/iris/core/project/IrisPackageCompiler.java b/core/src/main/java/art/arcane/iris/core/project/IrisPackageCompiler.java index 2011305f6..b6513060a 100644 --- a/core/src/main/java/art/arcane/iris/core/project/IrisPackageCompiler.java +++ b/core/src/main/java/art/arcane/iris/core/project/IrisPackageCompiler.java @@ -281,7 +281,6 @@ public class IrisPackageCompiler { return p; } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); } sender.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.IRIS_PROJECT_FAILED)); return null; diff --git a/core/src/main/java/art/arcane/iris/core/project/IrisProject.java b/core/src/main/java/art/arcane/iris/core/project/IrisProject.java index a07a9b713..8dabf6146 100644 --- a/core/src/main/java/art/arcane/iris/core/project/IrisProject.java +++ b/core/src/main/java/art/arcane/iris/core/project/IrisProject.java @@ -27,7 +27,6 @@ import art.arcane.volmlib.util.exceptions.IrisException; import art.arcane.iris.util.common.plugin.VolmitSender; import art.arcane.iris.util.common.scheduling.J; import lombok.Data; -import org.bukkit.GameMode; import org.bukkit.World; import java.io.File; @@ -45,6 +44,7 @@ public class IrisProject { private File path; private String name; private PlatformChunkGenerator activeProvider; + private StudioOpenCoordinator.StudioOpenKind activeOpenKind; public IrisProject(File path) { this.path = path; @@ -132,10 +132,6 @@ public class IrisProject { return; } - if (sender.isPlayer() && sender.player() != null) { - J.runEntity(sender.player(), () -> sender.player().setGameMode(GameMode.CREATIVE)); - } - activeProvider = IrisToolbelt.access(result.world()); maintenanceWorld = result.world(); if (maintenanceWorld != null) { IrisToolbelt.beginWorldMaintenance(maintenanceWorld, "studio-open"); diff --git a/core/src/main/java/art/arcane/iris/core/project/SchemaBuilder.java b/core/src/main/java/art/arcane/iris/core/project/SchemaBuilder.java index 870139495..df0ea3e4b 100644 --- a/core/src/main/java/art/arcane/iris/core/project/SchemaBuilder.java +++ b/core/src/main/java/art/arcane/iris/core/project/SchemaBuilder.java @@ -888,7 +888,6 @@ public class SchemaBuilder { a.put(j); } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); } } else { a.put(function.apply(gg)); diff --git a/core/src/main/java/art/arcane/iris/core/project/StudioOpenProgressReporter.java b/core/src/main/java/art/arcane/iris/core/project/StudioOpenProgressReporter.java index 0e8f13e75..6ae3375db 100644 --- a/core/src/main/java/art/arcane/iris/core/project/StudioOpenProgressReporter.java +++ b/core/src/main/java/art/arcane/iris/core/project/StudioOpenProgressReporter.java @@ -189,8 +189,6 @@ final class StudioOpenProgressReporter { case "create_world" -> IrisLanguage.text(RuntimeProgressMessages.STUDIO_STAGE_CREATE_WORLD); case "apply_world_rules" -> IrisLanguage.text(RuntimeProgressMessages.STUDIO_STAGE_APPLY_WORLD_RULES); case "prepare_generator" -> IrisLanguage.text(RuntimeProgressMessages.STUDIO_STAGE_PREPARE_GENERATOR); - case "request_entry_chunk" -> IrisLanguage.text(RuntimeProgressMessages.STUDIO_STAGE_REQUEST_ENTRY_CHUNK); - case "resolve_safe_entry" -> IrisLanguage.text(RuntimeProgressMessages.STUDIO_STAGE_RESOLVE_SAFE_ENTRY); case "teleport_player" -> IrisLanguage.text(RuntimeProgressMessages.STUDIO_STAGE_TELEPORT_PLAYER); case "finalize_open" -> IrisLanguage.text(RuntimeProgressMessages.STUDIO_STAGE_FINALIZE_OPEN); case "cleanup" -> IrisLanguage.text(RuntimeProgressMessages.STUDIO_STAGE_CLEANUP); diff --git a/core/src/main/java/art/arcane/iris/core/protocol/CursorInfoRequestHandler.java b/core/src/main/java/art/arcane/iris/core/protocol/CursorInfoRequestHandler.java new file mode 100644 index 000000000..bebeb7165 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/core/protocol/CursorInfoRequestHandler.java @@ -0,0 +1,24 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2026 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package art.arcane.iris.core.protocol; + +@FunctionalInterface +public interface CursorInfoRequestHandler { + void handle(String sessionId, int blockX, int blockZ); +} diff --git a/core/src/main/java/art/arcane/iris/core/protocol/IrisCursorRequestService.java b/core/src/main/java/art/arcane/iris/core/protocol/IrisCursorRequestService.java new file mode 100644 index 000000000..18ad63efd --- /dev/null +++ b/core/src/main/java/art/arcane/iris/core/protocol/IrisCursorRequestService.java @@ -0,0 +1,205 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2026 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package art.arcane.iris.core.protocol; + +import art.arcane.iris.engine.framework.Engine; +import art.arcane.iris.engine.framework.PreservationRegistry; +import art.arcane.iris.spi.IrisLogging; +import art.arcane.iris.spi.IrisServices; +import art.arcane.iris.spi.protocol.IrisProtocol; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public final class IrisCursorRequestService implements CursorInfoRequestHandler { + private static final int DEFAULT_MAX_PENDING_SESSIONS = 2048; + + private final EngineResolver engineResolver; + private final IrisSessionRegistry registry; + private final Executor executor; + private final int maxPendingSessions; + private final LinkedHashMap pending; + private final AtomicBoolean drainScheduled; + private final AtomicLong coalesced; + private final AtomicLong droppedSaturated; + private final AtomicLong droppedNoEngine; + private final AtomicLong droppedNoSession; + private final AtomicLong resolved; + + IrisCursorRequestService( + EngineResolver engineResolver, + IrisSessionRegistry registry, + Executor executor, + int maxPendingSessions + ) { + this.engineResolver = Objects.requireNonNull(engineResolver, "engine resolver"); + this.registry = Objects.requireNonNull(registry, "session registry"); + this.executor = Objects.requireNonNull(executor, "executor"); + this.maxPendingSessions = Math.max(1, maxPendingSessions); + this.pending = new LinkedHashMap<>(); + this.drainScheduled = new AtomicBoolean(false); + this.coalesced = new AtomicLong(0L); + this.droppedSaturated = new AtomicLong(0L); + this.droppedNoEngine = new AtomicLong(0L); + this.droppedNoSession = new AtomicLong(0L); + this.resolved = new AtomicLong(0L); + } + + public static IrisCursorRequestService create(EngineResolver engineResolver, IrisSessionRegistry registry) { + AtomicInteger threadIndex = new AtomicInteger(0); + ThreadPoolExecutor executor = new ThreadPoolExecutor( + 1, + 1, + 30L, + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), + runnable -> { + Thread thread = new Thread(runnable); + thread.setName("Iris Cursor Lookup " + threadIndex.incrementAndGet()); + thread.setDaemon(true); + thread.setPriority(Thread.MIN_PRIORITY); + return thread; + }); + executor.allowCoreThreadTimeOut(true); + PreservationRegistry preservation = IrisServices.getOrNull(PreservationRegistry.class); + if (preservation != null) { + preservation.register(executor); + } + return new IrisCursorRequestService(engineResolver, registry, executor, DEFAULT_MAX_PENDING_SESSIONS); + } + + @Override + public void handle(String sessionId, int blockX, int blockZ) { + if (sessionId == null || sessionId.isBlank()) { + droppedNoSession.incrementAndGet(); + return; + } + int shed = 0; + synchronized (pending) { + PendingRequest previous = pending.put(sessionId, new PendingRequest(sessionId, blockX, blockZ)); + if (previous != null) { + coalesced.incrementAndGet(); + } + while (pending.size() > maxPendingSessions) { + Iterator> iterator = pending.entrySet().iterator(); + iterator.next(); + iterator.remove(); + shed++; + } + } + if (shed > 0) { + droppedSaturated.addAndGet(shed); + } + scheduleDrain(); + } + + public void clearSession(String sessionId) { + if (sessionId == null || sessionId.isBlank()) { + return; + } + synchronized (pending) { + pending.remove(sessionId); + } + } + + public int pendingSize() { + synchronized (pending) { + return pending.size(); + } + } + + public long coalescedCount() { + return coalesced.get(); + } + + public long droppedSaturatedCount() { + return droppedSaturated.get(); + } + + public long droppedNoEngineCount() { + return droppedNoEngine.get(); + } + + public long droppedNoSessionCount() { + return droppedNoSession.get(); + } + + public long resolvedCount() { + return resolved.get(); + } + + private void scheduleDrain() { + if (!drainScheduled.compareAndSet(false, true)) { + return; + } + try { + executor.execute(this::drainPending); + } catch (RuntimeException failure) { + drainScheduled.set(false); + IrisLogging.reportError(failure); + } + } + + private void drainPending() { + while (true) { + PendingRequest request; + synchronized (pending) { + Iterator iterator = pending.values().iterator(); + if (!iterator.hasNext()) { + drainScheduled.set(false); + return; + } + request = iterator.next(); + iterator.remove(); + } + try { + process(request); + } catch (Throwable failure) { + IrisLogging.reportError(failure); + } + } + } + + private void process(PendingRequest request) { + IrisSession session = registry.get(request.sessionId()); + if (session == null || !session.isReady() || !session.hasCapability(IrisProtocol.CAPABILITY_CURSOR)) { + droppedNoSession.incrementAndGet(); + return; + } + Engine engine = engineResolver.resolve(request.sessionId()); + if (engine == null || engine.isClosed()) { + droppedNoEngine.incrementAndGet(); + return; + } + session.send(IrisCursorResolver.resolve(engine, request.blockX(), request.blockZ())); + resolved.incrementAndGet(); + } + + private record PendingRequest(String sessionId, int blockX, int blockZ) { + } +} diff --git a/core/src/main/java/art/arcane/iris/core/protocol/IrisProtocolServer.java b/core/src/main/java/art/arcane/iris/core/protocol/IrisProtocolServer.java index 04e1f74f7..c5c3e956a 100644 --- a/core/src/main/java/art/arcane/iris/core/protocol/IrisProtocolServer.java +++ b/core/src/main/java/art/arcane/iris/core/protocol/IrisProtocolServer.java @@ -45,11 +45,13 @@ public final class IrisProtocolServer { private final AtomicLong cursorOutOfBounds; private final AtomicLong visionTileForwarded; private final AtomicLong visionRateLimited; + private final AtomicLong visionOutOfBounds; private final AtomicLong pregenRegionDeltasBroadcast; private final AtomicLong studioHotloadsBroadcast; private final AtomicLong toastsBroadcast; private final AtomicLong toastsSent; private volatile EngineResolver engineResolver; + private volatile CursorInfoRequestHandler cursorInfoHandler; private volatile VisionTileRequestHandler visionTileHandler; public IrisProtocolServer(IrisSessionRegistry registry, long serverCapabilities, String serverBrand, boolean irisActive) { @@ -73,6 +75,7 @@ public final class IrisProtocolServer { this.cursorOutOfBounds = new AtomicLong(0L); this.visionTileForwarded = new AtomicLong(0L); this.visionRateLimited = new AtomicLong(0L); + this.visionOutOfBounds = new AtomicLong(0L); this.pregenRegionDeltasBroadcast = new AtomicLong(0L); this.studioHotloadsBroadcast = new AtomicLong(0L); this.toastsBroadcast = new AtomicLong(0L); @@ -87,6 +90,10 @@ public final class IrisProtocolServer { this.engineResolver = engineResolver; } + public void setCursorInfoHandler(CursorInfoRequestHandler cursorInfoHandler) { + this.cursorInfoHandler = cursorInfoHandler; + } + public void setVisionTileHandler(VisionTileRequestHandler visionTileHandler) { this.visionTileHandler = visionTileHandler; } @@ -279,6 +286,10 @@ public final class IrisProtocolServer { return visionRateLimited.get(); } + public long visionOutOfBoundsCount() { + return visionOutOfBounds.get(); + } + public long pregenRegionDeltasBroadcastCount() { return pregenRegionDeltasBroadcast.get(); } @@ -338,6 +349,12 @@ public final class IrisProtocolServer { cursorRateLimited.incrementAndGet(); return; } + CursorInfoRequestHandler handler = cursorInfoHandler; + if (handler != null) { + handler.handle(session.id(), request.blockX(), request.blockZ()); + cursorInfoServed.incrementAndGet(); + return; + } EngineResolver resolver = engineResolver; if (resolver == null) { noEngineDrops.incrementAndGet(); @@ -357,6 +374,10 @@ public final class IrisProtocolServer { capabilityRejected.incrementAndGet(); return; } + if (visionTileOutOfWorldBounds(request)) { + visionOutOfBounds.incrementAndGet(); + return; + } if (!session.allowVisionTile(clock.getAsLong())) { visionRateLimited.incrementAndGet(); return; @@ -373,4 +394,15 @@ public final class IrisProtocolServer { private static boolean outOfWorldBounds(int coordinate) { return coordinate > IrisProtocol.MAX_QUERY_BLOCK_COORDINATE || coordinate < -IrisProtocol.MAX_QUERY_BLOCK_COORDINATE; } + + private static boolean visionTileOutOfWorldBounds(IrisMessage.VisionTileRequest request) { + int zoom = Math.max(0, Math.min(request.zoomLevel(), IrisTileEncoder.MAX_ZOOM_LEVEL)); + long tileSize = (long) IrisTileEncoder.TILE_PIXELS << zoom; + long minBlockX = (long) request.tileX() * tileSize; + long minBlockZ = (long) request.tileZ() * tileSize; + long maxBlockX = minBlockX + tileSize - 1L; + long maxBlockZ = minBlockZ + tileSize - 1L; + long limit = IrisProtocol.MAX_QUERY_BLOCK_COORDINATE; + return minBlockX < -limit || maxBlockX > limit || minBlockZ < -limit || maxBlockZ > limit; + } } diff --git a/core/src/main/java/art/arcane/iris/core/protocol/IrisVisionRequestService.java b/core/src/main/java/art/arcane/iris/core/protocol/IrisVisionRequestService.java index f74b4928b..39dc2d393 100644 --- a/core/src/main/java/art/arcane/iris/core/protocol/IrisVisionRequestService.java +++ b/core/src/main/java/art/arcane/iris/core/protocol/IrisVisionRequestService.java @@ -26,8 +26,12 @@ import art.arcane.iris.spi.protocol.IrisMessage; import art.arcane.iris.spi.protocol.IrisProtocol; import java.util.ArrayDeque; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; @@ -37,7 +41,9 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; public final class IrisVisionRequestService implements VisionTileRequestHandler { - private static final int DEFAULT_MAX_PENDING = 64; + private static final int DEFAULT_MAX_PENDING = 8_192; + private static final int MAX_PENDING_PER_SESSION = 8; + private static final int MAX_DRAIN_WORKERS = 2; private static final long SHED_LOG_INTERVAL_MILLIS = 60_000L; private static final int SEQUENCE_WRAP_GUARD = Integer.MAX_VALUE - 1024; @@ -45,7 +51,11 @@ public final class IrisVisionRequestService implements VisionTileRequestHandler private final IrisSessionRegistry registry; private final Executor executor; private final int maxPending; - private final ArrayDeque pending; + private final LinkedHashMap> pendingBySession; + private final ArrayDeque sessionOrder; + private final Set scheduledSessions; + private final AtomicInteger activeDrains; + private int pendingCount; /** * One counter per session, not one per (session, tile, zoom). The client only ever compares sequences * within a single tile key, so a session-wide monotonic counter satisfies the "newer wins" contract in @@ -63,7 +73,10 @@ public final class IrisVisionRequestService implements VisionTileRequestHandler this.registry = Objects.requireNonNull(registry, "session registry"); this.executor = Objects.requireNonNull(executor, "executor"); this.maxPending = Math.max(1, maxPending); - this.pending = new ArrayDeque<>(); + this.pendingBySession = new LinkedHashMap<>(); + this.sessionOrder = new ArrayDeque<>(); + this.scheduledSessions = new HashSet<>(); + this.activeDrains = new AtomicInteger(0); this.sequences = new ConcurrentHashMap<>(); this.nextShedLogAt = new AtomicLong(0L); this.droppedSaturated = new AtomicLong(0L); @@ -91,20 +104,42 @@ public final class IrisVisionRequestService implements VisionTileRequestHandler @Override public void handle(String sessionId, int tileX, int tileZ, int zoomLevel) { - PendingRequest request = new PendingRequest(sessionId, tileX, tileZ, zoomLevel); + if (sessionId == null || sessionId.isBlank()) { + droppedNoSession.incrementAndGet(); + return; + } + TileKey tileKey = new TileKey(tileX, tileZ, zoomLevel); + PendingRequest request = new PendingRequest(sessionId, tileKey); int shed = 0; - synchronized (pending) { - while (pending.size() >= maxPending) { - pending.pollFirst(); + synchronized (pendingBySession) { + LinkedHashMap sessionPending = pendingBySession.get(sessionId); + if (sessionPending != null && sessionPending.containsKey(tileKey)) { + sessionPending.put(tileKey, request); + scheduleDrains(); + return; + } + if (sessionPending != null + && sessionPending.size() >= Math.min(MAX_PENDING_PER_SESSION, maxPending)) { + removeOldest(sessionPending); + pendingCount--; shed++; } - pending.addLast(request); + while (pendingCount >= maxPending) { + shedOnePendingSession(); + shed++; + } + sessionPending = pendingBySession.computeIfAbsent(sessionId, ignored -> new LinkedHashMap<>()); + sessionPending.put(tileKey, request); + pendingCount++; + if (scheduledSessions.add(sessionId)) { + sessionOrder.addLast(sessionId); + } } if (shed > 0) { droppedSaturated.addAndGet(shed); logShed(); } - executor.execute(this::drainOne); + scheduleDrains(); } /** @@ -112,12 +147,18 @@ public final class IrisVisionRequestService implements VisionTileRequestHandler * disconnects or unregisters so neither structure grows with the player count over a server's uptime. */ public void clearSession(String sessionId) { - if (sessionId == null || sessionId.isEmpty()) { + if (sessionId == null || sessionId.isBlank()) { return; } sequences.remove(sessionId); - synchronized (pending) { - pending.removeIf((PendingRequest request) -> sessionId.equals(request.sessionId())); + synchronized (pendingBySession) { + LinkedHashMap removed = pendingBySession.remove(sessionId); + if (removed != null) { + pendingCount -= removed.size(); + } + if (scheduledSessions.remove(sessionId)) { + sessionOrder.removeIf(sessionId::equals); + } } } @@ -138,20 +179,103 @@ public final class IrisVisionRequestService implements VisionTileRequestHandler } public int pendingSize() { - synchronized (pending) { - return pending.size(); + synchronized (pendingBySession) { + return pendingCount; } } - private void drainOne() { - PendingRequest request; - synchronized (pending) { - request = pending.pollFirst(); + private void scheduleDrains() { + while (true) { + int active = activeDrains.get(); + synchronized (pendingBySession) { + if (pendingCount <= active || active >= MAX_DRAIN_WORKERS) { + return; + } + } + if (!activeDrains.compareAndSet(active, active + 1)) { + continue; + } + try { + executor.execute(this::drainPending); + } catch (RuntimeException failure) { + activeDrains.decrementAndGet(); + IrisLogging.reportError(failure); + return; + } } - if (request == null) { + } + + private void drainPending() { + try { + while (true) { + PendingRequest request = pollNextRequest(); + if (request == null) { + return; + } + try { + process(request); + } catch (Throwable failure) { + IrisLogging.reportError(failure); + } + } + } finally { + activeDrains.decrementAndGet(); + scheduleDrains(); + } + } + + private PendingRequest pollNextRequest() { + synchronized (pendingBySession) { + while (true) { + String sessionId = sessionOrder.pollFirst(); + if (sessionId == null) { + return null; + } + scheduledSessions.remove(sessionId); + LinkedHashMap sessionPending = pendingBySession.get(sessionId); + if (sessionPending == null || sessionPending.isEmpty()) { + pendingBySession.remove(sessionId); + continue; + } + PendingRequest request = removeOldest(sessionPending); + pendingCount--; + if (sessionPending.isEmpty()) { + pendingBySession.remove(sessionId); + } else if (scheduledSessions.add(sessionId)) { + sessionOrder.addLast(sessionId); + } + return request; + } + } + } + + private void shedOnePendingSession() { + String selectedSession = null; + int selectedSize = 0; + for (Map.Entry> entry : pendingBySession.entrySet()) { + if (entry.getValue().size() > selectedSize) { + selectedSession = entry.getKey(); + selectedSize = entry.getValue().size(); + } + } + if (selectedSession == null) { return; } - process(request); + LinkedHashMap selected = pendingBySession.get(selectedSession); + removeOldest(selected); + pendingCount--; + if (selected.isEmpty()) { + pendingBySession.remove(selectedSession); + if (scheduledSessions.remove(selectedSession)) { + sessionOrder.removeIf(selectedSession::equals); + } + } + } + + private static PendingRequest removeOldest(LinkedHashMap requests) { + Map.Entry oldest = requests.entrySet().iterator().next(); + requests.remove(oldest.getKey()); + return oldest.getValue(); } private void process(PendingRequest request) { @@ -166,7 +290,13 @@ public final class IrisVisionRequestService implements VisionTileRequestHandler return; } int sequence = nextSequence(request); - List chunks = IrisTileEncoder.encode(engine, request.tileX(), request.tileZ(), request.zoomLevel(), sequence); + List chunks = IrisTileEncoder.encode( + engine, + request.tile().tileX(), + request.tile().tileZ(), + request.tile().zoomLevel(), + sequence + ); for (IrisMessage.VisionTile chunk : chunks) { session.send(chunk); } @@ -189,6 +319,9 @@ public final class IrisVisionRequestService implements VisionTileRequestHandler IrisLogging.warn("vision: request queue saturated at " + maxPending + ", shed " + droppedSaturated.get() + " total"); } - private record PendingRequest(String sessionId, int tileX, int tileZ, int zoomLevel) { + private record TileKey(int tileX, int tileZ, int zoomLevel) { + } + + private record PendingRequest(String sessionId, TileKey tile) { } } diff --git a/core/src/main/java/art/arcane/iris/core/runtime/GoldenHashEngine.java b/core/src/main/java/art/arcane/iris/core/runtime/GoldenHashEngine.java index d540de662..5540161aa 100644 --- a/core/src/main/java/art/arcane/iris/core/runtime/GoldenHashEngine.java +++ b/core/src/main/java/art/arcane/iris/core/runtime/GoldenHashEngine.java @@ -505,7 +505,7 @@ public final class GoldenHashEngine { MessageArgument.untrusted("file", diag.getName()) )); } - IrisLogging.info("goldenhash diag: chunk=" + chunkX + "," + chunkZ + " repeatStable=" + diffs.isEmpty() + " -> " + diag.getAbsolutePath()); + IrisLogging.debug("goldenhash diag: chunk=" + chunkX + "," + chunkZ + " repeatStable=" + diffs.isEmpty() + " -> " + diag.getAbsolutePath()); } catch (Throwable e) { IrisLogging.reportError(e); feedback.fail(IrisLanguage.plain( diff --git a/core/src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java b/core/src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java index 2d9b2ffd3..b5d4e8f09 100644 --- a/core/src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java +++ b/core/src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java @@ -21,7 +21,7 @@ import art.arcane.iris.util.common.scheduling.J; import art.arcane.volmlib.util.exceptions.IrisException; import art.arcane.volmlib.util.bukkit.WorldIdentity; import org.bukkit.Bukkit; -import org.bukkit.Chunk; +import org.bukkit.GameMode; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; @@ -37,12 +37,10 @@ import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -53,15 +51,10 @@ import java.util.function.Supplier; public final class StudioOpenCoordinator { private static final long STUDIO_CLOSE_TIMEOUT_SECONDS = 120L; - private static final long STUDIO_ENTRY_LOAD_TIMEOUT_SECONDS = 30L; - private static final long STUDIO_ENTRY_RELEASE_TIMEOUT_SECONDS = 15L; private static final long STUDIO_STRUCTURE_ACTIVATION_TIMEOUT_SECONDS = 30L; - private static final long STUDIO_ENTRY_CLEANUP_BOUNDARY_SECONDS = 120L; private static volatile StudioOpenCoordinator instance; - private final EntryLoadRegistry entryLoads; private StudioOpenCoordinator() { - entryLoads = new EntryLoadRegistry(); } public static StudioOpenCoordinator get() { @@ -115,9 +108,6 @@ public final class StudioOpenCoordinator { AtomicBoolean activeAdmission = Objects.requireNonNull( admission, "Studio teleport admission"); - if (!isStudioTeleportAdmitted(activeAdmission, deadlineNanos)) { - return studioTeleportDeadlineFailure("entry loading"); - } PlatformChunkGenerator provider = project.getActiveProvider(); if (provider == null) { return CompletableFuture.failedFuture(new IllegalStateException( @@ -128,77 +118,30 @@ public final class StudioOpenCoordinator { return CompletableFuture.failedFuture(new IllegalStateException( "Studio world is not loaded.")); } - Location entryAnchor = WorldRuntimeControlService.get().resolveEntryAnchor(world); - if (entryAnchor == null) { + Location entry = WorldRuntimeControlService.get().resolveEntryAnchor(world, provider); + if (entry == null) { return CompletableFuture.failedFuture(new IllegalStateException( - "Studio entry anchor could not be resolved.")); + "Studio entry point could not be resolved.")); } - EntryChunkResolution entryResolution = loadEntryChunk(world, entryAnchor); - CompletableFuture teleportOperation = beforeStudioTeleportDeadline( - entryResolution.chunk(), - activeAdmission, - deadlineNanos, - "entry loading") - .thenCompose(ignored -> { - if (!isStudioTeleportAdmitted(activeAdmission, deadlineNanos)) { - return studioTeleportDeadlineFailure("safe-entry resolution"); - } - return beforeStudioTeleportDeadline( - entryResolution.safeEntry(), - activeAdmission, - deadlineNanos, - "safe-entry resolution"); - }) - .thenCompose(entry -> { - if (entry == null) { - return CompletableFuture.failedFuture(new IllegalStateException( - "Studio entry point could not be resolved.")); - } - if (System.nanoTime() >= deadlineNanos - || !activeAdmission.compareAndSet(true, false)) { - return studioTeleportDeadlineFailure("native teleport delegation"); - } - CompletableFuture teleport = - WorldRuntimeControlService.get().teleport(player, entry); - if (teleport == null) { - return CompletableFuture.failedFuture(new IllegalStateException( - "Studio native teleport returned no completion future.")); - } - return teleport; - }); - return teleportOperation; - } - - private CompletableFuture beforeStudioTeleportDeadline( - CompletableFuture stage, - AtomicBoolean admission, - long deadlineNanos, - String stageName - ) { - if (stage == null) { + if (System.nanoTime() >= deadlineNanos + || !activeAdmission.compareAndSet(true, false)) { + return studioTeleportDeadlineFailure("native teleport delegation"); + } + CompletableFuture teleport = project.getActiveOpenKind() == StudioOpenKind.STANDARD + ? WorldRuntimeControlService.get().teleportInMode(player, entry, GameMode.SPECTATOR) + : WorldRuntimeControlService.get().teleport(player, entry); + if (teleport == null) { return CompletableFuture.failedFuture(new IllegalStateException( - "Studio " + stageName + " returned no completion future.")); + "Studio native teleport returned no completion future.")); } long remainingNanos = deadlineNanos - System.nanoTime(); - if (!admission.get() || remainingNanos <= 0L) { - return studioTeleportDeadlineFailure(stageName); + if (remainingNanos <= 0L) { + teleport.completeExceptionally(new TimeoutException( + "Studio teleport deadline expired before native teleport completion.")); + return teleport; } - CompletableFuture bounded = new CompletableFuture<>(); - stage.whenComplete((value, failure) -> { - if (failure == null) { - bounded.complete(value); - } else { - bounded.completeExceptionally(failure); - } - }); - CompletableFuture.delayedExecutor(remainingNanos, TimeUnit.NANOSECONDS) - .execute(() -> bounded.completeExceptionally(new TimeoutException( - "Studio teleport deadline expired during " + stageName + "."))); - return bounded; - } - - private boolean isStudioTeleportAdmitted(AtomicBoolean admission, long deadlineNanos) { - return admission.get() && System.nanoTime() < deadlineNanos; + teleport.orTimeout(remainingNanos, TimeUnit.NANOSECONDS); + return teleport; } private CompletableFuture studioTeleportDeadlineFailure(String stageName) { @@ -209,13 +152,10 @@ public final class StudioOpenCoordinator { private void executeOpen(StudioOpenRequest request, CompletableFuture future) { World world = null; PlatformChunkGenerator provider = null; - CompletableFuture entryLoadFuture = null; - CompletableFuture entryUseFuture = null; CompletableFuture nativeTeleportFuture = null; try { long openStart = System.nanoTime(); long t = openStart; - entryLoads.rejectNewOpen(); updateStage(request, "resolve_dimension", 0.04D); if (IrisToolbelt.getDimension(request.dimensionKey()) == null) { throw new IrisException("Dimension cannot be found for id " + request.dimensionKey() + "."); @@ -263,75 +203,11 @@ public final class StudioOpenCoordinator { } t = logStudioPhase(request, "resolve_entry_anchor", t, openStart); - long entryPrecomputeStartedAt = System.nanoTime(); - CompletableFuture preparedEntryChunks = CompletableFuture.completedFuture(null); - if (requiresLoadedEntry(request)) { - if (!(provider instanceof BukkitChunkGenerator bukkitGenerator)) { - throw new IllegalStateException( - "Studio runtime provider cannot prepare its entry chunks."); - } - preparedEntryChunks = bukkitGenerator.prepareStudioEntryChunks( - world, - entryAnchor.getBlockX() >> 4, - entryAnchor.getBlockZ() >> 4 - ); - } - updateStage(request, "prepare_structure_rings", 0.79D); endStudioEntryBootstrap(world, provider); t = logStudioPhase(request, "prepare_structure_rings", t, openStart); - if (requiresLoadedEntry(request)) { - try { - preparedEntryChunks.get( - STUDIO_ENTRY_LOAD_TIMEOUT_SECONDS, - TimeUnit.SECONDS - ); - } catch (TimeoutException e) { - throw new IllegalStateException( - "Studio entry chunk precompute did not finish in time.", e); - } - t = logOverlappedStudioPhase( - request, - "prepare_entry_chunks", - entryPrecomputeStartedAt, - openStart - ); - } - - Location safeEntry = entryAnchor; - if (requiresLoadedEntry(request)) { - updateStage(request, "load_entry_chunk", 0.80D); - int entryChunkX = entryAnchor.getBlockX() >> 4; - int entryChunkZ = entryAnchor.getBlockZ() >> 4; - EntryChunkResolution entryResolution = loadEntryChunk(world, entryAnchor); - CompletableFuture useSettlement = new CompletableFuture<>(); - entryUseFuture = useSettlement; - entryLoadFuture = entryResolution.safeEntry().thenCompose(ignored -> useSettlement); - entryLoads.register(request.worldName(), entryLoadFuture); - try { - entryResolution.chunk().get(STUDIO_ENTRY_LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS); - } catch (TimeoutException e) { - throw new IllegalStateException("Studio entry chunk did not load in time at " - + entryChunkX + "," + entryChunkZ + " — chunk system may be stalled.", e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IllegalStateException("Studio entry chunk load was interrupted at " - + entryChunkX + "," + entryChunkZ + ".", e); - } - t = logStudioPhase(request, "load_entry_chunk", t, openStart); - - updateStage(request, "resolve_safe_entry", 0.84D); - try { - safeEntry = entryResolution.safeEntry().get(5L, TimeUnit.SECONDS); - } catch (TimeoutException e) { - throw new IllegalStateException("Studio entry point resolution timed out — region thread may be stalled."); - } - if (safeEntry == null) { - throw new IllegalStateException("Studio entry point could not be resolved for world \"" + request.worldName() + "\"."); - } - t = logStudioPhase(request, "resolve_safe_entry", t, openStart); - } + Location entryLocation = entryAnchor; if (request.openKind().teleportThroughStandardEntry() && request.playerName() != null @@ -341,16 +217,19 @@ public final class StudioOpenCoordinator { if (player == null) { throw new IllegalStateException("Player \"" + request.playerName() + "\" is not online."); } - Boolean teleported; try { - nativeTeleportFuture = WorldRuntimeControlService.get().teleport(player, safeEntry); + nativeTeleportFuture = WorldRuntimeControlService.get().teleportInMode( + player, + entryLocation, + GameMode.SPECTATOR); if (nativeTeleportFuture == null) { throw new IllegalStateException( "Studio native teleport returned no completion future."); } teleported = nativeTeleportFuture.get(60L, TimeUnit.SECONDS); } catch (TimeoutException e) { + nativeTeleportFuture.completeExceptionally(e); throw new IllegalStateException("Studio teleport timed out — destination region may still be generating."); } if (!Boolean.TRUE.equals(teleported)) { @@ -361,6 +240,7 @@ public final class StudioOpenCoordinator { updateStage(request, "finalize_open", 1.00D); if (request.project() != null) { + request.project().setActiveOpenKind(request.openKind()); request.project().setActiveProvider(provider); } if (request.openKind().openWorkspace() && request.project() != null) { @@ -369,17 +249,10 @@ public final class StudioOpenCoordinator { runOpenFinalizer(request.onDone(), world); t = logStudioPhase(request, "finalize_open", t, openStart); - settleEntryUseAfterOperation(entryUseFuture, nativeTeleportFuture); - if (entryLoadFuture != null) { - entryLoadFuture.get(STUDIO_ENTRY_RELEASE_TIMEOUT_SECONDS, TimeUnit.SECONDS); - } - - IrisLogging.info("Studio open: " + world.getName() + " ready in " + IrisLogging.debug("Studio open: " + world.getName() + " ready in " + elapsedMillis(openStart) + "ms"); - entryLoads.release(request.worldName(), entryLoadFuture); - future.complete(new StudioOpenResult(world, safeEntry)); + future.complete(new StudioOpenResult(world, entryLocation)); } catch (Throwable e) { - settleEntryUseAfterOperation(entryUseFuture, nativeTeleportFuture); abandonStudioEntryBootstrap(world, e); IrisLogging.reportError("Studio open failed for world \"" + request.worldName() + "\".", e); if (!request.retainOnFailure()) { @@ -390,15 +263,7 @@ public final class StudioOpenCoordinator { deferFailedOpenCleanupToRestart( provider, request.worldName(), - world, - entryLoadFuture); - } else if (requiresDeferredEntryCleanup(entryLoadFuture)) { - deferFailedOpenCleanup( - entryLoadFuture, - provider, - request.worldName(), - world, - request.project()); + world); } else { try { CompletableFuture cleanup = cleanupFailedOpen( @@ -406,50 +271,27 @@ public final class StudioOpenCoordinator { request.worldName(), world, request.project()); - entryLoads.releaseAfterSuccessfulCompletion( - request.worldName(), - entryLoadFuture, - cleanup); cleanup.get(45L, TimeUnit.SECONDS); } catch (Throwable cleanupError) { IrisLogging.reportError("Studio cleanup failed for world \"" + request.worldName() + "\".", unwrapFailure(cleanupError)); } } - } else if (entryLoadFuture != null) { - entryLoads.releaseAfterSuccessfulCompletion( - request.worldName(), - entryLoadFuture, - entryLoadFuture); } future.completeExceptionally(e); } } - static boolean requiresLoadedEntry(StudioOpenRequest request) { - Objects.requireNonNull(request, "Studio open request"); - return request.openKind().teleportThroughStandardEntry() - && request.playerName() != null - && !request.playerName().isBlank(); - } - private void deferFailedOpenCleanupToRestart( PlatformChunkGenerator provider, String worldName, - World world, - CompletableFuture entryLoadFuture + World world ) { if (provider != null || world != null || transientWorldStorageExists(worldName)) { queueStartupCleanup( worldName, new IllegalStateException("Studio cleanup deferred across the queued server restart.")); } - if (entryLoadFuture != null) { - entryLoads.releaseAfterSuccessfulCompletion( - worldName, - entryLoadFuture, - entryLoadFuture); - } } private boolean transientWorldStorageExists(String worldName) { @@ -467,7 +309,7 @@ public final class StudioOpenCoordinator { private long logStudioPhase(StudioOpenRequest request, String phase, long t, long openStart) { long now = System.nanoTime(); - IrisLogging.info("[Studio timing] world=%s kind=%s phase=%s duration=%dms cumulative=%dms", + IrisLogging.debug("[Studio timing] world=%s kind=%s phase=%s duration=%dms cumulative=%dms", request.worldName(), request.openKind().name().toLowerCase(Locale.ROOT), phase, @@ -476,22 +318,6 @@ public final class StudioOpenCoordinator { return now; } - private long logOverlappedStudioPhase( - StudioOpenRequest request, - String phase, - long phaseStart, - long openStart - ) { - long now = System.nanoTime(); - IrisLogging.info("[Studio timing] world=%s kind=%s phase=%s duration=%dms cumulative=%dms", - request.worldName(), - request.openKind().name().toLowerCase(Locale.ROOT), - phase, - TimeUnit.NANOSECONDS.toMillis(now - phaseStart), - TimeUnit.NANOSECONDS.toMillis(now - openStart)); - return now; - } - private void runOpenFinalizer(Consumer finalizer, World world) throws InterruptedException, ExecutionException, TimeoutException { if (finalizer == null) { @@ -511,91 +337,13 @@ public final class StudioOpenCoordinator { } private void logMeasuredStudioPhase(StudioOpenRequest request, String phase, long duration) { - IrisLogging.info("[Studio timing] world=%s kind=%s phase=%s duration=%dms", + IrisLogging.debug("[Studio timing] world=%s kind=%s phase=%s duration=%dms", request.worldName(), request.openKind().name().toLowerCase(Locale.ROOT), phase, duration); } - private EntryChunkResolution loadEntryChunk(World world, Location entryAnchor) { - int chunkX = entryAnchor.getBlockX() >> 4; - int chunkZ = entryAnchor.getBlockZ() >> 4; - CompletableFuture chunkFuture = new CompletableFuture<>(); - CompletableFuture safeEntryFuture = new CompletableFuture<>(); - EntryChunkResolution resolution = new EntryChunkResolution(chunkFuture, safeEntryFuture); - CompletableFuture requested; - try { - requested = WorldRuntimeControlService.get().requestChunkAsync( - world, - chunkX, - chunkZ, - true, - true); - } catch (Throwable throwable) { - failEntryResolution(resolution, throwable); - return resolution; - } - if (requested == null) { - failEntryResolution(resolution, new IllegalStateException( - "Entry-chunk async request did not return a future at " + chunkX + "," + chunkZ + ".")); - return resolution; - } - requested.whenComplete((chunk, failure) -> { - if (failure != null) { - failEntryResolution(resolution, failure); - return; - } - if (chunk == null) { - failEntryResolution(resolution, new IllegalStateException( - "Entry-chunk async request returned no chunk at " + chunkX + "," + chunkZ + ".")); - return; - } - Runnable resolve = () -> { - chunkFuture.complete(chunk); - try { - Location safeEntry = WorldRuntimeControlService.findTopSafeStudioLocation(world, entryAnchor); - safeEntryFuture.complete(safeEntry); - } catch (Throwable resolutionFailure) { - failEntryResolution(resolution, resolutionFailure); - } - }; - try { - if (J.isOwnedByCurrentRegion(world, chunkX, chunkZ)) { - resolve.run(); - return; - } - if (!J.runRegion(world, chunkX, chunkZ, resolve)) { - failEntryResolution(resolution, new IllegalStateException( - "Failed to resolve the entry chunk on its owning region at " - + chunkX + "," + chunkZ + ".")); - } - } catch (Throwable schedulingFailure) { - failEntryResolution(resolution, schedulingFailure); - } - }); - return resolution; - } - - private void failEntryResolution(EntryChunkResolution resolution, Throwable failure) { - resolution.chunk().completeExceptionally(failure); - resolution.safeEntry().completeExceptionally(failure); - } - - private void settleEntryUseAfterOperation( - CompletableFuture entryUseFuture, - CompletableFuture operation - ) { - if (entryUseFuture == null) { - return; - } - if (operation == null) { - entryUseFuture.complete(null); - return; - } - operation.whenComplete((ignored, failure) -> entryUseFuture.complete(null)); - } - private void endStudioEntryBootstrap(World world, PlatformChunkGenerator provider) { if (!(provider instanceof BukkitChunkGenerator bukkitGenerator)) { throw new IllegalStateException("Studio runtime provider cannot finish its entry bootstrap."); @@ -678,40 +426,6 @@ public final class StudioOpenCoordinator { } } - private void deferFailedOpenCleanup( - CompletableFuture entryLoadFuture, - PlatformChunkGenerator provider, - String worldName, - World world, - IrisProject project - ) { - CompletableFuture cleanup = deferCleanupUntilEntrySettlement( - entryLoadFuture, - () -> cleanupFailedOpen(provider, worldName, world, project)); - entryLoads.releaseAfterSuccessfulCompletion(worldName, entryLoadFuture, cleanup); - cleanup.whenComplete((ignored, cleanupFailure) -> { - if (cleanupFailure != null) { - IrisLogging.reportError("Deferred Studio cleanup failed for world \"" - + worldName + "\".", unwrapFailure(cleanupFailure)); - } - }); - observeDeferredEntryCleanupBoundary(entryLoadFuture, worldName); - } - - static CompletableFuture deferCleanupUntilEntrySettlement( - CompletableFuture entryLoadFuture, - Supplier> cleanup - ) { - Objects.requireNonNull(entryLoadFuture, "Studio entry-load future"); - Objects.requireNonNull(cleanup, "Studio cleanup"); - return entryLoadFuture.handle((ignored, entryFailure) -> null) - .thenCompose(ignored -> invokePhase(cleanup)); - } - - static boolean requiresDeferredEntryCleanup(CompletableFuture entryLoadFuture) { - return entryLoadFuture != null && !entryLoadFuture.isDone(); - } - private CompletableFuture cleanupFailedOpen( PlatformChunkGenerator provider, String worldName, @@ -724,33 +438,6 @@ public final class StudioOpenCoordinator { : CompletableFuture.failedFuture(result.failureCause())); } - private void observeDeferredEntryCleanupBoundary( - CompletableFuture entryLoadFuture, - String worldName - ) { - CompletableFuture.delayedExecutor( - STUDIO_ENTRY_CLEANUP_BOUNDARY_SECONDS, - TimeUnit.SECONDS).execute(() -> { - if (entryLoadFuture.isDone()) { - return; - } - TimeoutException timeout = new TimeoutException( - "Studio entry generation remained active for " - + STUDIO_ENTRY_CLEANUP_BOUNDARY_SECONDS - + " seconds after the open timeout for \"" + worldName + "\"."); - boolean queued = queueStartupCleanup(worldName, timeout); - String recovery = queued - ? " The transient world is queued for deletion at the next clean startup." - : " The transient world could not be queued for startup deletion."; - IrisLogging.reportError( - "Studio world \"" + worldName - + "\" remains loaded because its entry generation is still active;" - + " Iris did not unload or close its generator." - + recovery, - timeout); - }); - } - private CompletableFuture closeWorldCoordinated( PlatformChunkGenerator provider, String worldName, @@ -801,6 +488,7 @@ public final class StudioOpenCoordinator { IrisProject project ) { AtomicBoolean unloadConfirmed = new AtomicBoolean(false); + AtomicBoolean unloadStarted = new AtomicBoolean(false); AtomicBoolean folderDeleted = new AtomicBoolean(!deleteFolder); AtomicBoolean terminalTimeout = new AtomicBoolean(false); if (world != null) { @@ -809,16 +497,20 @@ public final class StudioOpenCoordinator { CompletableFuture sequence = sequenceStudioClose( () -> evacuateWorldFamily(worldName, world), - () -> unloadWorldFamily(worldName, world).thenRun(() -> { - if (terminalTimeout.get()) { - throw new CompletionException(new TimeoutException( - "Studio close stopped after its terminal timeout.")); - } - unloadConfirmed.set(true); - if (project != null) { - project.setActiveProvider(null); - } - }), + () -> { + unloadStarted.set(true); + return unloadWorldFamily(worldName, world).thenRun(() -> { + if (terminalTimeout.get()) { + throw new CompletionException(new TimeoutException( + "Studio close stopped after its terminal timeout.")); + } + unloadConfirmed.set(true); + if (project != null) { + project.setActiveProvider(null); + project.setActiveOpenKind(null); + } + }); + }, () -> provider == null ? CompletableFuture.completedFuture(null) : provider.closeAsync(), () -> deleteFolder ? deleteWorldFamily(worldName).thenRun(() -> folderDeleted.set(true)) @@ -838,6 +530,7 @@ public final class StudioOpenCoordinator { )) .exceptionally(throwable -> { Throwable failure = unwrapFailure(throwable); + requestRestartAfterPartialClose(worldName, unloadStarted.get()); boolean queued = deleteFolder && queueStartupCleanup(worldName, failure); return new StudioCloseResult( worldName, @@ -854,6 +547,12 @@ public final class StudioOpenCoordinator { }); } + static void requestRestartAfterPartialClose(String worldName, boolean unloadStarted) { + if (unloadStarted) { + ServerConfigurator.restart("Studio close failed after world unload began for \"" + worldName + "\"."); + } + } + static CompletableFuture sequenceStudioClose( Supplier> evacuate, Supplier> unload, @@ -1039,11 +738,6 @@ public final class StudioOpenCoordinator { } for (String staleWorldName : staleWorldNames) { - if (entryLoads.isFenced(staleWorldName)) { - IrisLogging.warn("Skipping stale Studio cleanup for \"" + staleWorldName - + "\" because its open or deferred cleanup is still active."); - continue; - } try { StudioCloseResult cleanupResult = closeWorldCoordinated( null, @@ -1202,6 +896,10 @@ public final class StudioOpenCoordinator { true, true, IrisCreator.DatapackPreparation.REUSE_LOADED_RUNTIME_IF_READY), + OBJECT( + false, + true, + IrisCreator.DatapackPreparation.REUSE_LOADED_RUNTIME_IF_READY), JIGSAW( false, false, @@ -1232,7 +930,7 @@ public final class StudioOpenCoordinator { } public boolean prepareGeneratorState() { - return this == STANDARD; + return this != JIGSAW; } public IrisCreator.DatapackPreparation datapackPreparation() { @@ -1258,69 +956,4 @@ public final class StudioOpenCoordinator { } } - private record EntryChunkResolution( - CompletableFuture chunk, - CompletableFuture safeEntry - ) { - } - - static final class EntryLoadRegistry { - private final ConcurrentHashMap> entryLoads; - - EntryLoadRegistry() { - entryLoads = new ConcurrentHashMap<>(); - } - - void register(String worldName, CompletableFuture entryLoadFuture) { - Objects.requireNonNull(worldName, "Studio world name"); - Objects.requireNonNull(entryLoadFuture, "Studio entry-load future"); - CompletableFuture existing = entryLoads.putIfAbsent(worldName, entryLoadFuture); - if (existing != null) { - throw new IllegalStateException("Studio open or deferred cleanup is already active for \"" - + worldName + "\"."); - } - } - - void release( - String worldName, - CompletableFuture entryLoadFuture - ) { - if (worldName == null || entryLoadFuture == null) { - return; - } - entryLoads.remove(worldName, entryLoadFuture); - } - - void releaseAfterSuccessfulCompletion( - String worldName, - CompletableFuture entryLoadFuture, - CompletableFuture completion - ) { - Objects.requireNonNull(completion, "Studio lifecycle completion"); - completion.whenComplete((ignored, failure) -> { - if (failure == null) { - release(worldName, entryLoadFuture); - } - }); - } - - void rejectNewOpen() { - ArrayList activeWorlds = new ArrayList<>(); - for (Map.Entry> entry : entryLoads.entrySet()) { - activeWorlds.add(entry.getKey()); - } - if (activeWorlds.isEmpty()) { - return; - } - Collections.sort(activeWorlds); - throw new IllegalStateException("A previous Studio open or deferred cleanup is still active for " - + String.join(", ", activeWorlds) - + ". Wait for it to settle before opening another Studio; a reported cleanup failure" - + " requires the queued clean restart."); - } - - boolean isFenced(String worldName) { - return entryLoads.containsKey(worldName); - } - } } diff --git a/core/src/main/java/art/arcane/iris/core/runtime/WorldRuntimeControlService.java b/core/src/main/java/art/arcane/iris/core/runtime/WorldRuntimeControlService.java index a95614204..695261cc7 100644 --- a/core/src/main/java/art/arcane/iris/core/runtime/WorldRuntimeControlService.java +++ b/core/src/main/java/art/arcane/iris/core/runtime/WorldRuntimeControlService.java @@ -14,6 +14,7 @@ import art.arcane.iris.util.common.scheduling.J; import io.papermc.lib.PaperLib; import org.bukkit.Bukkit; import org.bukkit.Chunk; +import org.bukkit.GameMode; import org.bukkit.GameRule; import org.bukkit.HeightMap; import org.bukkit.Location; @@ -21,7 +22,6 @@ import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; -import org.bukkit.block.data.Levelled; import org.bukkit.block.data.Waterlogged; import org.bukkit.entity.Player; import org.bukkit.event.world.TimeSkipEvent; @@ -32,10 +32,13 @@ import org.bukkit.util.VoxelShape; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.LinkedHashSet; +import java.util.Objects; import java.util.Optional; import java.util.OptionalLong; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; public final class WorldRuntimeControlService { private static final int MAX_SAFE_ENTRY_HORIZONTAL_RADIUS = 15; @@ -312,18 +315,75 @@ public final class WorldRuntimeControlService { } public CompletableFuture teleport(Player player, Location location) { + return scheduleTeleport(player, location, null); + } + + public CompletableFuture teleportInMode( + Player player, + Location location, + GameMode gameMode + ) { + return scheduleTeleport( + player, + location, + Objects.requireNonNull(gameMode, "Teleport game mode")); + } + + static CompletableFuture scheduleTeleport( + Player player, + Location location, + GameMode gameMode + ) { + return scheduleTeleport(player, location, gameMode, PaperLib::teleportAsync); + } + + static CompletableFuture scheduleTeleport( + Player player, + Location location, + GameMode gameMode, + TeleportExecutor teleporter + ) { if (player == null || location == null) { return CompletableFuture.completedFuture(false); } + Objects.requireNonNull(teleporter, "teleporter"); CompletableFuture future = new CompletableFuture<>(); + GameModeRestore modeRestore = new GameModeRestore(player); + AtomicReference> activeTeleport = new AtomicReference<>(); + future.whenComplete((success, failure) -> { + if (Boolean.TRUE.equals(success)) { + return; + } + CompletableFuture teleport = activeTeleport.get(); + if (teleport != null && !teleport.isDone()) { + teleport.cancel(false); + } + modeRestore.restore(); + }); boolean scheduled = J.runEntity(player, () -> { try { - CompletableFuture teleportFuture = PaperLib.teleportAsync(player, location); + if (future.isDone()) { + return; + } + if (gameMode != null) { + modeRestore.apply(gameMode); + if (future.isDone()) { + modeRestore.restore(); + return; + } + } + CompletableFuture teleportFuture = teleporter.teleport(player, location); if (teleportFuture == null) { future.complete(false); return; } + activeTeleport.set(teleportFuture); + if (future.isDone()) { + teleportFuture.cancel(false); + modeRestore.restore(); + return; + } teleportFuture.whenComplete((success, throwable) -> { if (throwable != null) { @@ -332,8 +392,9 @@ public final class WorldRuntimeControlService { } if (Boolean.TRUE.equals(success)) { - J.runEntity(player, () -> IrisServices.get(BoardSVC.class).updatePlayer(player)); - future.complete(true); + if (future.complete(true)) { + J.runEntity(player, () -> IrisServices.get(BoardSVC.class).updatePlayer(player)); + } return; } @@ -344,7 +405,7 @@ public final class WorldRuntimeControlService { } }); if (!scheduled) { - return CompletableFuture.failedFuture(new IllegalStateException("Failed to schedule teleport for " + player.getName() + ".")); + future.completeExceptionally(new IllegalStateException("Failed to schedule teleport for " + player.getName() + ".")); } return future; @@ -431,62 +492,6 @@ public final class WorldRuntimeControlService { return null; } - static Location findTopSafeStudioLocation(World world, Location source) { - Location dryLocation = findTopSafeLocation(world, source); - if (dryLocation != null) { - return dryLocation; - } - - int sourceX = source.getBlockX(); - int sourceZ = source.getBlockZ(); - int chunkX = sourceX >> 4; - int chunkZ = sourceZ >> 4; - if (!world.isChunkLoaded(chunkX, chunkZ)) { - return null; - } - - int minimumFloorY = world.getMinHeight(); - int maximumFloorY = world.getMaxHeight() - 3; - if (minimumFloorY > maximumFloorY) { - return null; - } - - int minimumX = chunkX << 4; - int minimumZ = chunkZ << 4; - int maximumX = minimumX + 15; - int maximumZ = minimumZ + 15; - for (int radius = 0; radius <= MAX_SAFE_ENTRY_HORIZONTAL_RADIUS; radius++) { - for (int offsetX = -radius; offsetX <= radius; offsetX++) { - for (int offsetZ = -radius; offsetZ <= radius; offsetZ++) { - if (Math.max(Math.abs(offsetX), Math.abs(offsetZ)) != radius) { - continue; - } - - int x = sourceX + offsetX; - int z = sourceZ + offsetZ; - if (x < minimumX || x > maximumX || z < minimumZ || z > maximumZ) { - continue; - } - - Location waterLocation = findSafeWaterSurfaceLocationInColumn( - world, - x, - z, - minimumFloorY, - maximumFloorY, - source.getYaw(), - source.getPitch() - ); - if (waterLocation != null) { - return waterLocation; - } - } - } - } - - return null; - } - private static Location findSafeLocationInColumn( World world, int x, @@ -515,48 +520,6 @@ public final class WorldRuntimeControlService { return null; } - private static Location findSafeWaterSurfaceLocationInColumn( - World world, - int x, - int z, - int minimumFloorY, - int maximumFloorY, - float yaw, - float pitch - ) { - int surfaceY = world.getHighestBlockYAt(x, z, HeightMap.MOTION_BLOCKING_NO_LEAVES); - if (surfaceY <= minimumFloorY || surfaceY > maximumFloorY) { - return null; - } - - Block surface = world.getBlockAt(x, surfaceY, z); - if (!isStableWater(surface)) { - return null; - } - - Block support = world.getBlockAt(x, surfaceY - 1, z); - if (!isStableWater(support) && !isSafeFloor(support)) { - return null; - } - - Block feet = world.getBlockAt(x, surfaceY + 1, z); - Block head = world.getBlockAt(x, surfaceY + 2, z); - if (!isClearEntryBlock(feet) || !isClearEntryBlock(head)) { - return null; - } - - return new Location(world, x + BLOCK_CENTER, surfaceY + 1D, z + BLOCK_CENTER, yaw, pitch); - } - - private static boolean isStableWater(Block block) { - if (block.getType() != Material.WATER || !block.isLiquid()) { - return false; - } - - BlockData blockData = block.getBlockData(); - return blockData instanceof Levelled levelled && levelled.getLevel() == 0; - } - private static boolean isSafeFloor(Block block) { Material material = block.getType(); if (material == null @@ -813,4 +776,50 @@ public final class WorldRuntimeControlService { Method method = instance.getClass().getMethod(methodName); return method.invoke(instance); } + + @FunctionalInterface + interface TeleportExecutor { + CompletableFuture teleport(Player player, Location location); + } + + private static final class GameModeRestore { + private final Player player; + private final AtomicBoolean changed; + private final AtomicBoolean restored; + private GameMode previousMode; + + private GameModeRestore(Player player) { + this.player = player; + changed = new AtomicBoolean(false); + restored = new AtomicBoolean(false); + } + + private void apply(GameMode targetMode) { + GameMode currentMode = player.getGameMode(); + if (currentMode == targetMode) { + return; + } + previousMode = currentMode; + changed.set(true); + player.setGameMode(targetMode); + } + + private void restore() { + if (!changed.get() || !restored.compareAndSet(false, true)) { + return; + } + Runnable restoration = () -> { + try { + player.setGameMode(previousMode); + } catch (Throwable failure) { + IrisLogging.reportError("Failed to restore a player's game mode after an unsuccessful teleport.", failure); + } + }; + if (!J.runEntity(player, restoration)) { + IrisLogging.reportError( + "Failed to restore a player's game mode after an unsuccessful teleport.", + new IllegalStateException("The player entity scheduler rejected the game-mode restoration.")); + } + } + } } diff --git a/core/src/main/java/art/arcane/iris/core/service/ExternalDataSVC.java b/core/src/main/java/art/arcane/iris/core/service/ExternalDataSVC.java index 45b2aaab0..00b291701 100644 --- a/core/src/main/java/art/arcane/iris/core/service/ExternalDataSVC.java +++ b/core/src/main/java/art/arcane/iris/core/service/ExternalDataSVC.java @@ -66,7 +66,7 @@ public class ExternalDataSVC implements IrisService { @Override public void onEnable() { - IrisLogging.info("Loading ExternalDataProvider..."); + IrisLogging.debug("Loading ExternalDataProvider..."); // enable() registers every enabled service as a listener; self-registration here // doubled every handler invocation. @@ -147,7 +147,8 @@ public class ExternalDataSVC implements IrisService { public Optional getItemStack(Identifier key, KMap customNbt) { Optional provider = activeProviders.stream().filter(p -> p.isValidProvider(key, DataType.ITEM)).findFirst(); if (provider.isEmpty()) { - IrisLogging.warn("No matching Provider found for modded material \"%s\"!", key); + IrisLogging.warnOnce("external-provider:item:" + key, + "No matching Provider found for modded material \"%s\"!", key); return Optional.empty(); } try { @@ -161,7 +162,8 @@ public class ExternalDataSVC implements IrisService { public void processUpdate(Engine engine, Block block, Identifier blockId) { Optional provider = activeProviders.stream().filter(p -> p.isValidProvider(blockId, DataType.BLOCK)).findFirst(); if (provider.isEmpty()) { - IrisLogging.warn("No matching Provider found for modded material \"%s\"!", blockId); + IrisLogging.warnOnce("external-provider:block:" + blockId, + "No matching Provider found for modded material \"%s\"!", blockId); return; } provider.get().processUpdate(engine, block, blockId); @@ -170,7 +172,8 @@ public class ExternalDataSVC implements IrisService { public Entity spawnMob(Location location, Identifier mobId) { Optional provider = activeProviders.stream().filter(p -> p.isValidProvider(mobId, DataType.ENTITY)).findFirst(); if (provider.isEmpty()) { - IrisLogging.warn("No matching Provider found for modded mob \"%s\"!", mobId); + IrisLogging.warnOnce("external-provider:mob:" + mobId, + "No matching Provider found for modded mob \"%s\"!", mobId); return null; } try { @@ -243,7 +246,7 @@ public class ExternalDataSVC implements IrisService { Class rawProviderClass = Class.forName(definition.className(), true, ExternalDataSVC.class.getClassLoader()); Class providerClass = rawProviderClass.asSubclass(ExternalDataProvider.class); ExternalDataProvider provider = providerClass.getDeclaredConstructor().newInstance(); - IrisLogging.info(definition.pluginId() + " found, loading " + providerClass.getSimpleName() + "..."); + IrisLogging.debug(definition.pluginId() + " found, loading " + providerClass.getSimpleName() + "..."); activateProvider(provider); } catch (Throwable error) { IrisLogging.reportError("Failed to create Iris external data provider " + definition.className() + ".", error); @@ -263,7 +266,7 @@ public class ExternalDataSVC implements IrisService { providers.add(provider); } activeProviders.add(provider); - IrisLogging.info("Enabled ExternalDataProvider for %s.", provider.getPluginId()); + IrisLogging.debug("Enabled ExternalDataProvider for %s.", provider.getPluginId()); } catch (Throwable error) { IrisLogging.reportError("Failed to enable Iris external data provider " + provider.getPluginId() + ".", error); } diff --git a/core/src/main/java/art/arcane/iris/core/service/JigsawStudioService.java b/core/src/main/java/art/arcane/iris/core/service/JigsawStudioService.java index 6dd2631a2..c4f6ef504 100644 --- a/core/src/main/java/art/arcane/iris/core/service/JigsawStudioService.java +++ b/core/src/main/java/art/arcane/iris/core/service/JigsawStudioService.java @@ -362,7 +362,7 @@ public final class JigsawStudioService implements IrisService, JigsawStudioMenuC evaluations.remove(displacedRequestId); previewRenderer.removeRequest(displacedRequestId); } - IrisLogging.info("Jigsaw Studio authoring registered: world=%s structure=%s bays=%d", + IrisLogging.debug("Jigsaw Studio authoring registered: world=%s structure=%s bays=%d", world.getName(), activeGenerator.getSession().structureKey(), activeGenerator.getLayout().bays().size()); scheduleInitialEvaluation(next); scheduleOnlinePlayers(world.getUID()); @@ -460,7 +460,7 @@ public final class JigsawStudioService implements IrisService, JigsawStudioMenuC previewRenderer.forgetRequest(request.requestId()); JigsawStudioActivation.deactivate(request.packKey(), request.requestId()); clearWorldPlayerContexts(world.getUID()); - IrisLogging.info("Jigsaw Studio authoring unregistered: world=%s", world.getName()); + IrisLogging.debug("Jigsaw Studio authoring unregistered: world=%s", world.getName()); } private void scheduleUnregisterRetry(World world, UUID requestId) { @@ -2385,7 +2385,7 @@ public final class JigsawStudioService implements IrisService, JigsawStudioMenuC request.source().invalidateStructureResources(); message(player, "Deleted Jigsaw project '" + request.structureKey() + "' and " + result.removedResourceCount() + " owned resource(s)."); - IrisLogging.info("Jigsaw Studio project deleted: structure=%s resources=%d", + IrisLogging.debug("Jigsaw Studio project deleted: structure=%s resources=%d", request.structureKey(), result.removedResourceCount()); } catch (Throwable exception) { IrisLogging.reportError(exception); @@ -6086,7 +6086,7 @@ public final class JigsawStudioService implements IrisService, JigsawStudioMenuC if (unchanged) { scheduleEvaluation(studio); } - IrisLogging.info("Jigsaw Studio saved: structure=%s piece=%s object=%s connectors=%d status=%s", + IrisLogging.debug("Jigsaw Studio saved: structure=%s piece=%s object=%s connectors=%d status=%s", request.structureKey(), coordinator.saveIdentity().variantKey(), assembly.objectKey(), connectors.size(), result.status()); } catch (Throwable exception) { diff --git a/core/src/main/java/art/arcane/iris/core/service/ObjectStudioSaveService.java b/core/src/main/java/art/arcane/iris/core/service/ObjectStudioSaveService.java index 4d5b7fd4c..7470939b0 100644 --- a/core/src/main/java/art/arcane/iris/core/service/ObjectStudioSaveService.java +++ b/core/src/main/java/art/arcane/iris/core/service/ObjectStudioSaveService.java @@ -107,7 +107,7 @@ public class ObjectStudioSaveService implements IrisService { String packKey = engine.getDimension() == null ? null : engine.getDimension().getLoadKey(); studios.put(world.getUID(), new ActiveStudio(world.getUID(), layout, objectsDirs, packKey)); - IrisLogging.info("Object Studio live-save registered: world=%s cells=%d packs=%d", + IrisLogging.debug("Object Studio live-save registered: world=%s cells=%d packs=%d", world.getName(), layout.cells().size(), objectsDirs.size()); } @@ -118,7 +118,7 @@ public class ObjectStudioSaveService implements IrisService { if (removed.packKey != null) { ObjectStudioActivation.deactivate(removed.packKey); } - IrisLogging.info("Object Studio live-save unregistered: world=%s", world.getName()); + IrisLogging.debug("Object Studio live-save unregistered: world=%s", world.getName()); } } @@ -147,7 +147,7 @@ public class ObjectStudioSaveService implements IrisService { } player.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.OBJECT_STUDIO_SAVE_SERVICE_OBJECT_STUDIO_SAVING_X_X, MessageArgument.untrusted("pack", String.valueOf(cell.pack())), MessageArgument.untrusted("key", String.valueOf(cell.key())), MessageArgument.untrusted("w", String.valueOf(cell.w())), MessageArgument.untrusted("h", String.valueOf(cell.h())), MessageArgument.untrusted("d", String.valueOf(cell.d())))); - IrisLogging.info("Object Studio save triggered by %s for %s/%s", player.getName(), cell.pack(), cell.key()); + IrisLogging.debug("Object Studio save triggered by %s for %s/%s", player.getName(), cell.pack(), cell.key()); J.runRegion(world, cell.chunkMinX(), cell.chunkMinZ(), () -> { try { captureAndSave(studio, world, cell, player); @@ -192,7 +192,7 @@ public class ObjectStudioSaveService implements IrisService { double targetY = cell.originY() + cell.h() + 2.0D; Location location = new Location(world, targetX, targetY, targetZ); J.runEntity(player, () -> PaperLib.teleportAsync(player, location)); - IrisLogging.info("Object Studio goto: %s -> %s at %.0f,%.0f,%.0f", + IrisLogging.debug("Object Studio goto: %s -> %s at %.0f,%.0f,%.0f", player.getName(), objectKey, location.getX(), location.getY(), location.getZ()); return true; } @@ -266,7 +266,7 @@ public class ObjectStudioSaveService implements IrisService { parent.mkdirs(); } snapshot.write(targetFile); - IrisLogging.info("Object Studio saved: %s/%s (%dx%dx%d)", + IrisLogging.debug("Object Studio saved: %s/%s (%dx%dx%d)", cell.pack(), cell.key(), cell.w(), cell.h(), cell.d()); if (notify != null) { J.runEntity(notify, () -> notify.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.OBJECT_STUDIO_SAVE_SERVICE_OBJECT_STUDIO_SAVED, MessageArgument.untrusted("pack", String.valueOf(cell.pack())), MessageArgument.untrusted("key", String.valueOf(cell.key()))))); diff --git a/core/src/main/java/art/arcane/iris/core/service/PreservationSVC.java b/core/src/main/java/art/arcane/iris/core/service/PreservationSVC.java index 52ec73137..a830e7a08 100644 --- a/core/src/main/java/art/arcane/iris/core/service/PreservationSVC.java +++ b/core/src/main/java/art/arcane/iris/core/service/PreservationSVC.java @@ -63,7 +63,7 @@ public class PreservationSVC implements IrisService, PreservationRegistry { p += i.getUsage(); } - IrisLogging.info("Cached " + Form.f(s) + " / " + Form.f(m) + " (" + Form.pc(p / mf) + ") from " + caches.size() + " Caches"); + IrisLogging.debug("Cached " + Form.f(s) + " / " + Form.f(m) + " (" + Form.pc(p / mf) + ") from " + caches.size() + " Caches"); } public void dereference() { @@ -104,7 +104,7 @@ public class PreservationSVC implements IrisService, PreservationRegistry { if (i.isAlive()) { try { i.interrupt(); - IrisLogging.info("Shutdown Thread " + i.getName()); + IrisLogging.debug("Shutdown Thread " + i.getName()); } catch (Throwable e) { IrisLogging.reportError(e); } @@ -114,7 +114,7 @@ public class PreservationSVC implements IrisService, PreservationRegistry { for (ExecutorService i : services) { try { i.shutdownNow(); - IrisLogging.info("Shutdown Executor Service " + i); + IrisLogging.debug("Shutdown Executor Service " + i); } catch (Throwable e) { IrisLogging.reportError(e); } diff --git a/core/src/main/java/art/arcane/iris/core/service/StudioSVC.java b/core/src/main/java/art/arcane/iris/core/service/StudioSVC.java index e07dd7bec..49996e015 100644 --- a/core/src/main/java/art/arcane/iris/core/service/StudioSVC.java +++ b/core/src/main/java/art/arcane/iris/core/service/StudioSVC.java @@ -213,7 +213,6 @@ public class StudioSVC implements IrisService { IrisData previousData = IrisData.getLoaded(target.toFile()).orElse(null); IrisData createdData = null; boolean refreshedPreviousData = false; - sender.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.STUDIO_S_V_C_INSTALLING_PACKAGE, MessageArgument.untrusted("name", String.valueOf(source.getFileName())), MessageArgument.untrusted("loadKey", String.valueOf(dimensionKey)))); try { if (parent == null) { throw new IOException("World pack target has no parent: " + target); @@ -556,10 +555,23 @@ public class StudioSVC implements IrisService { } public void open(VolmitSender sender, long seed, String dimm, Consumer onDone) throws IrisException { + open(sender, seed, dimm, StudioOpenCoordinator.StudioOpenKind.STANDARD, onDone); + } + + public void open( + VolmitSender sender, + long seed, + String dimm, + StudioOpenCoordinator.StudioOpenKind openKind, + Consumer onDone + ) throws IrisException { if (reportPackAdmissionFailure(sender, dimm) != null) { return; } - studioTransitions.submit(() -> replaceActiveProject(sender, seed, dimm, onDone)) + StudioOpenCoordinator.StudioOpenKind requiredOpenKind = Objects.requireNonNull( + openKind, + "Studio open kind"); + studioTransitions.submit(() -> replaceActiveProject(sender, seed, dimm, requiredOpenKind, onDone)) .whenComplete((ignored, throwable) -> { if (throwable == null) { return; @@ -650,6 +662,7 @@ public class StudioSVC implements IrisService { VolmitSender sender, long seed, String dimension, + StudioOpenCoordinator.StudioOpenKind openKind, Consumer onDone ) { return closeActiveProjectForReplacement(sender).handle((closeResult, closeThrowable) -> { @@ -678,7 +691,7 @@ public class StudioSVC implements IrisService { } return true; }).thenCompose(closed -> closed - ? beginStudioOpen(sender, seed, dimension, onDone) + ? beginStudioOpen(sender, seed, dimension, openKind, onDone) : CompletableFuture.completedFuture(null)); } @@ -686,6 +699,7 @@ public class StudioSVC implements IrisService { VolmitSender sender, long seed, String dimension, + StudioOpenCoordinator.StudioOpenKind openKind, Consumer onDone ) { IrisProject project = new IrisProject(new File(getWorkspaceFolder(), dimension)); @@ -695,7 +709,7 @@ public class StudioSVC implements IrisService { opening = project.open( sender, seed, - StudioOpenCoordinator.StudioOpenKind.STANDARD, + openKind, onDone); } catch (IrisException e) { if (activeProject == project) { diff --git a/core/src/main/java/art/arcane/iris/core/structure/StructureCaptureImporter.java b/core/src/main/java/art/arcane/iris/core/structure/StructureCaptureImporter.java index c706bc814..70b3fce95 100644 --- a/core/src/main/java/art/arcane/iris/core/structure/StructureCaptureImporter.java +++ b/core/src/main/java/art/arcane/iris/core/structure/StructureCaptureImporter.java @@ -124,7 +124,6 @@ public final class StructureCaptureImporter { failed++; sender.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.STRUCTURE_CAPTURE_IMPORTER_FAIL_2, MessageArgument.untrusted("key", String.valueOf(key)), MessageArgument.untrusted("error", String.valueOf(e.getMessage())))); IrisLogging.reportError(e); - e.printStackTrace(); } int processed = imported + skipped + failed; diff --git a/core/src/main/java/art/arcane/iris/core/structure/StructureImporter.java b/core/src/main/java/art/arcane/iris/core/structure/StructureImporter.java index 613577974..f4bbc4189 100644 --- a/core/src/main/java/art/arcane/iris/core/structure/StructureImporter.java +++ b/core/src/main/java/art/arcane/iris/core/structure/StructureImporter.java @@ -162,7 +162,6 @@ public final class StructureImporter { structure = Bukkit.getStructureManager().loadStructure(key); } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); return new Result(false, "Failed to load structure " + key + ": " + e.getMessage(), 0, List.of(), true); } if (structure == null || structure.getPalettes().isEmpty()) { @@ -174,7 +173,6 @@ public final class StructureImporter { captured = captureStructure(structure); } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); return new Result(false, "Failed to capture structure " + key + ": " + e.getMessage(), 0, List.of(), true); } @@ -221,7 +219,6 @@ public final class StructureImporter { return new Result(false, "Failed writing import for '" + name + "': " + e.getMessage(), count, losses); } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); return new Result(false, "Failed writing import for '" + name + "': " + e.getMessage(), count, losses, true); } @@ -353,7 +350,6 @@ public final class StructureImporter { private static void reportWriteFailure(StructureWriteResult result) { result.failure().ifPresent((Throwable failure) -> { IrisLogging.reportError(failure); - failure.printStackTrace(); }); } @@ -395,7 +391,6 @@ public final class StructureImporter { } catch (Throwable e) { IrisLogging.reportError(e); if (VillageImporter.shouldPrintFullTrace(e)) { - e.printStackTrace(); } return new FinalStateResult(null, false); } @@ -432,7 +427,6 @@ public final class StructureImporter { return LegacyTileData.fromBukkit(block); } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); return null; } } @@ -465,7 +459,6 @@ public final class StructureImporter { maxSpan = Math.max(maxSpan, readObjectSpan(iob)); } catch (IOException e) { IrisLogging.reportError(e); - e.printStackTrace(); return new Result(false, "Cannot read imported object '" + rel + "': " + e.getMessage(), 0, List.of(), true); } @@ -508,7 +501,6 @@ public final class StructureImporter { + " template variants" + writeResultNote(writeResult), pieceNames.size(), List.of()); } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); return new Result(false, "Failed writing group structure '" + groupName + "': " + e.getMessage(), 0, List.of(), true); } @@ -598,7 +590,6 @@ public final class StructureImporter { return new Result(false, "Failed writing capture for '" + name + "': " + e.getMessage(), 0, List.of()); } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); return new Result(false, "Failed writing capture for '" + name + "': " + e.getMessage(), 0, List.of(), true); } diff --git a/core/src/main/java/art/arcane/iris/core/structure/VillageImporter.java b/core/src/main/java/art/arcane/iris/core/structure/VillageImporter.java index 928fe60d1..961da6b05 100644 --- a/core/src/main/java/art/arcane/iris/core/structure/VillageImporter.java +++ b/core/src/main/java/art/arcane/iris/core/structure/VillageImporter.java @@ -1204,7 +1204,6 @@ public final class VillageImporter { private static void reportFailure(Throwable failure) { IrisLogging.reportError(failure); if (shouldPrintFullTrace(failure)) { - failure.printStackTrace(); } } diff --git a/core/src/main/java/art/arcane/iris/core/tools/IrisConverter.java b/core/src/main/java/art/arcane/iris/core/tools/IrisConverter.java index 577762ee8..7c622406d 100644 --- a/core/src/main/java/art/arcane/iris/core/tools/IrisConverter.java +++ b/core/src/main/java/art/arcane/iris/core/tools/IrisConverter.java @@ -154,7 +154,7 @@ public class IrisConverter { } catch (Exception e) { sender.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.IRIS_CONVERTER_FAILED_CONVERT, MessageArgument.untrusted("name", String.valueOf(schem.getName())))); - e.printStackTrace(); + IrisLogging.reportError("Failed to convert schematic " + schem.getName() + ".", e); } } stopwatch.end(); @@ -184,4 +184,3 @@ public class IrisConverter { } } } - diff --git a/core/src/main/java/art/arcane/iris/core/tools/IrisPackBenchmarking.java b/core/src/main/java/art/arcane/iris/core/tools/IrisPackBenchmarking.java index ffda6e75d..e8eb34d87 100644 --- a/core/src/main/java/art/arcane/iris/core/tools/IrisPackBenchmarking.java +++ b/core/src/main/java/art/arcane/iris/core/tools/IrisPackBenchmarking.java @@ -103,10 +103,9 @@ public class IrisPackBenchmarking { writer.write(" - Highest CPS: " + findHighest(cps) + "\n"); writer.write(" - Lowest CPS: " + findLowest(cps) + "\n"); writer.write("-----------------\n"); - IrisLogging.info("Finished generating a report!"); + IrisLogging.debug("Finished generating an Iris pack benchmark report."); } catch (IOException e) { - IrisLogging.error("An error occurred writing to the file."); - e.printStackTrace(); + IrisLogging.reportError("Failed to write the Iris pack benchmark report.", e); } J.sfut(() -> { @@ -135,8 +134,7 @@ public class IrisPackBenchmarking { stopwatch.end(); } catch (Exception e) { - IrisLogging.error("Something has gone wrong!"); - e.printStackTrace(); + IrisLogging.reportError("Iris pack benchmarking failed.", e); } } diff --git a/core/src/main/java/art/arcane/iris/core/tools/WorldCreationProgressReporter.java b/core/src/main/java/art/arcane/iris/core/tools/WorldCreationProgressReporter.java index 47e9f2966..ebe4ffe09 100644 --- a/core/src/main/java/art/arcane/iris/core/tools/WorldCreationProgressReporter.java +++ b/core/src/main/java/art/arcane/iris/core/tools/WorldCreationProgressReporter.java @@ -18,22 +18,15 @@ package art.arcane.iris.core.tools; -import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.localization.IrisLanguage; import art.arcane.iris.core.localization.RuntimeProgressMessages; -import art.arcane.iris.spi.IrisLogging; import art.arcane.iris.util.common.format.C; import art.arcane.iris.util.common.plugin.VolmitSender; import art.arcane.iris.util.common.scheduling.J; import art.arcane.volmlib.util.format.Form; import art.arcane.volmlib.util.localization.MessageArgument; import art.arcane.volmlib.util.localization.TextKey; -import org.bukkit.Bukkit; -import org.bukkit.boss.BarColor; -import org.bukkit.boss.BarStyle; -import org.bukkit.boss.BossBar; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -56,8 +49,6 @@ final class WorldCreationProgressReporter { private final AtomicBoolean playerRenderQueued; private final AtomicInteger taskId; private final AtomicLong nextConsoleUpdate; - private volatile BossBar bossBar; - private volatile boolean hudDisabled; private WorldCreationProgressReporter(VolmitSender sender, String worldName) { this.sender = sender; @@ -72,45 +63,14 @@ final class WorldCreationProgressReporter { this.playerRenderQueued = new AtomicBoolean(false); this.taskId = new AtomicInteger(-1); this.nextConsoleUpdate = new AtomicLong(0L); - - this.bossBar = null; - this.hudDisabled = false; } static WorldCreationProgressReporter start(VolmitSender sender, String worldName) { WorldCreationProgressReporter reporter = new WorldCreationProgressReporter(sender, worldName); - if (sender.isPlayer() && sender.player() != null - && IrisSettings.get().getGeneral().isProgressBossBar()) { - try { - J.sfut(reporter::initializePlayerHud).get(5L, TimeUnit.SECONDS); - } catch (Throwable failure) { - reporter.hudDisabled = true; - J.runGlobal(reporter::releaseHud); - IrisLogging.reportError("Failed to initialize world creation progress HUD for \"" - + worldName + "\".", failure); - } - } reporter.taskId.set(J.ar(reporter::tick, 3)); return reporter; } - private void initializePlayerHud() { - if (hudDisabled) { - return; - } - bossBar = Bukkit.createBossBar( - IrisLanguage.text( - RuntimeProgressMessages.WORLD_CREATE_BOSSBAR_WORKING, - MessageArgument.untrusted("world", worldName) - ), - BarColor.BLUE, - BarStyle.SEGMENTED_20 - ); - bossBar.setProgress(0.01D); - bossBar.addPlayer(sender.player()); - bossBar.setVisible(true); - } - void update(double progress, String stage) { update(progress, stage, ""); } @@ -151,29 +111,19 @@ final class WorldCreationProgressReporter { if (!terminalRendered.compareAndSet(false, true)) { return; } - renderTerminal(currentProgress, currentStage, currentDetail, percent, elapsed); + renderTerminal(currentProgress, currentStage, currentDetail, elapsed); return; } if (sender.isPlayer() && sender.player() != null) { - if (hasPlayerHud()) { - schedulePlayerRender(() -> renderPlayerProgress( - currentProgress, - currentStage, - currentDetail, - percent, - elapsed - )); - } else { - schedulePlayerRender(() -> sender.sendAction(IrisLanguage.text( - RuntimeProgressMessages.WORLD_CREATE_LIFECYCLE_ACTION, - MessageArgument.trusted("bar", buildPlayerBar(currentProgress)), - MessageArgument.trusted("percent", percent), - MessageArgument.trusted("stage", currentStage), - MessageArgument.trusted("detail", currentDetail), - MessageArgument.trusted("elapsed", Form.duration(elapsed, 0)) - ))); - } + schedulePlayerRender(() -> sender.sendAction(IrisLanguage.text( + RuntimeProgressMessages.WORLD_CREATE_LIFECYCLE_ACTION, + MessageArgument.trusted("bar", buildPlayerBar(currentProgress)), + MessageArgument.trusted("percent", percent), + MessageArgument.trusted("stage", currentStage), + MessageArgument.trusted("detail", currentDetail), + MessageArgument.trusted("elapsed", Form.duration(elapsed, 0)) + ))); return; } @@ -192,59 +142,20 @@ final class WorldCreationProgressReporter { } } - private void renderPlayerProgress( - double currentProgress, - String currentStage, - String currentDetail, - int percent, - long elapsed - ) { - bossBar.setProgress(currentProgress); - bossBar.setTitle(IrisLanguage.text( - RuntimeProgressMessages.WORLD_CREATE_BOSSBAR_PROGRESS, - MessageArgument.untrusted("world", worldName), - MessageArgument.trusted("percent", percent), - MessageArgument.trusted("stage", currentStage) - )); - - sender.sendAction(IrisLanguage.text( - RuntimeProgressMessages.WORLD_CREATE_LIFECYCLE_ACTION, - MessageArgument.trusted("bar", buildPlayerBar(currentProgress)), - MessageArgument.trusted("percent", percent), - MessageArgument.trusted("stage", currentStage), - MessageArgument.trusted("detail", currentDetail), - MessageArgument.trusted("elapsed", Form.duration(elapsed, 0)) - )); - } - private void renderTerminal( double currentProgress, String currentStage, String currentDetail, - int percent, long elapsed ) { if (sender.isPlayer() && sender.player() != null) { - if (hasPlayerHud()) { - schedulePlayerTerminalRender(() -> renderPlayerTerminal( - currentProgress, - currentStage, - currentDetail, - percent, - elapsed - )); - } else { - schedulePlayerTerminalRender(() -> sender.sendAction(IrisLanguage.text( - failed.get() - ? RuntimeProgressMessages.WORLD_CREATE_LIFECYCLE_ACTION_FAILED - : RuntimeProgressMessages.WORLD_CREATE_LIFECYCLE_ACTION_READY, - MessageArgument.trusted("bar", buildPlayerBar(currentProgress)), - MessageArgument.trusted("percent", percent), - MessageArgument.trusted("stage", currentStage), - MessageArgument.trusted("detail", currentDetail), - MessageArgument.trusted("elapsed", Form.duration(elapsed, 1)) - ))); - } + schedulePlayerTerminalRender(() -> sender.sendAction(terminalPlayerMessage( + failed.get(), + currentProgress, + currentStage, + currentDetail, + elapsed + ))); return; } @@ -257,48 +168,6 @@ final class WorldCreationProgressReporter { )); } - private void renderPlayerTerminal( - double currentProgress, - String currentStage, - String currentDetail, - int percent, - long elapsed - ) { - bossBar.setProgress(currentProgress); - bossBar.setColor(failed.get() ? BarColor.RED : BarColor.GREEN); - bossBar.setTitle(IrisLanguage.text( - failed.get() - ? RuntimeProgressMessages.WORLD_CREATE_BOSSBAR_FAILED - : RuntimeProgressMessages.WORLD_CREATE_BOSSBAR_READY, - MessageArgument.untrusted("world", worldName), - MessageArgument.trusted("percent", percent) - )); - - sender.sendAction(IrisLanguage.text( - failed.get() - ? RuntimeProgressMessages.WORLD_CREATE_LIFECYCLE_ACTION_FAILED - : RuntimeProgressMessages.WORLD_CREATE_LIFECYCLE_ACTION_READY, - MessageArgument.trusted("bar", buildPlayerBar(currentProgress)), - MessageArgument.trusted("percent", percent), - MessageArgument.trusted("stage", currentStage), - MessageArgument.trusted("detail", currentDetail), - MessageArgument.trusted("elapsed", Form.duration(elapsed, 1)) - )); - - Runnable cleanup = () -> { - bossBar.removeAll(); - bossBar.setVisible(false); - }; - J.runEntity(sender.player(), cleanup, 60, cleanup); - } - - private boolean hasPlayerHud() { - return !hudDisabled - && sender.isPlayer() - && sender.player() != null - && bossBar != null; - } - private void schedulePlayerRender(Runnable render) { if (!playerRenderQueued.compareAndSet(false, true)) { return; @@ -314,24 +183,10 @@ final class WorldCreationProgressReporter { return; } playerRenderQueued.set(false); - hudDisabled = true; - J.runGlobal(this::releaseHud); } private void schedulePlayerTerminalRender(Runnable render) { - if (J.runEntity(sender.player(), render)) { - return; - } - hudDisabled = true; - J.runGlobal(this::releaseHud); - } - - private void releaseHud() { - BossBar activeBossBar = bossBar; - if (activeBossBar != null) { - activeBossBar.removeAll(); - activeBossBar.setVisible(false); - } + J.runEntity(sender.player(), render); } private void cancel() { @@ -359,6 +214,29 @@ final class WorldCreationProgressReporter { return buildBar(progress, CONSOLE_BAR_WIDTH, false); } + static String terminalPlayerMessage( + boolean failed, + double progress, + String stage, + String detail, + long elapsed + ) { + if (failed) { + return IrisLanguage.text( + RuntimeProgressMessages.WORLD_CREATE_LIFECYCLE_ACTION_FAILED, + MessageArgument.trusted("bar", buildPlayerBar(progress)), + MessageArgument.trusted("stage", stage), + MessageArgument.trusted("detail", detail), + MessageArgument.trusted("elapsed", Form.duration(elapsed, 1)) + ); + } + return IrisLanguage.text( + RuntimeProgressMessages.WORLD_CREATE_LIFECYCLE_ACTION_READY, + MessageArgument.trusted("bar", buildPlayerBar(progress)), + MessageArgument.trusted("elapsed", Form.duration(elapsed, 1)) + ); + } + private static String buildBar(double progress, int width, boolean colored) { int filled = (int) Math.round(clampProgress(progress) * width); StringBuilder bar = new StringBuilder(colored ? width * 3 + 4 : width + 2); diff --git a/core/src/main/java/art/arcane/iris/engine/EngineDataStore.java b/core/src/main/java/art/arcane/iris/engine/EngineDataStore.java index 84d54756b..ef881fc54 100644 --- a/core/src/main/java/art/arcane/iris/engine/EngineDataStore.java +++ b/core/src/main/java/art/arcane/iris/engine/EngineDataStore.java @@ -75,7 +75,6 @@ final class EngineDataStore { } } catch (IOException | JsonParseException e) { IrisLogging.reportError(e); - e.printStackTrace(); throw new IllegalStateException("Failed to read Iris engine data without modifying it: " + f.getAbsolutePath(), e); } } @@ -94,7 +93,6 @@ final class EngineDataStore { engineDataEstablished = true; } catch (IOException e) { IrisLogging.reportError(e); - e.printStackTrace(); throw new IllegalStateException("Failed to create Iris engine data: " + f.getAbsolutePath(), e); } } @@ -141,7 +139,6 @@ final class EngineDataStore { } catch (IOException e) { IrisLogging.error("Failed to save Engine Data"); IrisLogging.reportError(e); - e.printStackTrace(); throw new IllegalStateException("Failed to save Iris engine data: " + f.getAbsolutePath(), e); } } diff --git a/core/src/main/java/art/arcane/iris/engine/EngineHotloader.java b/core/src/main/java/art/arcane/iris/engine/EngineHotloader.java index 7357f1c69..71850a6e9 100644 --- a/core/src/main/java/art/arcane/iris/engine/EngineHotloader.java +++ b/core/src/main/java/art/arcane/iris/engine/EngineHotloader.java @@ -19,8 +19,6 @@ package art.arcane.iris.engine; import art.arcane.iris.core.loader.IrisData; -import art.arcane.iris.core.localization.ClientUiMessages; -import art.arcane.iris.core.localization.IrisLanguage; import art.arcane.iris.core.protocol.IrisProtocolServer; import art.arcane.iris.engine.EngineRuntime.BiomeMaxes; import art.arcane.iris.engine.EngineRuntimeBuilder.RuntimeAssembly; @@ -31,9 +29,7 @@ import art.arcane.iris.engine.framework.StructureReachability; import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.spi.IrisLogging; import art.arcane.iris.spi.IrisServices; -import art.arcane.iris.spi.protocol.IrisMessage; import art.arcane.iris.util.project.context.IrisContext; -import art.arcane.volmlib.util.localization.MessageArgument; import static art.arcane.iris.engine.EngineShutdownSequence.runCleanup; @@ -132,7 +128,6 @@ final class EngineHotloader { if (previousDataFailure != null) { IrisLogging.error("Failed to completely release the previous Iris data runtime."); IrisLogging.reportError(previousDataFailure); - previousDataFailure.printStackTrace(); } engine.getPrefetchSaveStarted().set(false); engine.getEngineData().getStatistics().hotloaded(); @@ -190,13 +185,8 @@ final class EngineHotloader { if (protocolServer == null) { return; } - IrisDimension dimension = engine.getDimension(); - String packKey = dimension == null ? "" : dimension.getLoadKey(); + String packKey = engine.getData().getDataFolder().getName(); protocolServer.broadcastStudioHotload(packKey, 0, failed, message); - protocolServer.broadcastToast( - failed ? IrisMessage.Toast.KIND_ERROR : IrisMessage.Toast.KIND_SUCCESS, - IrisLanguage.plain(ClientUiMessages.TOAST_STUDIO_HOTLOAD), - failed ? IrisLanguage.plain(ClientUiMessages.TOAST_PACK_FAILED, MessageArgument.untrusted("pack", packKey)) : packKey); } catch (Throwable broadcastFailure) { IrisLogging.error("Iris studio hotload broadcast failed: " + broadcastFailure.getClass().getSimpleName() + ": " + broadcastFailure.getMessage()); diff --git a/core/src/main/java/art/arcane/iris/engine/EngineRuntimeBuilder.java b/core/src/main/java/art/arcane/iris/engine/EngineRuntimeBuilder.java index 872382230..baa90d9b0 100644 --- a/core/src/main/java/art/arcane/iris/engine/EngineRuntimeBuilder.java +++ b/core/src/main/java/art/arcane/iris/engine/EngineRuntimeBuilder.java @@ -172,7 +172,6 @@ final class EngineRuntimeBuilder { } catch (Throwable e) { engineRuntime.hash32().completeExceptionally(e); IrisLogging.reportError(e); - e.printStackTrace(); } try { if (!engine.backgroundTasks.scheduleTrackedTask(() -> engine.getPlatformHooks().refreshDatapackWorkspace(engine))) { @@ -180,7 +179,6 @@ final class EngineRuntimeBuilder { } } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); } } @@ -219,7 +217,6 @@ final class EngineRuntimeBuilder { } catch (Throwable e) { configuredFailure = e; IrisLogging.reportError(e); - e.printStackTrace(); if (engine.getModeFallbackLogged().compareAndSet(false, true)) { IrisLogging.warn("Failed to initialize configured dimension mode for " + engine.getDimension().getLoadKey() + ", falling back to OVERWORLD mode."); } diff --git a/core/src/main/java/art/arcane/iris/engine/EngineShutdownSequence.java b/core/src/main/java/art/arcane/iris/engine/EngineShutdownSequence.java index 64393913f..27b6c417a 100644 --- a/core/src/main/java/art/arcane/iris/engine/EngineShutdownSequence.java +++ b/core/src/main/java/art/arcane/iris/engine/EngineShutdownSequence.java @@ -156,7 +156,6 @@ final class EngineShutdownSequence { private void reportIncompleteClose(Throwable failure) { IrisLogging.error("Iris engine shutdown remains incomplete after cleanup failures for " + engine.getWorld().name() + "."); IrisLogging.reportError(failure); - failure.printStackTrace(); throw new IllegalStateException("Iris engine shutdown remains incomplete after cleanup failures.", failure); } diff --git a/core/src/main/java/art/arcane/iris/engine/EngineTickRegistry.java b/core/src/main/java/art/arcane/iris/engine/EngineTickRegistry.java index 0c7f25d50..3773e5141 100644 --- a/core/src/main/java/art/arcane/iris/engine/EngineTickRegistry.java +++ b/core/src/main/java/art/arcane/iris/engine/EngineTickRegistry.java @@ -68,7 +68,6 @@ final class EngineTickRegistry { engine.tickRandomPlayer(); } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); } } } diff --git a/core/src/main/java/art/arcane/iris/engine/IrisEngine.java b/core/src/main/java/art/arcane/iris/engine/IrisEngine.java index 5646c1fc3..f8a15c44a 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisEngine.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisEngine.java @@ -224,7 +224,6 @@ public class IrisEngine implements Engine { StructureIndexService.writeOnce(getData()); } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); } IrisLogging.notice("Engine init: " + target.getWorld().name() + "/" + target.getDimension().getLoadKey() + " seed=" + getSeedManager().getSeed()); _t0 = M.ms(); @@ -653,7 +652,6 @@ public class IrisEngine implements Engine { failing = true; IrisLogging.error(error); IrisLogging.reportError(e); - e.printStackTrace(); } @Override diff --git a/core/src/main/java/art/arcane/iris/engine/IrisEngineEffects.java b/core/src/main/java/art/arcane/iris/engine/IrisEngineEffects.java index 7592bf3b8..bec2049b3 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisEngineEffects.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisEngineEffects.java @@ -27,7 +27,6 @@ import art.arcane.iris.util.common.scheduling.J; import art.arcane.volmlib.util.math.M; import org.bukkit.entity.Player; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -41,11 +40,13 @@ import java.util.concurrent.atomic.AtomicLong; public class IrisEngineEffects extends EngineAssignedComponent implements EngineEffects { private static final long EFFECT_BUDGET_NANOS = 1_500_000L; private static final long EMPTY_PLAYER_REFRESH_NANOS = 1_000_000_000L; + private static final EnginePlayer[] NO_PLAYERS = new EnginePlayer[0]; private final ConcurrentHashMap players; private final Semaphore limit; private final AtomicBoolean playerMapUpdateQueued; private final AtomicLong nextEmptyRefresh; + private volatile EnginePlayer[] playerSnapshot; public IrisEngineEffects(Engine engine) { super(engine, "FX"); @@ -53,6 +54,7 @@ public class IrisEngineEffects extends EngineAssignedComponent implements Engine limit = new Semaphore(1); playerMapUpdateQueued = new AtomicBoolean(false); nextEmptyRefresh = new AtomicLong(0L); + playerSnapshot = NO_PLAYERS; } @Override @@ -89,6 +91,7 @@ public class IrisEngineEffects extends EngineAssignedComponent implements Engine } players.keySet().removeIf((UUID playerId) -> !activeIds.contains(playerId)); + playerSnapshot = players.values().toArray(EnginePlayer[]::new); } @Override @@ -97,7 +100,8 @@ public class IrisEngineEffects extends EngineAssignedComponent implements Engine return; } try { - if (players.isEmpty()) { + EnginePlayer[] snapshot = playerSnapshot; + if (snapshot.length == 0) { long now = System.nanoTime(); long next = nextEmptyRefresh.get(); if (now >= next && nextEmptyRefresh.compareAndSet(next, now + EMPTY_PLAYER_REFRESH_NANOS)) { @@ -110,15 +114,14 @@ public class IrisEngineEffects extends EngineAssignedComponent implements Engine return; } - List snapshot = new ArrayList<>(players.values()); - if (snapshot.isEmpty()) { - return; - } - long started = System.nanoTime(); - int remaining = snapshot.size(); + int index = ThreadLocalRandom.current().nextInt(snapshot.length); + int remaining = snapshot.length; while (remaining-- > 0 && System.nanoTime() - started < EFFECT_BUDGET_NANOS) { - snapshot.get(ThreadLocalRandom.current().nextInt(snapshot.size())).tick(); + snapshot[index].tick(); + if (++index == snapshot.length) { + index = 0; + } } } finally { limit.release(); diff --git a/core/src/main/java/art/arcane/iris/engine/IrisEngineMantle.java b/core/src/main/java/art/arcane/iris/engine/IrisEngineMantle.java index 831ffd8e9..ad3197840 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisEngineMantle.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisEngineMantle.java @@ -325,7 +325,6 @@ public class IrisEngineMantle implements EngineMantle { panic.add("read.byte.range", start + " " + end); panic.add("read.byte.current", din.count() + ""); IrisLogging.reportError(error); - error.printStackTrace(); panic.panic(); TectonicPlate.addError(); } @@ -350,7 +349,6 @@ public class IrisEngineMantle implements EngineMantle { panic.add("read.byte.range", start + " " + end); panic.add("read.byte.current", din.count() + ""); IrisLogging.reportError(error); - error.printStackTrace(); panic.panic(); } diff --git a/core/src/main/java/art/arcane/iris/engine/IrisWorldManager.java b/core/src/main/java/art/arcane/iris/engine/IrisWorldManager.java index c5d9d752d..2fc6d0c60 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisWorldManager.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisWorldManager.java @@ -43,10 +43,10 @@ import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.player.PlayerTeleportEvent; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -63,7 +63,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { private final ChronoLatch chunkUpdater; private final ChronoLatch chunkDiscovery; private final KMap> cleanup = new KMap<>(); - private final ScheduledExecutorService cleanupService; + private final ScheduledThreadPoolExecutor cleanupService; @Getter(AccessLevel.NONE) @Setter(AccessLevel.NONE) final WorldEntitySpawner entitySpawner = new WorldEntitySpawner(this); @@ -80,7 +80,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { @Setter(AccessLevel.NONE) final WorldTeleportWarmup teleportWarmup = new WorldTeleportWarmup(this); private boolean looperStopped; - private boolean cleanupServiceStopped; + private volatile boolean cleanupServiceStopped; volatile int entityCount = 0; volatile boolean entityCountValid = false; volatile boolean playersPresent = false; @@ -105,11 +105,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { cln = new ChronoLatch(60000); cl = new ChronoLatch(3000); clw = new ChronoLatch(1000, true); - cleanupService = Executors.newSingleThreadScheduledExecutor(runnable -> { - Thread thread = new Thread(runnable, "Iris Mantle Cleanup " + getTarget().getWorld().name()); - thread.setPriority(Thread.MIN_PRIORITY); - return thread; - }); + cleanupService = createCleanupExecutor(getTarget().getWorld().name()); looper = new Looper() { @Override protected long loop() { @@ -126,6 +122,17 @@ public class IrisWorldManager extends EngineAssignedWorldManager { looper.setName("Iris World Manager " + getTarget().getWorld().name()); } + static ScheduledThreadPoolExecutor createCleanupExecutor(String worldName) { + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, runnable -> { + Thread thread = new Thread(runnable, "Iris Mantle Cleanup " + worldName); + thread.setPriority(Thread.MIN_PRIORITY); + return thread; + }); + executor.setRemoveOnCancelPolicy(true); + executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + return executor; + } + private long runLoop() { if (getEngine().isClosed()) { looper.interrupt(); @@ -288,6 +295,10 @@ public class IrisWorldManager extends EngineAssignedWorldManager { try { cleanupServiceStopped = true; if (cleanupService != null) { + for (Future future : cleanup.values()) { + future.cancel(false); + } + cleanup.clear(); cleanupService.shutdownNow(); awaitQuietly(cleanupService); } diff --git a/core/src/main/java/art/arcane/iris/engine/WorldChunkMaintenance.java b/core/src/main/java/art/arcane/iris/engine/WorldChunkMaintenance.java index fb57aa84d..f352fd593 100644 --- a/core/src/main/java/art/arcane/iris/engine/WorldChunkMaintenance.java +++ b/core/src/main/java/art/arcane/iris/engine/WorldChunkMaintenance.java @@ -41,11 +41,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Set; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -64,6 +60,7 @@ final class WorldChunkMaintenance { private final Set chunkUpdateQueue = ConcurrentHashMap.newKeySet(); private final AtomicBoolean chunkUpdateScanScheduled = new AtomicBoolean(); private final AtomicBoolean chunkDiscoveryScanScheduled = new AtomicBoolean(); + private volatile Position2[] loadedChunkPositions = new Position2[0]; private int forcedChunkUpdateCursor = 0; WorldChunkMaintenance(IrisWorldManager manager) { @@ -182,8 +179,14 @@ final class WorldChunkMaintenance { } List players = new ArrayList<>(world.getPlayers()); + Chunk[] loadedChunks = world.getLoadedChunks(); + Position2[] currentLoadedChunkPositions = new Position2[loadedChunks.length]; + for (int i = 0; i < loadedChunks.length; i++) { + currentLoadedChunkPositions[i] = new Position2(loadedChunks[i].getX(), loadedChunks[i].getZ()); + } + loadedChunkPositions = currentLoadedChunkPositions; manager.playersPresent = !players.isEmpty(); - manager.loadedChunkCount = world.getLoadedChunks().length; + manager.loadedChunkCount = currentLoadedChunkPositions.length; for (Player player : players) { if (player == null) { continue; @@ -373,33 +376,6 @@ final class WorldChunkMaintenance { if (world == null) { return new Position2[0]; } - - CompletableFuture future = new CompletableFuture<>(); - boolean scheduled = J.runGlobal(() -> { - try { - Chunk[] chunks = world.getLoadedChunks(); - Position2[] positions = new Position2[chunks.length]; - for (int i = 0; i < chunks.length; i++) { - positions[i] = new Position2(chunks[i].getX(), chunks[i].getZ()); - } - manager.loadedChunkCount = positions.length; - future.complete(positions); - } catch (Throwable e) { - future.completeExceptionally(e); - } - }); - if (!scheduled) { - return new Position2[0]; - } - - try { - return future.get(2, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return new Position2[0]; - } catch (ExecutionException | TimeoutException e) { - IrisLogging.reportError(e); - return new Position2[0]; - } + return loadedChunkPositions; } } diff --git a/core/src/main/java/art/arcane/iris/engine/WorldEntitySpawner.java b/core/src/main/java/art/arcane/iris/engine/WorldEntitySpawner.java index 5a8d808c8..acb9f6b27 100644 --- a/core/src/main/java/art/arcane/iris/engine/WorldEntitySpawner.java +++ b/core/src/main/java/art/arcane/iris/engine/WorldEntitySpawner.java @@ -42,29 +42,23 @@ import org.bukkit.Chunk; import org.bukkit.World; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; -import org.bukkit.entity.Player; -import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Ambient and marker entity spawning for a Bukkit Iris world. Every count and spawn hops onto the - * thread that owns the data it reads (global for whole-world scans, entity for Folia candidate - * scans, region for the chunk being populated) and spawning is paused whenever a count could not - * be completed, so an incomplete saturation reading can never authorize a spawn. + * thread that owns the data it reads (global for synchronized whole-world snapshots, region for + * the chunk being populated) and spawning is paused whenever a count could not be completed, so + * an incomplete saturation reading can never authorize a spawn. */ final class WorldEntitySpawner { private final IrisWorldManager manager; @@ -101,26 +95,11 @@ final class WorldEntitySpawner { if (realWorld == null) { manager.entityCount = 0; manager.entityCountValid = false; - } else if (J.isFolia()) { - Integer count = getFoliaLivingEntityCount(realWorld); - if (count != null) { - manager.entityCount = count; - manager.entityCountValid = true; - resetEntityCountFailures(); - } else { - manager.entityCountValid = false; - } } else { CompletableFuture future = new CompletableFuture<>(); boolean scheduled = J.runGlobal(() -> { try { - int count = 0; - for (Entity entity : realWorld.getEntities()) { - if (entity instanceof LivingEntity && !entity.isDead()) { - count++; - } - } - future.complete(count); + future.complete(livingEntityCount(realWorld)); } catch (Throwable ex) { future.completeExceptionally(ex); } @@ -184,6 +163,10 @@ final class WorldEntitySpawner { return actuallySpawned.get() > 0; } + static int livingEntityCount(World world) { + return world.getLivingEntities().size(); + } + boolean isPregenActiveForThisWorld() { World world = BukkitWorldBinding.world(manager.getEngine().getWorld()); if (world == null) { @@ -202,118 +185,6 @@ final class WorldEntitySpawner { return job.targetsWorldIdentity(WorldIdentity.serialize(world)); } - private Integer getFoliaLivingEntityCount(World world) { - CompletableFuture> playerFuture = new CompletableFuture<>(); - boolean scheduled = J.runGlobal(() -> { - try { - playerFuture.complete(new ArrayList<>(world.getPlayers())); - } catch (Throwable e) { - playerFuture.completeExceptionally(e); - } - }); - if (!scheduled) { - reportEntityCountFailure("Unable to schedule the Folia player snapshot; pausing Iris entity spawning until a complete count is available.", null); - return null; - } - - List players; - try { - players = playerFuture.get(2, TimeUnit.SECONDS); - } catch (InterruptedException e) { - manager.entityCountValid = false; - Thread.currentThread().interrupt(); - return null; - } catch (TimeoutException e) { - reportEntityCountFailure("Timed out while reading the Folia player snapshot; pausing Iris entity spawning until a complete count is available.", null); - return null; - } catch (ExecutionException e) { - Throwable cause = e.getCause() == null ? e : e.getCause(); - reportEntityCountFailure("Failed to read the Folia player snapshot; pausing Iris entity spawning until a complete count is available.", cause); - return null; - } - - Map candidates = new ConcurrentHashMap<>(); - AtomicBoolean incomplete = new AtomicBoolean(); - AtomicReference failure = new AtomicReference<>(); - - CountDownLatch latch = new CountDownLatch(players.size()); - for (Player player : players) { - if (player == null) { - latch.countDown(); - continue; - } - - if (!J.runEntity(player, () -> { - try { - if (!player.isOnline() || !world.equals(player.getWorld())) { - return; - } - candidates.put(player.getUniqueId().toString(), player); - for (Entity nearby : player.getNearbyEntities(64, 64, 64)) { - if (nearby != null) { - candidates.put(nearby.getUniqueId().toString(), nearby); - } - } - } catch (Throwable e) { - incomplete.set(true); - failure.compareAndSet(null, e); - } finally { - latch.countDown(); - } - })) { - incomplete.set(true); - latch.countDown(); - } - } - - if (!awaitEntityTasks(latch, 2, TimeUnit.SECONDS) || incomplete.get()) { - if (!Thread.currentThread().isInterrupted()) { - reportEntityCountFailure("The Folia entity candidate scan was incomplete; pausing Iris entity spawning until a complete count is available.", failure.get()); - } - return null; - } - - AtomicInteger count = new AtomicInteger(); - incomplete.set(false); - failure.set(null); - CountDownLatch entityLatch = new CountDownLatch(candidates.size()); - for (Entity entity : candidates.values()) { - if (!J.runEntity(entity, () -> { - try { - if (entity instanceof LivingEntity && world.equals(entity.getWorld()) && !entity.isDead()) { - count.incrementAndGet(); - } - } catch (Throwable e) { - incomplete.set(true); - failure.compareAndSet(null, e); - } finally { - entityLatch.countDown(); - } - })) { - incomplete.set(true); - entityLatch.countDown(); - } - } - - if (!awaitEntityTasks(entityLatch, 2, TimeUnit.SECONDS) || incomplete.get()) { - if (!Thread.currentThread().isInterrupted()) { - reportEntityCountFailure("The Folia entity validation scan was incomplete; pausing Iris entity spawning until a complete count is available.", failure.get()); - } - return null; - } - - return count.get(); - } - - static boolean awaitEntityTasks(CountDownLatch latch, long timeout, TimeUnit unit) { - try { - return latch.await(timeout, unit); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return false; - } - } - private boolean spawnChunkSafely(World world, int chunkX, int chunkZ, boolean initial) { if (world == null) { return false; diff --git a/core/src/main/java/art/arcane/iris/engine/data/cache/AtomicCache.java b/core/src/main/java/art/arcane/iris/engine/data/cache/AtomicCache.java index a9e9ca322..61f3c367d 100644 --- a/core/src/main/java/art/arcane/iris/engine/data/cache/AtomicCache.java +++ b/core/src/main/java/art/arcane/iris/engine/data/cache/AtomicCache.java @@ -64,7 +64,7 @@ public class AtomicCache { try { return t.get(); } catch (Throwable e) { - e.printStackTrace(); + IrisLogging.reportError("Atomic cache supplier failed.", e); return null; } }); diff --git a/core/src/main/java/art/arcane/iris/engine/framework/structure/StructureGraphCatalog.java b/core/src/main/java/art/arcane/iris/engine/framework/structure/StructureGraphCatalog.java index 938bf048f..0ec1d3e07 100644 --- a/core/src/main/java/art/arcane/iris/engine/framework/structure/StructureGraphCatalog.java +++ b/core/src/main/java/art/arcane/iris/engine/framework/structure/StructureGraphCatalog.java @@ -91,7 +91,6 @@ public final class StructureGraphCatalog { } } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); return false; } } diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/MantleSliceRetention.java b/core/src/main/java/art/arcane/iris/engine/mantle/MantleSliceRetention.java index eec7c30dc..a1161477a 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/MantleSliceRetention.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/MantleSliceRetention.java @@ -46,7 +46,7 @@ public final class MantleSliceRetention { return; } if (retained.add(className)) { - IrisLogging.info("Mantle slice retained across chunk cleanup: " + className); + IrisLogging.debug("Mantle slice retained across chunk cleanup: " + className); } } diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/GoldenDebugObjectPlacer.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/GoldenDebugObjectPlacer.java index 34fa73ebe..0361961c8 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/GoldenDebugObjectPlacer.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/GoldenDebugObjectPlacer.java @@ -77,14 +77,14 @@ final class GoldenDebugObjectPlacer implements IObjectPlacer { @Override public int getHighest(int x, int z, IrisData data) { int result = delegate.getHighest(x, z, data); - IrisLogging.info("Goldendebug query: tag=" + tag + " getHighest(" + x + "," + z + ")=" + result); + IrisLogging.debug("Goldendebug query: tag=" + tag + " getHighest(" + x + "," + z + ")=" + result); return result; } @Override public int getHighest(int x, int z, IrisData data, boolean ignoreFluid) { int result = delegate.getHighest(x, z, data, ignoreFluid); - IrisLogging.info("Goldendebug query: tag=" + tag + " getHighest(" + x + "," + z + ",ignoreFluid=" + ignoreFluid + ")=" + result); + IrisLogging.debug("Goldendebug query: tag=" + tag + " getHighest(" + x + "," + z + ",ignoreFluid=" + ignoreFluid + ")=" + result); return result; } @@ -106,7 +106,7 @@ final class GoldenDebugObjectPlacer implements IObjectPlacer { @Override public boolean isCarved(int x, int y, int z) { boolean result = delegate.isCarved(x, y, z); - IrisLogging.info("Goldendebug query: tag=" + tag + " isCarved(" + x + "," + y + "," + z + ")=" + result); + IrisLogging.debug("Goldendebug query: tag=" + tag + " isCarved(" + x + "," + y + "," + z + ")=" + result); return result; } @@ -118,7 +118,7 @@ final class GoldenDebugObjectPlacer implements IObjectPlacer { @Override public boolean isSolid(int x, int y, int z) { boolean result = delegate.isSolid(x, y, z); - IrisLogging.info("Goldendebug query: tag=" + tag + " isSolid(" + x + "," + y + "," + z + ")=" + result); + IrisLogging.debug("Goldendebug query: tag=" + tag + " isSolid(" + x + "," + y + "," + z + ")=" + result); return result; } diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisStructureComponent.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisStructureComponent.java index aaa84ed52..b497ede03 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisStructureComponent.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/IrisStructureComponent.java @@ -95,7 +95,7 @@ public class IrisStructureComponent extends IrisMantleComponent { boolean trace = IrisSettings.get().getGeneral().isDebug(); if (trace) { - IrisLogging.info("[StructTrace] ORIGIN chunk=" + cx + "," + cz + " structures=" + placement.getStructures() + IrisLogging.debug("[StructTrace] ORIGIN chunk=" + cx + "," + cz + " structures=" + placement.getStructures() + " anchor=" + placement.resolvedAnchor() + " band=" + placement.getMinHeight() + ".." + placement.getMaxHeight()); } @@ -108,7 +108,7 @@ public class IrisStructureComponent extends IrisMantleComponent { RNG rng = resolved.rng(); int baseY = resolved.baseY(); if (trace) { - IrisLogging.info("[StructTrace] ASSEMBLED chunk=" + cx + "," + cz + " key=" + key + " baseY=" + baseY + " pieces=" + pieces.size()); + IrisLogging.debug("[StructTrace] ASSEMBLED chunk=" + cx + "," + cz + " key=" + key + " baseY=" + baseY + " pieces=" + pieces.size()); } if (!placement.isAnchoredUnderground()) { diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java index cfff8c348..58da33f51 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java @@ -132,7 +132,7 @@ public class MantleObjectComponent extends IrisMantleComponent { carvedBlocks++; } } - IrisLogging.info("Cave object diag: chunk=" + x + "," + z + IrisLogging.debug("Cave object diag: chunk=" + x + "," + z + " surfaceBiome=" + surfaceBiome.getLoadKey() + " caveBiome=" + caveBiome.getLoadKey() + " surfaceY=" + surfaceY @@ -143,7 +143,7 @@ public class MantleObjectComponent extends IrisMantleComponent { + " sameBiome=" + (caveBiome == surfaceBiome || java.util.Objects.equals(caveBiome.getLoadKey(), surfaceBiome.getLoadKey()))); } if (traceRegen) { - IrisLogging.info("Regen object layer start: chunk=" + x + "," + z + IrisLogging.debug("Regen object layer start: chunk=" + x + "," + z + " surfaceBiome=" + surfaceBiome.getLoadKey() + " caveBiome=" + caveBiome.getLoadKey() + " region=" + region.getLoadKey() @@ -160,7 +160,7 @@ public class MantleObjectComponent extends IrisMantleComponent { placeUpperObjects(writer, rng, x, z, xxx, zzz, surfaceY, upperCtx, dimension, complex, traceRegen); } if (traceRegen) { - IrisLogging.info("Regen object layer done: chunk=" + x + "," + z + IrisLogging.debug("Regen object layer done: chunk=" + x + "," + z + " biomeSurfacePlacersChecked=" + summary.biomeSurfacePlacersChecked() + " biomeSurfacePlacersTriggered=" + summary.biomeSurfacePlacersTriggered() + " biomeCavePlacersChecked=" + summary.biomeCavePlacersChecked() @@ -240,7 +240,7 @@ public class MantleObjectComponent extends IrisMantleComponent { biomeSurfaceChecked++; boolean chance = rng.chance(i.getChance() + rng.d(-0.005, 0.005)); if (traceRegen) { - IrisLogging.info("Regen object placer chance: chunk=" + x + "," + z + IrisLogging.debug("Regen object placer chance: chunk=" + x + "," + z + " scope=biome-surface" + " chanceResult=" + chance + " chanceBase=" + i.getChance() @@ -262,7 +262,6 @@ public class MantleObjectComponent extends IrisMantleComponent { IrisLogging.error("Failed to place objects in the following biome: " + surfaceBiome.getName()); IrisLogging.error("Object(s) " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ")."); IrisLogging.error("Are these objects missing?"); - e.printStackTrace(); } } } @@ -274,7 +273,7 @@ public class MantleObjectComponent extends IrisMantleComponent { biomeCaveChecked++; boolean chance = rng.chance(i.getChance()); if (traceRegen) { - IrisLogging.info("Regen object placer chance: chunk=" + x + "," + z + IrisLogging.debug("Regen object placer chance: chunk=" + x + "," + z + " scope=biome-cave" + " chanceResult=" + chance + " chanceBase=" + i.getChance() @@ -296,7 +295,6 @@ public class MantleObjectComponent extends IrisMantleComponent { IrisLogging.error("Failed to place cave objects in the following biome: " + caveBiome.getName()); IrisLogging.error("Object(s) " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ")."); IrisLogging.error("Are these objects missing?"); - e.printStackTrace(); } } } @@ -305,7 +303,7 @@ public class MantleObjectComponent extends IrisMantleComponent { regionSurfaceChecked++; boolean chance = rng.chance(i.getChance() + rng.d(-0.005, 0.005)); if (traceRegen) { - IrisLogging.info("Regen object placer chance: chunk=" + x + "," + z + IrisLogging.debug("Regen object placer chance: chunk=" + x + "," + z + " scope=region-surface" + " chanceResult=" + chance + " chanceBase=" + i.getChance() @@ -327,7 +325,6 @@ public class MantleObjectComponent extends IrisMantleComponent { IrisLogging.error("Failed to place objects in the following region: " + region.getName()); IrisLogging.error("Object(s) " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ")."); IrisLogging.error("Are these objects missing?"); - e.printStackTrace(); } } } @@ -339,7 +336,7 @@ public class MantleObjectComponent extends IrisMantleComponent { regionCaveChecked++; boolean chance = rng.chance(i.getChance()); if (traceRegen) { - IrisLogging.info("Regen object placer chance: chunk=" + x + "," + z + IrisLogging.debug("Regen object placer chance: chunk=" + x + "," + z + " scope=region-cave" + " chanceResult=" + chance + " chanceBase=" + i.getChance() @@ -361,7 +358,6 @@ public class MantleObjectComponent extends IrisMantleComponent { IrisLogging.error("Failed to place cave objects in the following region: " + region.getName()); IrisLogging.error("Object(s) " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ")."); IrisLogging.error("Are these objects missing?"); - e.printStackTrace(); } } } @@ -418,7 +414,7 @@ public class MantleObjectComponent extends IrisMantleComponent { boolean treePlacement = p instanceof IrisProceduralTree; boolean chancePassed = passesProceduralChance(rng, p.getChance()); if (golden) { - IrisLogging.info("Goldendebug procedural chance: chunk=" + x + "," + z + IrisLogging.debug("Goldendebug procedural chance: chunk=" + x + "," + z + " scope=" + scope + " placement=" + p.getName() + " passed=" + chancePassed); @@ -443,7 +439,7 @@ public class MantleObjectComponent extends IrisMantleComponent { IrisObject variant = p.getVariantObject(getData(), rng); if (variant == null) { if (golden) { - IrisLogging.info("Goldendebug procedural pick: chunk=" + x + "," + z + IrisLogging.debug("Goldendebug procedural pick: chunk=" + x + "," + z + " placement=" + p.getName() + " densityIndex=" + i + " variant=null"); @@ -468,7 +464,7 @@ public class MantleObjectComponent extends IrisMantleComponent { } if (carving && caveAnchor == null) { if (golden) { - IrisLogging.info("Goldendebug procedural cave anchor rejected: chunk=" + x + "," + z + IrisLogging.debug("Goldendebug procedural cave anchor rejected: chunk=" + x + "," + z + " placement=" + p.getName() + " minDepthBelowSurface=" + minDepthBelowSurface); } @@ -479,7 +475,7 @@ public class MantleObjectComponent extends IrisMantleComponent { int id = rng.i(0, Integer.MAX_VALUE); if (golden) { KList pool = p.getVariantObjects(getData()); - IrisLogging.info("Goldendebug procedural pick: chunk=" + x + "," + z + IrisLogging.debug("Goldendebug procedural pick: chunk=" + x + "," + z + " placement=" + p.getName() + " densityIndex=" + i + " variant=" + variant.getLoadKey() @@ -495,7 +491,7 @@ public class MantleObjectComponent extends IrisMantleComponent { if (carving) { int caveFloorY = caveAnchor.y(); if (golden) { - IrisLogging.info("Goldendebug procedural caveFloor: chunk=" + x + "," + z + IrisLogging.debug("Goldendebug procedural caveFloor: chunk=" + x + "," + z + " placement=" + p.getName() + " xx=" + xx + " zz=" + zz @@ -529,7 +525,7 @@ public class MantleObjectComponent extends IrisMantleComponent { }, null, getData()); } if (golden) { - IrisLogging.info("Goldendebug procedural result: chunk=" + x + "," + z + IrisLogging.debug("Goldendebug procedural result: chunk=" + x + "," + z + " placement=" + p.getName() + " variant=" + variant.getLoadKey() + " xx=" + xx @@ -540,7 +536,6 @@ public class MantleObjectComponent extends IrisMantleComponent { } catch (Throwable e) { IrisLogging.reportError(e); IrisLogging.error("Failed to place procedural object '" + p.getName() + "' in " + scope); - e.printStackTrace(); } } } @@ -690,7 +685,7 @@ public class MantleObjectComponent extends IrisMantleComponent { if (v == null) { nullObjects++; if (traceRegen) { - IrisLogging.warn("Regen object placement null object: chunk=" + chunkX + "," + chunkZ + IrisLogging.debug("Regen object placement null object: chunk=" + chunkX + "," + chunkZ + " scope=" + scope + " densityIndex=" + i + " density=" + density @@ -705,7 +700,7 @@ public class MantleObjectComponent extends IrisMantleComponent { int id = rng.i(0, Integer.MAX_VALUE); IObjectPlacer placePlacer = golden ? new GoldenDebugObjectPlacer(writer, scope + "/" + v.getLoadKey()) : writer; if (golden) { - IrisLogging.info("Goldendebug object attempt: chunk=" + chunkX + "," + chunkZ + IrisLogging.debug("Goldendebug object attempt: chunk=" + chunkX + "," + chunkZ + " scope=" + scope + " object=" + v.getLoadKey() + " densityIndex=" + i @@ -734,7 +729,7 @@ public class MantleObjectComponent extends IrisMantleComponent { } if (golden) { - IrisLogging.info("Goldendebug object result: chunk=" + chunkX + "," + chunkZ + IrisLogging.debug("Goldendebug object result: chunk=" + chunkX + "," + chunkZ + " scope=" + scope + " object=" + v.getLoadKey() + " resultY=" + result @@ -742,7 +737,7 @@ public class MantleObjectComponent extends IrisMantleComponent { } if (traceRegen) { - IrisLogging.info("Regen object placement result: chunk=" + chunkX + "," + chunkZ + IrisLogging.debug("Regen object placement result: chunk=" + chunkX + "," + chunkZ + " scope=" + scope + " object=" + v.getLoadKey() + " resultY=" + result @@ -829,7 +824,7 @@ public class MantleObjectComponent extends IrisMantleComponent { if (object == null) { nullObjects++; if (traceRegen) { - IrisLogging.warn("Regen cave object placement null object: chunk=" + metricChunkX + "," + metricChunkZ + IrisLogging.debug("Regen cave object placement null object: chunk=" + metricChunkX + "," + metricChunkZ + " scope=" + scope + " densityIndex=" + i + " density=" + density @@ -943,7 +938,7 @@ public class MantleObjectComponent extends IrisMantleComponent { } if (traceRegen) { - IrisLogging.info("Regen cave object placement result: chunk=" + metricChunkX + "," + metricChunkZ + IrisLogging.debug("Regen cave object placement result: chunk=" + metricChunkX + "," + metricChunkZ + " scope=" + scope + " object=" + object.getLoadKey() + " resultY=" + result @@ -1017,7 +1012,6 @@ public class MantleObjectComponent extends IrisMantleComponent { IrisLogging.reportError(e); IrisLogging.error("Failed to place upper-dimension objects in biome " + upperBiome.getName() + ": " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ")"); - e.printStackTrace(); } } } @@ -1033,7 +1027,6 @@ public class MantleObjectComponent extends IrisMantleComponent { IrisLogging.reportError(e); IrisLogging.error("Failed to place upper-dimension objects in region " + upperRegion.getName() + ": " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ")"); - e.printStackTrace(); } } } @@ -1104,7 +1097,7 @@ public class MantleObjectComponent extends IrisMantleComponent { }, null, getData()); if (traceRegen) { - IrisLogging.info("Upper object placement: chunk=" + chunkX + "," + chunkZ + IrisLogging.debug("Upper object placement: chunk=" + chunkX + "," + chunkZ + " scope=" + scope + " object=" + v.getLoadKey() + " anchorY=" + anchorY @@ -1163,7 +1156,7 @@ public class MantleObjectComponent extends IrisMantleComponent { int suppressed = state.suppressed.getAndSet(0); String anchorYText = anchorY == null ? "none" : String.valueOf(anchorY); String errorText = error == null ? "none" : error.getClass().getSimpleName() + ":" + String.valueOf(error.getMessage()); - IrisLogging.warn("Cave object reject: scope=" + scope + IrisLogging.debug("Cave object reject: scope=" + scope + " reason=" + reason + " chunk=" + chunkX + "," + chunkZ + " object=" + objectKey @@ -1609,7 +1602,6 @@ public class MantleObjectComponent extends IrisMantleComponent { return IrisObject.sampleSize(getData().getObjectLoader().findFile(objectKey)); } catch (IOException e) { IrisLogging.reportError(e); - e.printStackTrace(); } return null; diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleRiverCaveVoxelView.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleRiverCaveVoxelView.java index c095b66cc..a151d0345 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleRiverCaveVoxelView.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleRiverCaveVoxelView.java @@ -52,10 +52,6 @@ final class MantleRiverCaveVoxelView implements MantleRiverHydrologyComponent.Tu @Override public CaveVoxel voxelAt(CavePosition position) { - RiverCaveHydrology hydrology = dataIfPresent(position, RiverCaveHydrology.class); - if (hydrology != null && hydrology.carves()) { - return hydrology.isWet() ? CaveVoxel.COMPATIBLE_FLUID : CaveVoxel.CAVE_AIR; - } MatterCavern cavern = dataIfPresent(position, MatterCavern.class); if (cavern != null) { if (cavern.isLava()) { diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleRiverHydrologyComponent.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleRiverHydrologyComponent.java index 40f847317..736ed1ab4 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleRiverHydrologyComponent.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleRiverHydrologyComponent.java @@ -15,6 +15,7 @@ import art.arcane.iris.engine.river.RiverAnchor; import art.arcane.iris.engine.river.RiverRouteState; import art.arcane.iris.engine.river.RiverSample; import art.arcane.iris.engine.river.RiverSection; +import art.arcane.iris.engine.river.RiverTopologyComplexity; import art.arcane.iris.engine.river.cave.CavePosition; import art.arcane.iris.engine.river.cave.CaveVoxel; import art.arcane.iris.engine.river.cave.CaveVoxelPrecondition; @@ -223,7 +224,11 @@ public final class MantleRiverHydrologyComponent extends IrisMantleComponent { } static int tunnelHalo(IrisRiverRuntime runtime) { - return Math.max(1, (int) StrictMath.ceil(runtime.maximumChannelWidth() * 0.5D) + 1); + return RiverTopologyComplexity.tunnelHalo( + runtime.maximumChannelWidth(), + runtime.maximumTunnelWidthMultiplier(), + runtime.tunnelMouthBlend() + ); } static int waterHeadY(IrisRiverSurfaceSample sample, IrisRiverCaves caves) { @@ -261,6 +266,7 @@ public final class MantleRiverHydrologyComponent extends IrisMantleComponent { int chunkX, int chunkZ, int halo, + int dryHeadroom, FootprintSampler footprintSampler, TunnelSampler tunnelSampler, SurfaceSampler surfaceSampler @@ -286,13 +292,25 @@ public final class MantleRiverHydrologyComponent extends IrisMantleComponent { return TunnelPlan.empty(); } - Map candidateActions = mergeActions(solidColumns); - ArrayList containedColumns = new ArrayList<>(solidColumns.size()); - for (TunnelColumn column : solidColumns) { - if (isTunnelColumnContained(view, column, candidateActions.keySet(), surfaceSampler)) { - containedColumns.add(column); + ArrayList containedColumns = new ArrayList<>(solidColumns); + boolean changed; + do { + changed = false; + Set candidateActions = mergeActions(containedColumns).keySet(); + for (int index = containedColumns.size() - 1; index >= 0; index--) { + TunnelColumn column = containedColumns.get(index); + if (!isTunnelColumnContained( + view, + column, + candidateActions, + dryHeadroom, + surfaceSampler + )) { + containedColumns.remove(index); + changed = true; + } } - } + } while (changed && !containedColumns.isEmpty()); Map actions = mergeActions(containedColumns); LinkedHashMap preconditions = new LinkedHashMap<>(); for (CavePosition position : actions.keySet()) { @@ -302,15 +320,32 @@ public final class MantleRiverHydrologyComponent extends IrisMantleComponent { )); } for (CavePosition position : List.copyOf(actions.keySet())) { + RiverCaveAction action = actions.get(position); for (int[] offset : NEIGHBORS) { CavePosition neighbor = offset(position, offset); - if (actions.containsKey(neighbor) - || !view.isInWorld(neighbor) - || view.voxelAt(neighbor) != CaveVoxel.SOLID) { + if (actions.containsKey(neighbor) || !view.isInWorld(neighbor)) { + continue; + } + CaveVoxel neighborVoxel = view.voxelAt(neighbor); + boolean sealsSolidBoundary = neighborVoxel == CaveVoxel.SOLID; + boolean sealsCaveWaterline = action == RiverCaveAction.WET_SOURCE + && neighborVoxel == CaveVoxel.CAVE_AIR; + if (!sealsSolidBoundary && !sealsCaveWaterline) { + if (action == RiverCaveAction.DRY_AIR + && neighborVoxel == CaveVoxel.CAVE_AIR + && !view.isOpenToSurface(neighbor)) { + preconditions.putIfAbsent( + neighbor, + new CaveVoxelPrecondition(CaveVoxel.CAVE_AIR, false) + ); + } continue; } actions.putIfAbsent(neighbor, RiverCaveAction.SEAL_GUARD); - preconditions.putIfAbsent(neighbor, new CaveVoxelPrecondition(CaveVoxel.SOLID, false)); + preconditions.putIfAbsent(neighbor, new CaveVoxelPrecondition( + neighborVoxel, + view.isOpenToSurface(neighbor) + )); } } return new TunnelPlan(Map.copyOf(actions), Map.copyOf(preconditions)); @@ -332,7 +367,7 @@ public final class MantleRiverHydrologyComponent extends IrisMantleComponent { ? RiverCaveAction.WET_SOURCE : RiverCaveAction.DRY_AIR; if (!view.isInWorld(position) - || (view.voxelAt(position) != CaveVoxel.SOLID + || (!canCarveTunnelVoxel(view, position) && !matchesPublishedAction(view, position, action))) { return null; } @@ -345,6 +380,7 @@ public final class MantleRiverHydrologyComponent extends IrisMantleComponent { CaveVoxelView view, TunnelColumn column, Set candidateActions, + int dryHeadroom, SurfaceSampler surfaceSampler ) { for (CavePosition position : column.actions().keySet()) { @@ -356,7 +392,11 @@ public final class MantleRiverHydrologyComponent extends IrisMantleComponent { if (!view.isInWorld(neighbor)) { return false; } - if (view.voxelAt(neighbor) == CaveVoxel.SOLID || isSurfaceMouth(neighbor, surfaceSampler)) { + CaveVoxel neighborVoxel = view.voxelAt(neighbor); + if (neighborVoxel == CaveVoxel.SOLID + || (neighborVoxel == CaveVoxel.CAVE_AIR + && !view.isOpenToSurface(neighbor)) + || isSurfaceMouth(neighbor, dryHeadroom, surfaceSampler)) { continue; } return false; @@ -365,14 +405,24 @@ public final class MantleRiverHydrologyComponent extends IrisMantleComponent { return true; } - private static boolean isSurfaceMouth(CavePosition position, SurfaceSampler surfaceSampler) { + private static boolean canCarveTunnelVoxel(CaveVoxelView view, CavePosition position) { + CaveVoxel voxel = view.voxelAt(position); + return voxel == CaveVoxel.SOLID + || (voxel == CaveVoxel.CAVE_AIR && !view.isOpenToSurface(position)); + } + + private static boolean isSurfaceMouth( + CavePosition position, + int dryHeadroom, + SurfaceSampler surfaceSampler + ) { IrisRiverSurfaceSample sample = surfaceSampler.sample(position.x(), position.z()); if (!isWetChannelBed(sample) || sample.subterranean()) { return false; } int bedY = (int) Math.round(sample.terrainHeight()); int headY = (int) Math.round(sample.waterSurfaceY()); - return position.y() > bedY && position.y() <= headY; + return position.y() > bedY && position.y() <= headY + Math.max(0, dryHeadroom); } private static Map mergeActions(List columns) { @@ -423,6 +473,7 @@ public final class MantleRiverHydrologyComponent extends IrisMantleComponent { chunkX, chunkZ, tunnelHalo(runtime), + runtime.maximumTunnelHeadroom(), runtime::sampleFootprint, runtime::sampleTunnel, runtime::sample @@ -470,11 +521,16 @@ public final class MantleRiverHydrologyComponent extends IrisMantleComponent { int x = (int) StrictMath.floor(anchor.x()); int z = (int) StrictMath.floor(anchor.z()); IrisRiverSurfaceSample sample = runtime.sample(x, z); - if (!isWetChannelBed(sample)) { + IrisRiverTunnelSample tunnel = runtime.sampleTunnel(x, z); + if (!isWetChannelBed(sample) && tunnel == null) { return null; } - int bedY = (int) Math.round(sample.terrainHeight()); - int headY = waterHeadY(sample, caves); + int bedY = tunnel == null + ? (int) Math.round(sample.terrainHeight()) + : tunnel.bedY(); + int headY = tunnel == null + ? waterHeadY(sample, caves) + : tunnel.waterHeadY() + caves.getWaterLevelOffset(); int entryY = Math.max(bedY, headY); CavePosition entry = new CavePosition(x, entryY, z); if (!view.isInWorld(entry)) { @@ -548,7 +604,10 @@ public final class MantleRiverHydrologyComponent extends IrisMantleComponent { int offsetX, int offsetZ ) { - int preferredY = headY + caves.getDryHeadroom() - caves.getGrottoVerticalRadius(); + int preferredY = Math.min( + headY + caves.getDryHeadroom() - caves.getGrottoVerticalRadius(), + entry.y() - caves.getGrottoVerticalRadius() - 1 + ); int maximumY = Math.min(Math.min(entry.y() - 1, headY), preferredY); int minimumY = Math.max(1, entry.y() - caves.getMaxBoreDepth()); for (int y = maximumY; y >= minimumY; y--) { diff --git a/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveModifier.java b/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveModifier.java index 8ad292b6e..60694e3f0 100644 --- a/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveModifier.java +++ b/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveModifier.java @@ -28,6 +28,7 @@ import art.arcane.iris.engine.object.IrisDecorationPart; import art.arcane.iris.engine.object.IrisDecorator; import art.arcane.iris.engine.object.IrisDimensionCarvingResolver; import art.arcane.iris.engine.object.IrisProceduralBlocks; +import art.arcane.iris.engine.object.IrisRiverCaves; import art.arcane.iris.engine.river.cave.RiverCaveAction; import art.arcane.iris.engine.river.cave.RiverCaveHydrology; import art.arcane.iris.util.project.context.ChunkContext; @@ -58,6 +59,8 @@ public class IrisCarveModifier extends EngineAssignedModifier { + walls.forEach((rx, yy, rz, cavern, riverBoundary) -> { RiverCaveHydrology hydrology = dataIfPresent( mantleChunk, rx, yy, rz, RiverCaveHydrology.class); if (hydrology != null && hydrology.protectsPlacement()) { @@ -408,16 +411,16 @@ public class IrisCarveModifier extends EngineAssignedModifier= 0) { if (rz < 15 && !columnMasks[columnIndex + 1].contains(yy)) { - walls.put(rx, yy, rz + 1, BASIC_CAVERN); + walls.put(rx, yy, rz + 1, BASIC_CAVERN, false); } if (rx < 15 && !columnMasks[columnIndex + 16].contains(yy)) { - walls.put(rx + 1, yy, rz, BASIC_CAVERN); + walls.put(rx + 1, yy, rz, BASIC_CAVERN, false); } if (rz > 0 && !columnMasks[columnIndex - 1].contains(yy)) { - walls.put(rx, yy, rz - 1, BASIC_CAVERN); + walls.put(rx, yy, rz - 1, BASIC_CAVERN, false); } if (rx > 0 && !columnMasks[columnIndex - 16].contains(yy)) { - walls.put(rx - 1, yy, rz, BASIC_CAVERN); + walls.put(rx - 1, yy, rz, BASIC_CAVERN, false); } yy = columnMask.nextSetBit(yy + 1); } @@ -437,17 +440,19 @@ public class IrisCarveModifier extends EngineAssignedModifier= 0) { MatterCavern cavern = composedCavernAt(mc, rx, yy, rz); if (cavern != null) { + RiverCaveHydrology hydrology = dataIfPresent(mc, rx, yy, rz, RiverCaveHydrology.class); + boolean riverBoundary = hydrology != null && !hydrology.floodedBiomeKey().isEmpty(); if (rz < 15 && composedCavernAt(mc, rx, yy, rz + 1) == null) { - walls.put(rx, yy, rz + 1, cavern); + walls.put(rx, yy, rz + 1, cavern, riverBoundary); } if (rx < 15 && composedCavernAt(mc, rx + 1, yy, rz) == null) { - walls.put(rx + 1, yy, rz, cavern); + walls.put(rx + 1, yy, rz, cavern, riverBoundary); } if (rz > 0 && composedCavernAt(mc, rx, yy, rz - 1) == null) { - walls.put(rx, yy, rz - 1, cavern); + walls.put(rx, yy, rz - 1, cavern, riverBoundary); } if (rx > 0 && composedCavernAt(mc, rx - 1, yy, rz) == null) { - walls.put(rx - 1, yy, rz, cavern); + walls.put(rx - 1, yy, rz, cavern, riverBoundary); } } yy = columnMask.nextSetBit(yy + 1); @@ -523,7 +528,10 @@ public class IrisCarveModifier extends EngineAssignedModifier mantleChunk, int x, int y, int z, int worldX, int worldZ, IrisDimensionCarvingResolver.State resolverState, Long2ObjectOpenHashMap caveBiomeCache, Map customBiomeCache) { MatterCavern cavern = composedCavernAt(mantleChunk, x, y, z); + RiverCaveHydrology hydrology = dataIfPresent( + mantleChunk, x, y, z, RiverCaveHydrology.class); return resolveCaveBoundaryBiome( - cavern, worldX, y, worldZ, resolverState, caveBiomeCache, customBiomeCache); + cavern, worldX, y, worldZ, resolverState, caveBiomeCache, customBiomeCache, + hydrology != null && !hydrology.floodedBiomeKey().isEmpty()); } IrisBiome resolveCaveBoundaryBiome(MatterCavern cavern, int worldX, int y, int worldZ, IrisDimensionCarvingResolver.State resolverState, Long2ObjectOpenHashMap caveBiomeCache, Map customBiomeCache) { + return resolveCaveBoundaryBiome( + cavern, worldX, y, worldZ, resolverState, caveBiomeCache, customBiomeCache, false); + } + + private IrisBiome resolveCaveBoundaryBiome(MatterCavern cavern, int worldX, int y, int worldZ, IrisDimensionCarvingResolver.State resolverState, Long2ObjectOpenHashMap caveBiomeCache, Map customBiomeCache, boolean riverBoundary) { if (cavern != null && !cavern.getCustomBiome().isEmpty()) { + if (riverBoundary && selectsParentRiverBiome( + getEngine().getSeedManager().getCarve(), + worldX, + worldZ, + riverParentBiomeInheritance())) { + IrisBiome parent = resolveRiverParentBiome( + caveBiomeCache, worldX, y, worldZ, resolverState); + if (parent != null) { + return parent; + } + } return resolveCustomBiome(customBiomeCache, cavern.getCustomBiome()); } return resolveCaveBiome(caveBiomeCache, worldX, y, worldZ, resolverState); } + static boolean selectsParentRiverBiome(long seed, int worldX, int worldZ, double inheritance) { + if (inheritance <= 0D) { + return false; + } + if (inheritance >= 1D) { + return true; + } + int cellX = Math.floorDiv(worldX, RIVER_BIOME_INHERITANCE_CELL_SIZE); + int cellZ = Math.floorDiv(worldZ, RIVER_BIOME_INHERITANCE_CELL_SIZE); + long hash = (seed + RIVER_BIOME_INHERITANCE_SALT) ^ BlockPosition.toLong(cellX, 0, cellZ); + hash = (hash ^ (hash >>> 30)) * 0xBF58476D1CE4E5B9L; + hash = (hash ^ (hash >>> 27)) * 0x94D049BB133111EBL; + hash ^= hash >>> 31; + double roll = (hash >>> 11) * 0x1.0p-53; + return roll < inheritance; + } + + static boolean canReplaceRiverGuard( + RiverCaveHydrology hydrology, + PlatformBlockState layer, + boolean ceiling + ) { + if (hydrology == null || hydrology.action() != RiverCaveAction.SEAL_GUARD) { + return true; + } + return layer != null + && B.isSolid(layer) + && !B.isFluid(layer) + && (!ceiling || !isGravityAffected(layer)); + } + + private double riverParentBiomeInheritance() { + if (getDimension().getRivers() == null || getDimension().getRivers().getCaves() == null) { + return 0D; + } + IrisRiverCaves caves = getDimension().getRivers().getCaves(); + return Math.max(0D, Math.min(1D, caves.getParentBiomeInheritance())); + } + + private IrisBiome resolveRiverParentBiome( + Long2ObjectOpenHashMap caveBiomeCache, + int worldX, + int y, + int worldZ, + IrisDimensionCarvingResolver.State resolverState + ) { + IrisBiome parent = resolveCaveBiome(caveBiomeCache, worldX, y, worldZ, resolverState); + if (parent != null && parent == getEngine().getSurfaceBiome(worldX, worldZ)) { + IrisBiome natural = getComplex().getNaturalTrueBiomeStream().get(worldX, worldZ); + return natural == null ? parent : natural; + } + return parent; + } + static boolean canReplaceCaveFloorLayer(Hunk output, int x, int y, int z, PlatformBlockState layer) { return !isGravityAffected(layer) || y > 0 && B.isSolid(output.getRaw(x, y - 1, z)); } diff --git a/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveScratch.java b/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveScratch.java index 678fbd38c..8397feef3 100644 --- a/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveScratch.java +++ b/core/src/main/java/art/arcane/iris/engine/modifier/IrisCarveScratch.java @@ -140,6 +140,7 @@ final class CarveWallBuffer { private int[] keys; private MatterCavern[] values; + private boolean[] riverBoundaries; private int mask; private int resizeAt; private int size; @@ -154,11 +155,12 @@ final class CarveWallBuffer { keys = new int[capacity]; Arrays.fill(keys, EMPTY_KEY); values = new MatterCavern[capacity]; + riverBoundaries = new boolean[capacity]; mask = capacity - 1; resizeAt = Math.max(1, (int) (capacity * LOAD_FACTOR)); } - void put(int x, int y, int z, MatterCavern value) { + void put(int x, int y, int z, MatterCavern value, boolean riverBoundary) { int key = pack(x, y, z); int index = mix(key) & mask; @@ -167,6 +169,7 @@ final class CarveWallBuffer { if (existingKey == EMPTY_KEY) { keys[index] = key; values[index] = value; + riverBoundaries[index] = riverBoundary; size++; if (size >= resizeAt) { resize(); @@ -176,6 +179,7 @@ final class CarveWallBuffer { if (existingKey == key) { values[index] = value; + riverBoundaries[index] = riverBoundaries[index] || riverBoundary; return; } @@ -198,6 +202,21 @@ final class CarveWallBuffer { } } + boolean isRiverBoundary(int x, int y, int z) { + int key = pack(x, y, z); + int index = mix(key) & mask; + while (true) { + int existingKey = keys[index]; + if (existingKey == EMPTY_KEY) { + return false; + } + if (existingKey == key) { + return riverBoundaries[index]; + } + index = (index + 1) & mask; + } + } + void forEach(Consumer consumer) { for (int index = 0; index < keys.length; index++) { int key = keys[index]; @@ -207,7 +226,7 @@ final class CarveWallBuffer { MatterCavern cavern = values[index]; if (cavern != null) { - consumer.accept(unpackX(key), unpackY(key), unpackZ(key), cavern); + consumer.accept(unpackX(key), unpackY(key), unpackZ(key), cavern, riverBoundaries[index]); } } } @@ -215,16 +234,19 @@ final class CarveWallBuffer { void clear() { Arrays.fill(keys, EMPTY_KEY); Arrays.fill(values, null); + Arrays.fill(riverBoundaries, false); size = 0; } private void resize() { int[] oldKeys = keys; MatterCavern[] oldValues = values; + boolean[] oldRiverBoundaries = riverBoundaries; int nextCapacity = oldKeys.length << 1; keys = new int[nextCapacity]; Arrays.fill(keys, EMPTY_KEY); values = new MatterCavern[nextCapacity]; + riverBoundaries = new boolean[nextCapacity]; mask = nextCapacity - 1; resizeAt = Math.max(1, (int) (nextCapacity * LOAD_FACTOR)); size = 0; @@ -233,12 +255,12 @@ final class CarveWallBuffer { int key = oldKeys[index]; MatterCavern value = oldValues[index]; if (key != EMPTY_KEY && value != null) { - reinsert(key, value); + reinsert(key, value, oldRiverBoundaries[index]); } } } - private void reinsert(int key, MatterCavern value) { + private void reinsert(int key, MatterCavern value, boolean riverBoundary) { int index = mix(key) & mask; while (keys[index] != EMPTY_KEY) { index = (index + 1) & mask; @@ -246,6 +268,7 @@ final class CarveWallBuffer { keys[index] = key; values[index] = value; + riverBoundaries[index] = riverBoundary; size++; } @@ -272,6 +295,6 @@ final class CarveWallBuffer { @FunctionalInterface interface Consumer { - void accept(int x, int y, int z, MatterCavern cavern); + void accept(int x, int y, int z, MatterCavern cavern, boolean riverBoundary); } } diff --git a/core/src/main/java/art/arcane/iris/engine/modifier/IrisFloatingChildBiomeModifier.java b/core/src/main/java/art/arcane/iris/engine/modifier/IrisFloatingChildBiomeModifier.java index 0b73da9b2..eb8692c78 100644 --- a/core/src/main/java/art/arcane/iris/engine/modifier/IrisFloatingChildBiomeModifier.java +++ b/core/src/main/java/art/arcane/iris/engine/modifier/IrisFloatingChildBiomeModifier.java @@ -100,7 +100,6 @@ public class IrisFloatingChildBiomeModifier extends EngineAssignedModifier preparedStudioEntryChunks; private final AtomicInteger a = new AtomicInteger(0); private volatile long lastChunkGenTime = 0L; private final CompletableFuture spawnChunks = new CompletableFuture<>(); @@ -156,7 +146,6 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun this.hotloadChecker = new ChronoLatch(1000, false); this.studio = studio; this.studioEntryBootstrapActive = new AtomicBoolean(studio); - this.preparedStudioEntryChunks = new ConcurrentHashMap<>(); this.dataLocation = dataLocation; this.dimensionKey = dimensionKey; this.folder = new ReactiveFolder( @@ -211,7 +200,6 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun spawnChunks.completeExceptionally(e); IrisLogging.reportError(e); IrisLogging.error("Failed to initialize Iris generator for " + world.getName()); - e.printStackTrace(); if (e instanceof RuntimeException runtimeException) { throw runtimeException; } @@ -489,7 +477,6 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun if (currentEngine != null && !currentEngine.isClosed()) { currentEngine.close(); } - preparedStudioEntryChunks.clear(); folder.clear(); populators.clear(); }); @@ -540,124 +527,6 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun studioEntryBootstrapActive.set(false); } - public CompletableFuture prepareStudioEntryChunks( - World bukkitWorld, - int centerChunkX, - int centerChunkZ - ) { - if (!studio || closing) { - return CompletableFuture.completedFuture(null); - } - Engine activeEngine = getEngine(bukkitWorld); - computeStudioGenerator(); - if (studioGenerator != null) { - return CompletableFuture.completedFuture(null); - } - - long generationSessionId = activeEngine.getGenerationSessionId(); - ConcurrentHashMap prepared = new ConcurrentHashMap<>(); - ExecutorService executor = createStudioEntryExecutor(); - int diameter = STUDIO_ENTRY_PRECOMPUTE_RADIUS * 2 + 1; - ArrayList> tasks = new ArrayList<>(diameter * diameter); - for (int offsetX = -STUDIO_ENTRY_PRECOMPUTE_RADIUS; - offsetX <= STUDIO_ENTRY_PRECOMPUTE_RADIUS; - offsetX++) { - for (int offsetZ = -STUDIO_ENTRY_PRECOMPUTE_RADIUS; - offsetZ <= STUDIO_ENTRY_PRECOMPUTE_RADIUS; - offsetZ++) { - int chunkX = centerChunkX + offsetX; - int chunkZ = centerChunkZ + offsetZ; - CompletableFuture task = CompletableFuture.runAsync( - () -> prepareStudioEntryChunk( - bukkitWorld, - activeEngine, - generationSessionId, - chunkX, - chunkZ, - prepared), - executor); - tasks.add(task); - } - } - - CompletableFuture completion = CompletableFuture.allOf( - tasks.toArray(new CompletableFuture[0])); - CompletableFuture publication = completion.thenRun(() -> { - if (closing - || engine != activeEngine - || activeEngine.getGenerationSessionId() != generationSessionId) { - throw new IllegalStateException( - "Studio entry precompute finished for a replaced engine runtime."); - } - preparedStudioEntryChunks.clear(); - preparedStudioEntryChunks.putAll(prepared); - }); - return publication.whenComplete((ignored, failure) -> executor.shutdownNow()); - } - - private ExecutorService createStudioEntryExecutor() { - return Executors.newFixedThreadPool(STUDIO_ENTRY_PRECOMPUTE_THREADS, runnable -> { - Thread thread = new Thread( - runnable, - "Iris Studio Entry-" + STUDIO_ENTRY_THREAD_SEQUENCE.incrementAndGet()); - thread.setDaemon(true); - thread.setPriority(Thread.NORM_PRIORITY); - return thread; - }); - } - - private void prepareStudioEntryChunk( - World bukkitWorld, - Engine activeEngine, - long generationSessionId, - int chunkX, - int chunkZ, - ConcurrentHashMap prepared - ) { - try (GenerationStagePermit ignored = acquireGenerationStage( - "studio_entry_chunk_precompute")) { - TerrainChunk terrainChunk = TerrainChunk.create(bukkitWorld); - ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(terrainChunk.getChunkData()); - Hunk biomes = Hunk.viewBiomes(terrainChunk); - ChunkContext context = createStudioEntryContext( - activeEngine, - generationSessionId, - chunkX, - chunkZ); - activeEngine.generateMatter(chunkX, chunkZ, true, context); - try { - activeEngine.generate(chunkX << 4, chunkZ << 4, blocks, biomes, false); - } catch (WrongEngineBroException exception) { - throw new CompletionException(exception); - } - prepared.put( - chunkKey(chunkX, chunkZ), - new PreparedStudioChunk(activeEngine, generationSessionId, blocks) - ); - } - } - - private ChunkContext createStudioEntryContext( - Engine activeEngine, - long generationSessionId, - int chunkX, - int chunkZ - ) { - boolean cacheContext = !activeEngine.getPlatformHooks() - .shouldDisableChunkContextCache(activeEngine); - ChunkContext.PrefillPlan prefillPlan = cacheContext - ? ChunkContext.PrefillPlan.NO_CAVE - : ChunkContext.PrefillPlan.NONE; - return new ChunkContext( - chunkX << 4, - chunkZ << 4, - activeEngine.getComplex(), - generationSessionId, - cacheContext, - prefillPlan, - activeEngine.getMetrics()); - } - public boolean isStudioEntryBootstrapActive() { return studioEntryBootstrapActive.get(); } @@ -783,10 +652,8 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun } catch (InterruptedException e) { Thread.currentThread().interrupt(); IrisLogging.reportError(e); - e.printStackTrace(); } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); } finally { if (acquired) { loadLock.releaseExclusive(); @@ -916,14 +783,6 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun if (studioGenerator != null) { studioGenerator.generateChunk(engine, tc, x, z); } else { - PreparedStudioChunk prepared = preparedStudioEntryChunks.remove(chunkKey(x, z)); - if (prepared != null - && prepared.engine() == engine - && prepared.generationSessionId() == engine.getGenerationSessionId()) { - prepared.blocks().applyTo(d); - IrisLogging.debug("Applied prepared Studio entry chunk " + x + " " + z); - return; - } ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(d); Hunk biomes = Hunk.viewBiomes(tc); try (GenerationSessionLease lease = engine.acquireGenerationLease("bukkit_terrain_stage"); @@ -941,17 +800,13 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun throw new IllegalStateException("Iris chunk generation was rejected during an engine transition.", e); } - IrisLogging.error("======================================"); - e.printStackTrace(); + IrisLogging.reportError("Iris chunk generation could not acquire its engine runtime at " + x + "," + z + ".", e); reportErrorChunk(x, z, e); - IrisLogging.error("======================================"); throw new IllegalStateException("Iris chunk generation could not acquire its engine runtime.", e); } catch (Throwable e) { - IrisLogging.error("======================================"); - e.printStackTrace(); + IrisLogging.reportError("Iris failed to generate chunk " + x + "," + z + ".", e); reportErrorChunk(x, z, e); - IrisLogging.error("======================================"); throw new IllegalStateException("Iris failed to generate chunk " + x + "," + z + ".", e); } @@ -969,17 +824,6 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun return isMaintenanceActive(); } - private static long chunkKey(int chunkX, int chunkZ) { - return ((long) chunkX << 32) ^ (chunkZ & 0xFFFFFFFFL); - } - - private record PreparedStudioChunk( - Engine engine, - long generationSessionId, - ChunkDataHunkHolder blocks - ) { - } - private boolean isMaintenanceActive() { World realWorld = BukkitWorldBinding.world(this.world); return realWorld != null && IrisToolbelt.isWorldMaintenanceActive(realWorld); diff --git a/core/src/main/java/art/arcane/iris/engine/platform/EngineBukkitOps.java b/core/src/main/java/art/arcane/iris/engine/platform/EngineBukkitOps.java index dd3eba0ad..29c19d064 100644 --- a/core/src/main/java/art/arcane/iris/engine/platform/EngineBukkitOps.java +++ b/core/src/main/java/art/arcane/iris/engine/platform/EngineBukkitOps.java @@ -620,7 +620,6 @@ public final class EngineBukkitOps { Thread.currentThread().interrupt(); } catch (WrongEngineBroException | ExecutionException e) { IrisLogging.reportError(e); - e.printStackTrace(); } finally { if (search != null && !resultDispatched) { ACTIVE_LOCATE_REQUESTS.remove(playerId, search); diff --git a/core/src/main/java/art/arcane/iris/engine/platform/studio/generators/ObjectStudioGenerator.java b/core/src/main/java/art/arcane/iris/engine/platform/studio/generators/ObjectStudioGenerator.java index db25412dc..68e68dc36 100644 --- a/core/src/main/java/art/arcane/iris/engine/platform/studio/generators/ObjectStudioGenerator.java +++ b/core/src/main/java/art/arcane/iris/engine/platform/studio/generators/ObjectStudioGenerator.java @@ -267,7 +267,7 @@ public class ObjectStudioGenerator extends EnginedStudioGenerator { int cellCount = layout.cells().size(); IrisBlockVector worldExtent = computeExtent(layout); - IrisLogging.info("Object Studio layout built: %d cells from %d pack(s), extent %d x %d blocks", + IrisLogging.debug("Object Studio layout built: %d cells from %d pack(s), extent %d x %d blocks", cellCount, sources.size(), worldExtent.getBlockX(), worldExtent.getBlockZ()); } } diff --git a/core/src/main/java/art/arcane/iris/engine/river/RiverNetwork.java b/core/src/main/java/art/arcane/iris/engine/river/RiverNetwork.java index 51285a6dc..481873478 100644 --- a/core/src/main/java/art/arcane/iris/engine/river/RiverNetwork.java +++ b/core/src/main/java/art/arcane/iris/engine/river/RiverNetwork.java @@ -14,12 +14,28 @@ public final class RiverNetwork { private static final long NODE_RANK_SALT = 0x3C6EF372FE94F82BL; private static final long BASIN_X_SALT = 0xCBBB9D5DC1059ED8L; private static final long BASIN_Z_SALT = 0x629A292A367CD507L; + private static final long BASIN_DEVIATION_X_SALT = 0xA4093822299F31D0L; + private static final long BASIN_DEVIATION_Z_SALT = 0x082EFA98EC4E6C89L; private static final long DIAGONAL_SALT = 0xA54FF53A5F1D36F1L; private static final long SOURCE_SALT = 0x510E527FADE682D1L; private static final long SOURCE_FLOOR_SALT = 0xD6E8FEB86659FD93L; private static final long REACH_SALT = 0x9B05688C2B3E6C1FL; private static final long DRY_SALT = 0x1F83D9ABFB41BD6BL; private static final long MEANDER_SALT = 0x5BE0CD19137E2179L; + private static final long MEANDER_PERSONALITY_SALT = 0x243F6A8885A308D3L; + private static final long MEANDER_AMPLITUDE_SALT = 0x13198A2E03707344L; + private static final long MEANDER_PHASE_SALT = 0xD1310BA698DFB5ACL; + private static final long MEANDER_SKEW_SALT = 0x2FFD72DBD01ADFB7L; + private static final long MEANDER_CYCLE_SALT = 0x452821E638D01377L; + private static final long MEANDER_FEATURE_A_SALT = 0xBE5466CF34E90C6CL; + private static final long MEANDER_FEATURE_B_SALT = 0xC0AC29B7C97C50DDL; + private static final long CONFLUENCE_SALT = 0x9E3779B97F4A7C15L; + private static final long BRANCH_SLOT_SALT = 0x94D049BB133111EBL; + private static final long BRANCH_GATE_SALT = 0x2545F4914F6CDD1DL; + private static final int MEANDER_NOISE_SAMPLES = 12; + private static final int WIDTH_PROFILE_SAMPLES = 12; + private static final double TWO_PI = StrictMath.PI * 2D; + private static final MeanderPersonality[] MEANDER_PERSONALITIES = MeanderPersonality.values(); private final RiverNetworkOptions options; @@ -213,10 +229,20 @@ public final class RiverNetwork { private double drainageDistance(RiverNodeId id) { int basinCells = options.routingBasinCells(); - long basinX = Math.floorDiv(id.cellX(), basinCells); - long basinZ = Math.floorDiv(id.cellZ(), basinCells); double nodeX = id.cellX() + 0.5D; double nodeZ = id.cellZ() + 0.5D; + double deviationStrength = options.routingDeviationStrengthCells(); + if (deviationStrength > 0D) { + int deviationScale = options.routingDeviationScaleCells(); + double originalX = nodeX; + double originalZ = nodeZ; + nodeX += smoothCellNoise(originalX, originalZ, deviationScale, BASIN_DEVIATION_X_SALT) + * deviationStrength; + nodeZ += smoothCellNoise(originalX, originalZ, deviationScale, BASIN_DEVIATION_Z_SALT) + * deviationStrength; + } + long basinX = (long) StrictMath.floor(nodeX / basinCells); + long basinZ = (long) StrictMath.floor(nodeZ / basinCells); double jitterRadius = basinCells * 0.45D; double nearestDistance = Double.MAX_VALUE; for (long candidateX = basinX - 1L; candidateX <= basinX + 1L; candidateX++) { @@ -234,6 +260,24 @@ public final class RiverNetwork { return nearestDistance; } + private double smoothCellNoise(double x, double z, int scale, long salt) { + double scaledX = x / scale; + double scaledZ = z / scale; + long minimumX = (long) StrictMath.floor(scaledX); + long minimumZ = (long) StrictMath.floor(scaledZ); + double fractionX = scaledX - minimumX; + double fractionZ = scaledZ - minimumZ; + double fadeX = fractionX * fractionX * (3D - 2D * fractionX); + double fadeZ = fractionZ * fractionZ * (3D - 2D * fractionZ); + double northwest = centered(hash(minimumX, minimumZ, salt)); + double northeast = centered(hash(minimumX + 1L, minimumZ, salt)); + double southwest = centered(hash(minimumX, minimumZ + 1L, salt)); + double southeast = centered(hash(minimumX + 1L, minimumZ + 1L, salt)); + double north = northwest + (northeast - northwest) * fadeX; + double south = southwest + (southeast - southwest) * fadeX; + return north + (south - north) * fadeZ; + } + private NodePosition nodePosition(RiverNodeId id) { double centerX = ((double) id.cellX() + 0.5D) * options.cellSize(); double centerZ = ((double) id.cellZ() + 0.5D) * options.cellSize(); @@ -261,13 +305,19 @@ public final class RiverNetwork { if (compareRank(neighbor, node) >= 0) { continue; } + if (!branchPermitted(node, neighbor, resolver)) { + continue; + } RiverRoutingContext context = resolver.routingContext(node, neighbor); double routingCost = finiteNonNegative(resolver.terrain.reachRoutingCost(context)); double oceanAttraction = neighbor.ocean() ? options.oceanAttraction() : 0.0; double flowAlignmentCost = flowAlignmentCost(node, neighbor, resolver); + double confluenceAttraction = unit(hash(neighbor.id(), CONFLUENCE_SALT)) + * options.confluenceWeight(); ranked.add(new RankedCandidate( neighbor, - neighbor.routingScore() + routingCost + flowAlignmentCost - oceanAttraction + neighbor.routingScore() + routingCost + flowAlignmentCost + - oceanAttraction - confluenceAttraction )); } ranked.sort((first, second) -> { @@ -281,6 +331,19 @@ public final class RiverNetwork { return List.copyOf(candidates); } + private boolean branchPermitted(RiverNode child, RiverNode parent, NodeResolver resolver) { + RiverEdgeId childEdge = RiverEdgeId.of(child.id(), parent.id()); + int childSlot = resolver.branchSlot(parent, child); + if (childSlot < options.branchSoftCap()) { + return true; + } + double survivalChance = 1D; + for (int overflow = options.branchSoftCap(); overflow <= childSlot; overflow++) { + survivalChance *= options.branchChildShrinkFactor(); + } + return gate(hash(childEdge, BRANCH_GATE_SALT), survivalChance); + } + private RiverRoute trace(RiverNodeId sourceId, NodeResolver resolver) { if (!resolver.sourcePermitted(sourceId)) { return new RiverRoute(sourceId, RiverRouteState.SUPPRESSED, List.of(), false, false); @@ -290,6 +353,7 @@ public final class RiverNetwork { ArrayList edges = new ArrayList<>(options.maxRouteReaches()); RiverNode current = source; boolean reachedOcean = false; + boolean exhaustedHorizon = true; for (int reachIndex = 0; reachIndex < options.maxRouteReaches(); reachIndex++) { RiverNode next = null; int examined = 0; @@ -305,10 +369,12 @@ public final class RiverNetwork { } } if (next == null) { + exhaustedHorizon = false; break; } RiverEdgeId edgeId = RiverEdgeId.of(current.id(), next.id()); if (!resolver.continuationPermitted(resolver.routingContext(current, next))) { + exhaustedHorizon = false; break; } edges.add(edgeId); @@ -322,6 +388,9 @@ public final class RiverNetwork { if (reachedOcean) { return new RiverRoute(sourceId, RiverRouteState.WET, edges, true, false); } + if (exhaustedHorizon && !options.requireOcean()) { + return new RiverRoute(sourceId, RiverRouteState.WET, edges, false, false); + } if (!edges.isEmpty()) { RiverTerminalPolicy terminalPolicy = resolver.terminalPolicy(current); if (terminalPolicy == RiverTerminalPolicy.WET @@ -388,13 +457,11 @@ public final class RiverNetwork { NodeResolver resolver ) { int pointCount = options.meanderSubdivisions() + 1; - double[] x = new double[pointCount]; - double[] z = new double[pointCount]; + double[] baseX = new double[pointCount]; + double[] baseZ = new double[pointCount]; double deltaX = to.x() - from.x(); double deltaZ = to.z() - from.z(); double length = StrictMath.hypot(deltaX, deltaZ); - double normalX = length == 0.0 ? 0.0 : -deltaZ / length; - double normalZ = length == 0.0 ? 0.0 : deltaX / length; double maximumOffset = StrictMath.min(options.meanderStrength(), length * 0.35); double directionX = length == 0D ? 1D : deltaX / length; double directionZ = length == 0D ? 0D : deltaZ / length; @@ -416,17 +483,29 @@ public final class RiverNetwork { + fromTangentWeight * fromTangent.z() * maximumOffset + toWeight * to.z() + toTangentWeight * toTangent.z() * maximumOffset; + baseX[point] = curvedX; + baseZ[point] = curvedZ; + } + if (maximumOffset <= 0D) { + return new RiverPolyline(baseX, baseZ); + } + MeanderProfile profile = meanderProfile(id); + double[] meanderNoise = meanderNoise(id, baseX, baseZ, resolver); + double[] meanderSignal = meanderSignal(id, profile, meanderNoise); + double[] x = new double[pointCount]; + double[] z = new double[pointCount]; + for (int point = 0; point < pointCount; point++) { + double t = (double) point / (pointCount - 1); + double tSquared = t * t; + double envelope = 16D * tSquared * (1D - t) * (1D - t); double straightX = from.x() + deltaX * t; double straightZ = from.z() + deltaZ * t; - RiverMeanderContext context = new RiverMeanderContext(id, t, straightX, straightZ); - double configuredNoise = resolver.terrain.meanderNoise(context); - double noise = Double.isFinite(configuredNoise) - ? StrictMath.max(-1.0, StrictMath.min(1.0, configuredNoise)) - : smoothEdgeNoise(id, t * 3.0); - double envelope = 16D * tSquared * (1D - t) * (1D - t); - double offset = maximumOffset * 0.5D * envelope * noise; - x[point] = curvedX + normalX * offset; - z[point] = curvedZ + normalZ * offset; + double baseDisplacement = StrictMath.hypot(baseX[point] - straightX, baseZ[point] - straightZ); + double availableOffset = StrictMath.max(0D, maximumOffset - baseDisplacement); + double offset = availableOffset * 0.92D * envelope * meanderSignal[point]; + FlowTangent normal = localNormal(baseX, baseZ, point); + x[point] = baseX[point] + normal.x() * offset; + z[point] = baseZ[point] + normal.z() * offset; } x[0] = from.x(); z[0] = from.z(); @@ -435,6 +514,200 @@ public final class RiverNetwork { return new RiverPolyline(x, z); } + private double[] meanderNoise( + RiverEdgeId id, + double[] baseX, + double[] baseZ, + NodeResolver resolver + ) { + int sampleCount = StrictMath.min(MEANDER_NOISE_SAMPLES, baseX.length); + double[] knots = new double[sampleCount]; + for (int sample = 0; sample < sampleCount; sample++) { + double t = (double) sample / (sampleCount - 1); + double position = t * (baseX.length - 1); + int lower = (int) StrictMath.floor(position); + int upper = StrictMath.min(baseX.length - 1, lower + 1); + double interpolation = position - lower; + double curvedX = baseX[lower] + (baseX[upper] - baseX[lower]) * interpolation; + double curvedZ = baseZ[lower] + (baseZ[upper] - baseZ[lower]) * interpolation; + RiverMeanderContext context = new RiverMeanderContext(id, t, curvedX, curvedZ); + double configuredNoise = resolver.terrain.meanderNoise(context); + knots[sample] = Double.isFinite(configuredNoise) + ? StrictMath.max(-1D, StrictMath.min(1D, configuredNoise)) + : smoothEdgeNoise(id, t * 3D); + } + double[] noise = new double[baseX.length]; + for (int point = 0; point < noise.length; point++) { + double t = (double) point / (noise.length - 1); + double position = t * (sampleCount - 1); + int lower = (int) StrictMath.floor(position); + int upper = StrictMath.min(sampleCount - 1, lower + 1); + double interpolation = smoothStep(position - lower); + noise[point] = knots[lower] + (knots[upper] - knots[lower]) * interpolation; + } + return noise; + } + + private MeanderProfile meanderProfile(RiverEdgeId id) { + int personalityIndex = (int) StrictMath.floor( + unit(hash(id, MEANDER_PERSONALITY_SALT)) * MEANDER_PERSONALITIES.length + ); + MeanderPersonality personality = MEANDER_PERSONALITIES[ + StrictMath.min(MEANDER_PERSONALITIES.length - 1, personalityIndex) + ]; + double amplitudeRoll = unit(hash(id, MEANDER_AMPLITUDE_SALT)); + double amplitude = personality == MeanderPersonality.QUIET + ? 0.16D + amplitudeRoll * 0.24D + : 0.48D + amplitudeRoll * 0.52D; + double maximumCycles = StrictMath.max(1D, StrictMath.min(8D, options.meanderSubdivisions() / 5D)); + double cycleRoll = unit(hash(id, MEANDER_CYCLE_SALT)); + double cycles = switch (personality) { + case SWEEP, HOOK, QUIET -> 0.45D + cycleRoll * 0.65D; + case S_CURVE -> 0.75D + cycleRoll * StrictMath.min(1.25D, maximumCycles); + case OXBOW -> 1D + cycleRoll * StrictMath.min(1.5D, maximumCycles); + case COIL -> StrictMath.min(maximumCycles, 2.25D + cycleRoll * 4.75D); + case WANDER -> 0.65D + cycleRoll * StrictMath.min(2.35D, maximumCycles); + case CHIRP -> StrictMath.min(maximumCycles, 1.5D + cycleRoll * 5.5D); + }; + double handedness = (hash(id, MEANDER_SALT) & 1L) == 0L ? -1D : 1D; + return new MeanderProfile( + personality, + amplitude, + handedness, + unit(hash(id, MEANDER_PHASE_SALT)) * TWO_PI, + centered(hash(id, MEANDER_SKEW_SALT)) * 0.72D, + cycles, + unit(hash(id, MEANDER_FEATURE_A_SALT)), + unit(hash(id, MEANDER_FEATURE_B_SALT)) + ); + } + + private double[] meanderSignal(RiverEdgeId id, MeanderProfile profile, double[] noise) { + int subdivisions = noise.length - 1; + for (int point = 0; point < noise.length; point++) { + double t = (double) point / subdivisions; + noise[point] = noise[point] * 0.78D + smoothEdgeNoise(id, t * 3.5D) * 0.22D; + } + smoothSignal(noise); + double detailCycles = StrictMath.max(0.5D, profile.cycles() + profile.featureB() * 1.5D); + double baseStep = TWO_PI * detailCycles / subdivisions; + double phase = profile.phase() + profile.featureA() * StrictMath.PI; + double[] signal = new double[noise.length]; + for (int point = 0; point < noise.length; point++) { + double t = (double) point / subdivisions; + if (point > 0) { + double frequencyNoise = (noise[point - 1] + noise[point]) * 0.25D + 0.5D; + phase += baseStep * (0.45D + frequencyNoise * 1.1D); + } + double warpedT = warpMeanderPosition(t, profile.skew()); + double macro = personalitySignal(profile, warpedT, noise[point]); + double detailWeight = detailWeight(profile.personality()); + double detail = detailWeight <= 0D + ? 0D + : StrictMath.sin(phase) * (0.65D + StrictMath.abs(noise[point]) * 0.35D); + double noiseWeight = noiseWeight(profile.personality()); + double macroWeight = 1D - detailWeight - noiseWeight; + signal[point] = clampSigned(profile.amplitude() + * (macro * macroWeight + detail * detailWeight + noise[point] * noiseWeight)); + } + smoothSignal(signal); + return signal; + } + + private double personalitySignal(MeanderProfile profile, double t, double noise) { + double handedness = profile.handedness(); + return switch (profile.personality()) { + case SWEEP -> handedness * (0.68D + StrictMath.sin(StrictMath.PI * t) * 0.32D); + case HOOK -> handedness * (0.12D + smoothStep( + profile.featureA() < 0.5D ? t : 1D - t + ) * 0.88D); + case S_CURVE -> handedness * StrictMath.sin( + TWO_PI * profile.cycles() * t + profile.phase() + ); + case COIL -> StrictMath.sin(TWO_PI * profile.cycles() * t + profile.phase()) + * (0.62D + StrictMath.sin(StrictMath.PI * t) * 0.38D); + case WANDER -> clampSigned( + noise * 0.82D + + StrictMath.sin(TWO_PI * profile.cycles() * t + profile.phase()) * 0.38D + + handedness * 0.12D + ); + case OXBOW -> handedness * clampSigned( + compactLobe(t, 0.18D + profile.featureA() * 0.2D, 0.24D + profile.featureB() * 0.12D) + - compactLobe(t, 0.62D + profile.featureB() * 0.2D, 0.22D + profile.featureA() * 0.14D) + * (0.55D + profile.featureA() * 0.45D) + ); + case CHIRP -> StrictMath.sin( + profile.phase() + TWO_PI * (0.45D * t + profile.cycles() * t * t) + ); + case QUIET -> handedness * (0.72D + noise * 0.28D); + }; + } + + private double detailWeight(MeanderPersonality personality) { + return switch (personality) { + case SWEEP, HOOK, OXBOW, QUIET -> 0D; + case S_CURVE -> 0.12D; + case COIL, WANDER -> 0.08D; + case CHIRP -> 0.1D; + }; + } + + private double noiseWeight(MeanderPersonality personality) { + return switch (personality) { + case SWEEP, S_CURVE, COIL, OXBOW, CHIRP -> 0.08D; + case HOOK -> 0.1D; + case WANDER -> 0.34D; + case QUIET -> 0.14D; + }; + } + + private double warpMeanderPosition(double t, double skew) { + return t + skew * t * (1D - t); + } + + private double compactLobe(double t, double center, double radius) { + double distance = StrictMath.abs(t - center) / radius; + if (distance >= 1D) { + return 0D; + } + return smoothStep(1D - distance); + } + + private double smoothStep(double value) { + double clamped = StrictMath.max(0D, StrictMath.min(1D, value)); + return clamped * clamped * (3D - 2D * clamped); + } + + private void smoothSignal(double[] signal) { + if (signal.length < 3) { + return; + } + double previousRaw = signal[0]; + double currentRaw = signal[1]; + for (int point = 1; point < signal.length - 1; point++) { + double nextRaw = signal[point + 1]; + signal[point] = (previousRaw + currentRaw * 2D + nextRaw) * 0.25D; + previousRaw = currentRaw; + currentRaw = nextRaw; + } + } + + private double clampSigned(double value) { + return StrictMath.max(-1D, StrictMath.min(1D, value)); + } + + private FlowTangent localNormal(double[] x, double[] z, int point) { + int previous = StrictMath.max(0, point - 1); + int next = StrictMath.min(x.length - 1, point + 1); + double tangentX = x[next] - x[previous]; + double tangentZ = z[next] - z[previous]; + double tangentLength = StrictMath.hypot(tangentX, tangentZ); + if (tangentLength <= 0.0000001D) { + return new FlowTangent(0D, 0D); + } + return new FlowTangent(-tangentZ / tangentLength, tangentX / tangentLength); + } + private FlowTangent flowTangent( RiverNode node, double fallbackX, @@ -643,6 +916,8 @@ public final class RiverNetwork { private final Map terminalPolicies; private final Map routingContexts; private final Map flowTangents; + private final Map branchSlots; + private final Map resolvedBranchParents; private NodeResolver(RiverTerrainSampler terrain) { this.terrain = terrain; @@ -656,6 +931,8 @@ public final class RiverNetwork { terminalPolicies = new HashMap<>(); routingContexts = new HashMap<>(); flowTangents = new HashMap<>(); + branchSlots = new HashMap<>(); + resolvedBranchParents = new HashMap<>(); } private RiverNode resolve(RiverNodeId id) { @@ -668,6 +945,32 @@ public final class RiverNetwork { ignored -> computeDownstreamCandidates(node, this)); } + private int branchSlot(RiverNode parent, RiverNode child) { + if (!resolvedBranchParents.containsKey(parent.id())) { + ArrayList upstream = new ArrayList<>(8); + for (RiverNodeId siblingId : neighbors(parent.id())) { + RiverNode sibling = resolve(siblingId); + if (sibling.riverAllowed() && compareRank(sibling, parent) > 0) { + upstream.add(sibling); + } + } + upstream.sort((first, second) -> { + long firstPriority = hash(RiverEdgeId.of(first.id(), parent.id()), BRANCH_SLOT_SALT); + long secondPriority = hash(RiverEdgeId.of(second.id(), parent.id()), BRANCH_SLOT_SALT); + int priorityComparison = Long.compareUnsigned(firstPriority, secondPriority); + return priorityComparison != 0 + ? priorityComparison + : first.id().compareTo(second.id()); + }); + for (int slot = 0; slot < upstream.size(); slot++) { + RiverNode sibling = upstream.get(slot); + branchSlots.put(RiverEdgeId.of(sibling.id(), parent.id()), slot); + } + resolvedBranchParents.put(parent.id(), true); + } + return branchSlots.getOrDefault(RiverEdgeId.of(child.id(), parent.id()), Integer.MAX_VALUE); + } + private boolean sourcePermitted(RiverNodeId sourceId) { return sourceGates.computeIfAbsent(sourceId, this::computeSourcePermitted); } @@ -819,6 +1122,29 @@ public final class RiverNetwork { private record FlowTangent(double x, double z) { } + private record MeanderProfile( + MeanderPersonality personality, + double amplitude, + double handedness, + double phase, + double skew, + double cycles, + double featureA, + double featureB + ) { + } + + private enum MeanderPersonality { + QUIET, + SWEEP, + HOOK, + S_CURVE, + COIL, + WANDER, + OXBOW, + CHIRP + } + private record SourceTileId(long tileX, long tileZ) { } @@ -867,10 +1193,6 @@ public final class RiverNetwork { private RiverReach build() { int flow = wetFlow + dryFlow; int order = 1 + (31 - Integer.numberOfLeadingZeros(flow)); - double baseWidth = positiveOrFallback( - terrain.channelWidth(context, options.channelWidth()), - options.channelWidth() - ); double bankWidth = nonNegativeOrFallback( terrain.bankWidth(context, options.bankWidth()), options.bankWidth() @@ -879,10 +1201,8 @@ public final class RiverNetwork { terrain.depth(context, options.depth()), options.depth() ); - double width = StrictMath.min( - options.maxChannelWidth(), - baseWidth * (1.0 + options.orderWidthFactor() * (order - 1)) - ); + RiverWidthProfile widthProfile = widthProfile(order); + double width = widthProfile.maximum(); bankWidth = StrictMath.min(options.maxBankWidth(), bankWidth); double depth = StrictMath.min( options.maxDepth(), @@ -897,6 +1217,7 @@ public final class RiverNetwork { flow, order, width, + widthProfile, bankWidth, depth, state == RiverRouteState.WET && to.ocean(), @@ -906,6 +1227,54 @@ public final class RiverNetwork { context.polyline() ); } + + private RiverWidthProfile widthProfile(int order) { + RiverPolyline polyline = context.polyline(); + int sampleCount = Math.min(WIDTH_PROFILE_SAMPLES, Math.max(2, polyline.size())); + double[] positions = new double[sampleCount]; + double[] widths = new double[sampleCount]; + double orderScale = 1D + options.orderWidthFactor() * (order - 1); + for (int index = 0; index < sampleCount; index++) { + double alongReach = (double) index / (sampleCount - 1); + ReachPosition position = positionAt(polyline, alongReach); + double baseWidth = positiveOrFallback( + terrain.channelWidth( + context, + position.x(), + position.z(), + options.channelWidth() + ), + options.channelWidth() + ); + positions[index] = alongReach; + widths[index] = StrictMath.min( + options.maxChannelWidth(), + StrictMath.max(1D, baseWidth * orderScale) + ); + } + return new RiverWidthProfile(positions, widths); + } + } + + private static ReachPosition positionAt(RiverPolyline polyline, double alongReach) { + double targetDistance = Math.max(0D, Math.min(1D, alongReach)) * polyline.length(); + for (int point = 0; point < polyline.size() - 1; point++) { + double segmentStart = polyline.cumulativeLength(point); + double segmentEnd = polyline.cumulativeLength(point + 1); + if (targetDistance > segmentEnd && point < polyline.size() - 2) { + continue; + } + double segmentLength = segmentEnd - segmentStart; + double interpolation = segmentLength <= 0D + ? 0D + : (targetDistance - segmentStart) / segmentLength; + return new ReachPosition( + polyline.x(point) + (polyline.x(point + 1) - polyline.x(point)) * interpolation, + polyline.z(point) + (polyline.z(point + 1) - polyline.z(point)) * interpolation + ); + } + int last = polyline.size() - 1; + return new ReachPosition(polyline.x(last), polyline.z(last)); } private static double positiveOrFallback(double value, double fallback) { @@ -915,4 +1284,7 @@ public final class RiverNetwork { private static double nonNegativeOrFallback(double value, double fallback) { return Double.isFinite(value) && value >= 0.0 ? value : fallback; } + + private record ReachPosition(double x, double z) { + } } diff --git a/core/src/main/java/art/arcane/iris/engine/river/RiverNetworkOptions.java b/core/src/main/java/art/arcane/iris/engine/river/RiverNetworkOptions.java index 464202b33..98fa0ed8e 100644 --- a/core/src/main/java/art/arcane/iris/engine/river/RiverNetworkOptions.java +++ b/core/src/main/java/art/arcane/iris/engine/river/RiverNetworkOptions.java @@ -9,6 +9,8 @@ public record RiverNetworkOptions( int minimumSourcesPerTile, int downstreamCandidateLimit, int routingBasinCells, + int routingDeviationScaleCells, + double routingDeviationStrengthCells, double routingPlateauHeight, double hydraulicBaseHeight, boolean requireOcean, @@ -18,6 +20,9 @@ public record RiverNetworkOptions( double terrainHeightWeight, double routingNoiseWeight, double flowAlignmentWeight, + double confluenceWeight, + int branchSoftCap, + double branchChildShrinkFactor, double oceanAttraction, double channelWidth, double bankWidth, @@ -38,6 +43,8 @@ public record RiverNetworkOptions( requireRange(minimumSourcesPerTile, 0, tileCells * tileCells, "minimumSourcesPerTile"); requireRange(downstreamCandidateLimit, 1, 8, "downstreamCandidateLimit"); requireRange(routingBasinCells, 8, 256, "routingBasinCells"); + requireRange(routingDeviationScaleCells, 8, 256, "routingDeviationScaleCells"); + requireRange(routingDeviationStrengthCells, 0D, 32D, "routingDeviationStrengthCells"); requirePositive(routingPlateauHeight, "routingPlateauHeight"); requireFinite(hydraulicBaseHeight, "hydraulicBaseHeight"); requireRange(meanderSubdivisions, 1, 64, "meanderSubdivisions"); @@ -48,6 +55,9 @@ public record RiverNetworkOptions( requireFiniteNonNegative(terrainHeightWeight, "terrainHeightWeight"); requireFiniteNonNegative(routingNoiseWeight, "routingNoiseWeight"); requireFiniteNonNegative(flowAlignmentWeight, "flowAlignmentWeight"); + requireFiniteNonNegative(confluenceWeight, "confluenceWeight"); + requireRange(branchSoftCap, 1, 8, "branchSoftCap"); + requireProbability(branchChildShrinkFactor, "branchChildShrinkFactor"); requireFiniteNonNegative(oceanAttraction, "oceanAttraction"); requirePositive(channelWidth, "channelWidth"); requireFiniteNonNegative(bankWidth, "bankWidth"); @@ -80,6 +90,12 @@ public record RiverNetworkOptions( } } + private static void requireRange(double value, double minimum, double maximum, String name) { + if (!Double.isFinite(value) || value < minimum || value > maximum) { + throw new IllegalArgumentException(name + " must be between " + minimum + " and " + maximum); + } + } + private static void requireProbability(double value, String name) { if (!Double.isFinite(value) || value < 0.0 || value > 1.0) { throw new IllegalArgumentException(name + " must be finite and between 0 and 1"); @@ -113,6 +129,8 @@ public record RiverNetworkOptions( private int minimumSourcesPerTile; private int downstreamCandidateLimit; private int routingBasinCells; + private int routingDeviationScaleCells; + private double routingDeviationStrengthCells; private double routingPlateauHeight; private double hydraulicBaseHeight; private boolean requireOcean; @@ -122,6 +140,9 @@ public record RiverNetworkOptions( private double terrainHeightWeight; private double routingNoiseWeight; private double flowAlignmentWeight; + private double confluenceWeight; + private int branchSoftCap; + private double branchChildShrinkFactor; private double oceanAttraction; private double channelWidth; private double bankWidth; @@ -144,6 +165,8 @@ public record RiverNetworkOptions( minimumSourcesPerTile = 0; downstreamCandidateLimit = 4; routingBasinCells = 64; + routingDeviationScaleCells = 24; + routingDeviationStrengthCells = 0D; routingPlateauHeight = 8.0; hydraulicBaseHeight = 64D; requireOcean = false; @@ -153,6 +176,9 @@ public record RiverNetworkOptions( terrainHeightWeight = 1.0; routingNoiseWeight = 24.0; flowAlignmentWeight = 0D; + confluenceWeight = 0D; + branchSoftCap = 4; + branchChildShrinkFactor = 0.35D; oceanAttraction = 64.0; channelWidth = 10.0; bankWidth = 8.0; @@ -202,6 +228,16 @@ public record RiverNetworkOptions( return this; } + public Builder routingDeviationScaleCells(int value) { + routingDeviationScaleCells = value; + return this; + } + + public Builder routingDeviationStrengthCells(double value) { + routingDeviationStrengthCells = value; + return this; + } + public Builder routingPlateauHeight(double value) { routingPlateauHeight = value; return this; @@ -247,6 +283,21 @@ public record RiverNetworkOptions( return this; } + public Builder confluenceWeight(double value) { + confluenceWeight = value; + return this; + } + + public Builder branchSoftCap(int value) { + branchSoftCap = value; + return this; + } + + public Builder branchChildShrinkFactor(double value) { + branchChildShrinkFactor = value; + return this; + } + public Builder oceanAttraction(double value) { oceanAttraction = value; return this; @@ -320,6 +371,8 @@ public record RiverNetworkOptions( minimumSourcesPerTile, downstreamCandidateLimit, routingBasinCells, + routingDeviationScaleCells, + routingDeviationStrengthCells, routingPlateauHeight, hydraulicBaseHeight, requireOcean, @@ -329,6 +382,9 @@ public record RiverNetworkOptions( terrainHeightWeight, routingNoiseWeight, flowAlignmentWeight, + confluenceWeight, + branchSoftCap, + branchChildShrinkFactor, oceanAttraction, channelWidth, bankWidth, diff --git a/core/src/main/java/art/arcane/iris/engine/river/RiverReach.java b/core/src/main/java/art/arcane/iris/engine/river/RiverReach.java index 2b795d4cf..8abfe4b17 100644 --- a/core/src/main/java/art/arcane/iris/engine/river/RiverReach.java +++ b/core/src/main/java/art/arcane/iris/engine/river/RiverReach.java @@ -10,6 +10,7 @@ public record RiverReach( int flow, int order, double width, + RiverWidthProfile widthProfile, double bankWidth, double depth, boolean mouth, @@ -21,6 +22,7 @@ public record RiverReach( Objects.requireNonNull(from); Objects.requireNonNull(to); Objects.requireNonNull(state); + Objects.requireNonNull(widthProfile); Objects.requireNonNull(polyline); if (state == RiverRouteState.SUPPRESSED) { throw new IllegalArgumentException("Suppressed routes cannot produce reaches"); @@ -32,5 +34,12 @@ public record RiverReach( || !Double.isFinite(depth) || depth <= 0.0) { throw new IllegalArgumentException("River reach dimensions must be finite and valid"); } + if (Double.compare(width, widthProfile.maximum()) != 0) { + throw new IllegalArgumentException("River reach width must equal its profile maximum"); + } + } + + public double widthAt(double alongReach) { + return widthProfile.sample(alongReach); } } diff --git a/core/src/main/java/art/arcane/iris/engine/river/RiverTerrainSampler.java b/core/src/main/java/art/arcane/iris/engine/river/RiverTerrainSampler.java index a414624f5..445657cf7 100644 --- a/core/src/main/java/art/arcane/iris/engine/river/RiverTerrainSampler.java +++ b/core/src/main/java/art/arcane/iris/engine/river/RiverTerrainSampler.java @@ -62,6 +62,15 @@ public interface RiverTerrainSampler { return fallback; } + default double channelWidth( + RiverRoutingContext context, + double x, + double z, + double fallback + ) { + return channelWidth(context, fallback); + } + default double bankWidth(RiverRoutingContext context, double fallback) { return fallback; } diff --git a/core/src/main/java/art/arcane/iris/engine/river/RiverTile.java b/core/src/main/java/art/arcane/iris/engine/river/RiverTile.java index 7dc838858..483d953bf 100644 --- a/core/src/main/java/art/arcane/iris/engine/river/RiverTile.java +++ b/core/src/main/java/art/arcane/iris/engine/river/RiverTile.java @@ -117,13 +117,27 @@ public final class RiverTile { } public RiverSample sample(double x, double z) { + return sampleExpanded(x, z, 0D); + } + + public RiverSample sampleExpanded(double x, double z, double additionalRadius) { + if (!Double.isFinite(additionalRadius) || additionalRadius < 0D) { + throw new IllegalArgumentException("Additional river sample radius must be finite and non-negative"); + } RiverReach nearestReach = null; double nearestDistanceSquared = Double.POSITIVE_INFINITY; double nearestAlongReach = 0.0; - for (RiverReach reach : indexedReaches(x, z)) { - ClosestPoint closest = closestPoint(reach.polyline(), x, z); - double outerRadius = reach.width() * 0.5 + reach.bankWidth(); - if (closest.distanceSquared() > outerRadius * outerRadius) { + List candidates = additionalRadius == 0D + ? indexedReaches(x, z) + : indexedReaches( + x - additionalRadius, + z - additionalRadius, + x + additionalRadius, + z + additionalRadius + ); + for (RiverReach reach : candidates) { + ClosestPoint closest = closestCoveringPoint(reach, x, z, additionalRadius); + if (closest == null) { continue; } if (closest.distanceSquared() < nearestDistanceSquared @@ -162,15 +176,14 @@ public final class RiverTile { queryMaximumX, queryMaximumZ )) { - ClosestPoint closest = closestPoint( - reach.polyline(), + ClosestPoint closest = closestCoveringPoint( + reach, queryMinimumX, queryMinimumZ, queryMaximumX, queryMaximumZ ); - double outerRadius = reach.width() * 0.5 + reach.bankWidth(); - if (closest.distanceSquared() > outerRadius * outerRadius) { + if (closest == null) { continue; } if (closest.distanceSquared() < nearestDistanceSquared @@ -196,7 +209,8 @@ public final class RiverTile { ) { double distance = StrictMath.sqrt(nearestDistanceSquared); - double channelRadius = nearestReach.width() * 0.5; + double localWidth = nearestReach.widthAt(nearestAlongReach); + double channelRadius = localWidth * 0.5; RiverSection section = section(nearestReach, distance, channelRadius); double carveWeight = carveWeight(distance, channelRadius, nearestReach.bankWidth()); return new RiverSample( @@ -208,7 +222,7 @@ public final class RiverTile { carveWeight, nearestReach.flow(), nearestReach.order(), - nearestReach.width(), + localWidth, nearestReach.bankWidth(), nearestReach.depth(), nearestReach.terminal(), @@ -302,190 +316,252 @@ public final class RiverTile { return 1.0 - smooth; } - private static ClosestPoint closestPoint(RiverPolyline polyline, double x, double z) { + private static ClosestPoint closestCoveringPoint( + RiverReach reach, + double x, + double z, + double additionalRadius + ) { + RiverPolyline polyline = reach.polyline(); + if (polyline.length() == 0D) { + double distanceSquared = squared(x - polyline.x(0)) + squared(z - polyline.z(0)); + double radius = reach.widthAt(0D) * 0.5D + reach.bankWidth() + additionalRadius; + return distanceSquared <= radius * radius ? new ClosestPoint(distanceSquared, 0D) : null; + } double nearest = Double.POSITIVE_INFINITY; double nearestAlong = 0.0; for (int point = 0; point < polyline.size() - 1; point++) { - SegmentPoint segmentPoint = segmentPoint( - polyline.x(point), - polyline.z(point), - polyline.x(point + 1), - polyline.z(point + 1), - x, - z - ); - if (segmentPoint.distanceSquared() < nearest) { - nearest = segmentPoint.distanceSquared(); - double segmentLength = polyline.cumulativeLength(point + 1) - polyline.cumulativeLength(point); - double alongLength = polyline.cumulativeLength(point) + segmentLength * segmentPoint.t(); - nearestAlong = polyline.length() == 0.0 ? 0.0 : alongLength / polyline.length(); + double segmentStartAlong = polyline.cumulativeLength(point) / polyline.length(); + double segmentEndAlong = polyline.cumulativeLength(point + 1) / polyline.length(); + double segmentAlongSpan = segmentEndAlong - segmentStartAlong; + if (segmentAlongSpan == 0D) { + continue; + } + double deltaX = polyline.x(point + 1) - polyline.x(point); + double deltaZ = polyline.z(point + 1) - polyline.z(point); + for (int profileIndex = 0; profileIndex < reach.widthProfile().size() - 1; profileIndex++) { + double profileStart = reach.widthProfile().position(profileIndex); + double profileEnd = reach.widthProfile().position(profileIndex + 1); + double overlapStart = StrictMath.max(segmentStartAlong, profileStart); + double overlapEnd = StrictMath.min(segmentEndAlong, profileEnd); + if (overlapStart > overlapEnd) { + continue; + } + double intervalStart = (overlapStart - segmentStartAlong) / segmentAlongSpan; + double intervalEnd = (overlapEnd - segmentStartAlong) / segmentAlongSpan; + double widthSlope = (reach.widthProfile().width(profileIndex + 1) + - reach.widthProfile().width(profileIndex)) / (profileEnd - profileStart); + double radiusBase = (reach.widthProfile().width(profileIndex) + + widthSlope * (segmentStartAlong - profileStart)) * 0.5D + + reach.bankWidth() + + additionalRadius; + double radiusSlope = widthSlope * segmentAlongSpan * 0.5D; + ClosestPoint candidate = coveringPoint( + intervalStart, + intervalEnd, + deltaX, + polyline.x(point) - x, + deltaZ, + polyline.z(point) - z, + radiusSlope, + radiusBase, + segmentStartAlong, + segmentAlongSpan + ); + if (candidate != null && candidate.distanceSquared() < nearest) { + nearest = candidate.distanceSquared(); + nearestAlong = candidate.alongReach(); + } } } - return new ClosestPoint(nearest, nearestAlong); + return Double.isFinite(nearest) ? new ClosestPoint(nearest, nearestAlong) : null; } - private static ClosestPoint closestPoint( - RiverPolyline polyline, + private static ClosestPoint closestCoveringPoint( + RiverReach reach, double minimumX, double minimumZ, double maximumX, double maximumZ ) { - double nearest = Double.POSITIVE_INFINITY; - double nearestAlong = 0.0; - for (int point = 0; point < polyline.size() - 1; point++) { - SegmentPoint segmentPoint = segmentRectanglePoint( - polyline.x(point), - polyline.z(point), - polyline.x(point + 1), - polyline.z(point + 1), + RiverPolyline polyline = reach.polyline(); + if (polyline.length() == 0D) { + double distanceSquared = pointRectangleDistanceSquared( + polyline.x(0), + polyline.z(0), minimumX, minimumZ, maximumX, maximumZ ); - if (segmentPoint.distanceSquared() < nearest) { - nearest = segmentPoint.distanceSquared(); - double segmentLength = polyline.cumulativeLength(point + 1) - polyline.cumulativeLength(point); - double alongLength = polyline.cumulativeLength(point) + segmentLength * segmentPoint.t(); - nearestAlong = polyline.length() == 0.0 ? 0.0 : alongLength / polyline.length(); + double radius = reach.widthAt(0D) * 0.5D + reach.bankWidth(); + return distanceSquared <= radius * radius ? new ClosestPoint(distanceSquared, 0D) : null; + } + double nearest = Double.POSITIVE_INFINITY; + double nearestAlong = 0.0; + for (int point = 0; point < polyline.size() - 1; point++) { + double segmentStartAlong = polyline.cumulativeLength(point) / polyline.length(); + double segmentEndAlong = polyline.cumulativeLength(point + 1) / polyline.length(); + double segmentAlongSpan = segmentEndAlong - segmentStartAlong; + if (segmentAlongSpan == 0D) { + continue; + } + double startX = polyline.x(point); + double startZ = polyline.z(point); + double deltaX = polyline.x(point + 1) - startX; + double deltaZ = polyline.z(point + 1) - startZ; + for (int profileIndex = 0; profileIndex < reach.widthProfile().size() - 1; profileIndex++) { + double profileStart = reach.widthProfile().position(profileIndex); + double profileEnd = reach.widthProfile().position(profileIndex + 1); + double overlapStart = StrictMath.max(segmentStartAlong, profileStart); + double overlapEnd = StrictMath.min(segmentEndAlong, profileEnd); + if (overlapStart > overlapEnd) { + continue; + } + double intervalStart = (overlapStart - segmentStartAlong) / segmentAlongSpan; + double intervalEnd = (overlapEnd - segmentStartAlong) / segmentAlongSpan; + double widthSlope = (reach.widthProfile().width(profileIndex + 1) + - reach.widthProfile().width(profileIndex)) / (profileEnd - profileStart); + double radiusBase = (reach.widthProfile().width(profileIndex) + + widthSlope * (segmentStartAlong - profileStart)) * 0.5D + reach.bankWidth(); + double radiusSlope = widthSlope * segmentAlongSpan * 0.5D; + double cursor = intervalStart; + do { + double next = intervalEnd; + next = nextCrossing(startX, deltaX, minimumX, cursor, next); + next = nextCrossing(startX, deltaX, maximumX, cursor, next); + next = nextCrossing(startZ, deltaZ, minimumZ, cursor, next); + next = nextCrossing(startZ, deltaZ, maximumZ, cursor, next); + double middle = (cursor + next) * 0.5D; + double middleX = startX + deltaX * middle; + double middleZ = startZ + deltaZ * middle; + double distanceSlopeX = middleX < minimumX || middleX > maximumX ? deltaX : 0D; + double distanceBaseX = middleX < minimumX + ? startX - minimumX + : middleX > maximumX ? startX - maximumX : 0D; + double distanceSlopeZ = middleZ < minimumZ || middleZ > maximumZ ? deltaZ : 0D; + double distanceBaseZ = middleZ < minimumZ + ? startZ - minimumZ + : middleZ > maximumZ ? startZ - maximumZ : 0D; + ClosestPoint candidate = coveringPoint( + cursor, + next, + distanceSlopeX, + distanceBaseX, + distanceSlopeZ, + distanceBaseZ, + radiusSlope, + radiusBase, + segmentStartAlong, + segmentAlongSpan + ); + if (candidate != null && candidate.distanceSquared() < nearest) { + nearest = candidate.distanceSquared(); + nearestAlong = candidate.alongReach(); + } + cursor = next; + } while (cursor < intervalEnd); } } - return new ClosestPoint(nearest, nearestAlong); + return Double.isFinite(nearest) ? new ClosestPoint(nearest, nearestAlong) : null; } - private static SegmentPoint segmentPoint( - double startX, - double startZ, - double endX, - double endZ, - double x, - double z + private static ClosestPoint coveringPoint( + double intervalStart, + double intervalEnd, + double distanceSlopeX, + double distanceBaseX, + double distanceSlopeZ, + double distanceBaseZ, + double radiusSlope, + double radiusBase, + double segmentStartAlong, + double segmentAlongSpan ) { - double deltaX = endX - startX; - double deltaZ = endZ - startZ; - double lengthSquared = deltaX * deltaX + deltaZ * deltaZ; - if (lengthSquared == 0.0) { - return new SegmentPoint(squared(x - startX) + squared(z - startZ), 0.0); + double distanceQuadratic = squared(distanceSlopeX) + squared(distanceSlopeZ); + double distanceLinear = 2D * (distanceSlopeX * distanceBaseX + distanceSlopeZ * distanceBaseZ); + double distanceConstant = squared(distanceBaseX) + squared(distanceBaseZ); + double coverageQuadratic = distanceQuadratic - squared(radiusSlope); + double coverageLinear = distanceLinear - 2D * radiusSlope * radiusBase; + double coverageConstant = distanceConstant - squared(radiusBase); + double distancePosition = distanceQuadratic == 0D + ? intervalStart + : clamp(-distanceLinear / (2D * distanceQuadratic), intervalStart, intervalEnd); + if (quadraticValue(coverageQuadratic, coverageLinear, coverageConstant, distancePosition) <= 0D) { + return new ClosestPoint( + StrictMath.max(0D, quadraticValue( + distanceQuadratic, + distanceLinear, + distanceConstant, + distancePosition + )), + segmentStartAlong + segmentAlongSpan * distancePosition + ); } - double projection = ((x - startX) * deltaX + (z - startZ) * deltaZ) / lengthSquared; - double t = StrictMath.max(0.0, StrictMath.min(1.0, projection)); - double nearestX = startX + deltaX * t; - double nearestZ = startZ + deltaZ * t; - return new SegmentPoint(squared(x - nearestX) + squared(z - nearestZ), t); + double coveragePosition = intervalStart; + double minimumCoverage = quadraticValue( + coverageQuadratic, + coverageLinear, + coverageConstant, + coveragePosition + ); + double endCoverage = quadraticValue(coverageQuadratic, coverageLinear, coverageConstant, intervalEnd); + if (endCoverage < minimumCoverage) { + minimumCoverage = endCoverage; + coveragePosition = intervalEnd; + } + if (coverageQuadratic > 0D) { + double vertex = clamp(-coverageLinear / (2D * coverageQuadratic), intervalStart, intervalEnd); + double vertexCoverage = quadraticValue(coverageQuadratic, coverageLinear, coverageConstant, vertex); + if (vertexCoverage < minimumCoverage) { + minimumCoverage = vertexCoverage; + coveragePosition = vertex; + } + } + if (minimumCoverage > 0D) { + return null; + } + double uncovered = distancePosition; + double covered = coveragePosition; + for (int iteration = 0; iteration < 40; iteration++) { + double middle = (uncovered + covered) * 0.5D; + if (quadraticValue(coverageQuadratic, coverageLinear, coverageConstant, middle) <= 0D) { + covered = middle; + } else { + uncovered = middle; + } + } + return new ClosestPoint( + StrictMath.max(0D, quadraticValue( + distanceQuadratic, + distanceLinear, + distanceConstant, + covered + )), + segmentStartAlong + segmentAlongSpan * covered + ); } - private static SegmentPoint segmentRectanglePoint( - double startX, - double startZ, - double endX, - double endZ, - double minimumX, - double minimumZ, - double maximumX, - double maximumZ + private static double nextCrossing( + double start, + double delta, + double boundary, + double cursor, + double currentNext ) { - double intersectionPosition = segmentRectangleIntersectionPosition( - startX, - startZ, - endX, - endZ, - minimumX, - minimumZ, - maximumX, - maximumZ - ); - if (!Double.isNaN(intersectionPosition)) { - return new SegmentPoint(0.0, intersectionPosition); + if (delta == 0D) { + return currentNext; } - - double nearestDistanceSquared = pointRectangleDistanceSquared( - startX, - startZ, - minimumX, - minimumZ, - maximumX, - maximumZ - ); - double nearestPosition = 0.0; - double endDistanceSquared = pointRectangleDistanceSquared( - endX, - endZ, - minimumX, - minimumZ, - maximumX, - maximumZ - ); - if (endDistanceSquared < nearestDistanceSquared) { - nearestDistanceSquared = endDistanceSquared; - nearestPosition = 1.0; - } - - SegmentPoint corner = segmentPoint(startX, startZ, endX, endZ, minimumX, minimumZ); - if (corner.distanceSquared() < nearestDistanceSquared) { - nearestDistanceSquared = corner.distanceSquared(); - nearestPosition = corner.t(); - } - corner = segmentPoint(startX, startZ, endX, endZ, minimumX, maximumZ); - if (corner.distanceSquared() < nearestDistanceSquared) { - nearestDistanceSquared = corner.distanceSquared(); - nearestPosition = corner.t(); - } - corner = segmentPoint(startX, startZ, endX, endZ, maximumX, minimumZ); - if (corner.distanceSquared() < nearestDistanceSquared) { - nearestDistanceSquared = corner.distanceSquared(); - nearestPosition = corner.t(); - } - corner = segmentPoint(startX, startZ, endX, endZ, maximumX, maximumZ); - if (corner.distanceSquared() < nearestDistanceSquared) { - nearestDistanceSquared = corner.distanceSquared(); - nearestPosition = corner.t(); - } - return new SegmentPoint(nearestDistanceSquared, nearestPosition); + double crossing = (boundary - start) / delta; + return crossing > cursor && crossing < currentNext ? crossing : currentNext; } - private static double segmentRectangleIntersectionPosition( - double startX, - double startZ, - double endX, - double endZ, - double minimumX, - double minimumZ, - double maximumX, - double maximumZ - ) { - double minimumPosition = 0.0; - double maximumPosition = 1.0; - double deltaX = endX - startX; - if (deltaX == 0.0) { - if (startX < minimumX || startX > maximumX) { - return Double.NaN; - } - } else { - double first = (minimumX - startX) / deltaX; - double second = (maximumX - startX) / deltaX; - minimumPosition = StrictMath.max(minimumPosition, StrictMath.min(first, second)); - maximumPosition = StrictMath.min(maximumPosition, StrictMath.max(first, second)); - if (minimumPosition > maximumPosition) { - return Double.NaN; - } - } + private static double quadraticValue(double quadratic, double linear, double constant, double value) { + return (quadratic * value + linear) * value + constant; + } - double deltaZ = endZ - startZ; - if (deltaZ == 0.0) { - if (startZ < minimumZ || startZ > maximumZ) { - return Double.NaN; - } - } else { - double first = (minimumZ - startZ) / deltaZ; - double second = (maximumZ - startZ) / deltaZ; - minimumPosition = StrictMath.max(minimumPosition, StrictMath.min(first, second)); - maximumPosition = StrictMath.min(maximumPosition, StrictMath.max(first, second)); - if (minimumPosition > maximumPosition) { - return Double.NaN; - } - } - return minimumPosition; + private static double clamp(double value, double minimum, double maximum) { + return StrictMath.max(minimum, StrictMath.min(maximum, value)); } private static double pointRectangleDistanceSquared( @@ -616,6 +692,4 @@ public final class RiverTile { private record ClosestPoint(double distanceSquared, double alongReach) { } - private record SegmentPoint(double distanceSquared, double t) { - } } diff --git a/core/src/main/java/art/arcane/iris/engine/river/RiverTopologyComplexity.java b/core/src/main/java/art/arcane/iris/engine/river/RiverTopologyComplexity.java index f6f786bf1..8dc1fd19b 100644 --- a/core/src/main/java/art/arcane/iris/engine/river/RiverTopologyComplexity.java +++ b/core/src/main/java/art/arcane/iris/engine/river/RiverTopologyComplexity.java @@ -7,6 +7,7 @@ public final class RiverTopologyComplexity { public static final long MAXIMUM_SOURCE_WINDOW_CELLS = 65_536L; public static final long MAXIMUM_ROUTE_SCAN_STEPS = 65_536L; public static final long MAXIMUM_BUCKET_WRITES_PER_REACH = 1_048_576L; + public static final long MAXIMUM_TUNNEL_SAMPLE_COLUMNS = 65_536L; private static final int SPATIAL_BUCKET_SIZE = 64; private RiverTopologyComplexity() { @@ -83,6 +84,70 @@ public final class RiverTopologyComplexity { } } + public static int tunnelHalo( + double maximumChannelWidth, + double maximumTunnelWidthMultiplier, + double tunnelMouthBlend + ) { + if (!Double.isFinite(maximumChannelWidth) || maximumChannelWidth <= 0D + || !Double.isFinite(maximumTunnelWidthMultiplier) || maximumTunnelWidthMultiplier < 1D + || !Double.isFinite(tunnelMouthBlend) || tunnelMouthBlend < 0D) { + throw new IllegalArgumentException("River tunnel dimensions must be finite and valid"); + } + return Math.max( + 1, + (int) StrictMath.ceil( + maximumChannelWidth * 0.5D * maximumTunnelWidthMultiplier + tunnelMouthBlend + ) + 1 + ); + } + + public static long tunnelSampleColumns( + double maximumChannelWidth, + double maximumTunnelWidthMultiplier, + double tunnelMouthBlend + ) { + long axis = 16L + 2L * tunnelHalo( + maximumChannelWidth, + maximumTunnelWidthMultiplier, + tunnelMouthBlend + ); + return saturatedMultiply(axis, axis); + } + + public static String tunnelPlanViolation( + double maximumChannelWidth, + double maximumTunnelWidthMultiplier, + double tunnelMouthBlend + ) { + long columns = tunnelSampleColumns( + maximumChannelWidth, + maximumTunnelWidthMultiplier, + tunnelMouthBlend + ); + if (columns <= MAXIMUM_TUNNEL_SAMPLE_COLUMNS) { + return null; + } + return "River tunnel planning may sample " + columns + + " columns per generated chunk, above the safe limit of " + MAXIMUM_TUNNEL_SAMPLE_COLUMNS + + "; reduce maxChannelWidth, tunnelWidthMultiplier.max, or tunnelMouthBlend."; + } + + public static void requireSafeTunnelPlan( + double maximumChannelWidth, + double maximumTunnelWidthMultiplier, + double tunnelMouthBlend + ) { + String violation = tunnelPlanViolation( + maximumChannelWidth, + maximumTunnelWidthMultiplier, + tunnelMouthBlend + ); + if (violation != null) { + throw new IllegalArgumentException(violation); + } + } + private static long ceilToLong(double value) { if (!Double.isFinite(value) || value >= Long.MAX_VALUE) { return Long.MAX_VALUE; diff --git a/core/src/main/java/art/arcane/iris/engine/river/RiverWidthProfile.java b/core/src/main/java/art/arcane/iris/engine/river/RiverWidthProfile.java new file mode 100644 index 000000000..93cb2baa2 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/engine/river/RiverWidthProfile.java @@ -0,0 +1,89 @@ +package art.arcane.iris.engine.river; + +import java.util.Arrays; + +public final class RiverWidthProfile { + private final double[] positions; + private final double[] widths; + private final double maximum; + + public RiverWidthProfile(double[] positions, double[] widths) { + if (positions == null || widths == null || positions.length < 2 || positions.length != widths.length) { + throw new IllegalArgumentException("River width profiles require matching position and width samples"); + } + this.positions = positions.clone(); + this.widths = widths.clone(); + double resolvedMaximum = 0D; + for (int index = 0; index < this.positions.length; index++) { + double position = this.positions[index]; + double width = this.widths[index]; + if (!Double.isFinite(position) || position < 0D || position > 1D + || (index > 0 && position <= this.positions[index - 1])) { + throw new IllegalArgumentException("River width profile positions must increase from zero to one"); + } + if (!Double.isFinite(width) || width <= 0D) { + throw new IllegalArgumentException("River width profile widths must be finite and positive"); + } + resolvedMaximum = Math.max(resolvedMaximum, width); + } + if (this.positions[0] != 0D || this.positions[this.positions.length - 1] != 1D) { + throw new IllegalArgumentException("River width profile positions must include zero and one"); + } + maximum = resolvedMaximum; + } + + public static RiverWidthProfile constant(double width) { + return new RiverWidthProfile(new double[]{0D, 1D}, new double[]{width, width}); + } + + public double sample(double alongReach) { + double position = Math.max(0D, Math.min(1D, alongReach)); + int index = Arrays.binarySearch(positions, position); + if (index >= 0) { + return widths[index]; + } + int upper = -index - 1; + if (upper <= 0) { + return widths[0]; + } + if (upper >= positions.length) { + return widths[widths.length - 1]; + } + int lower = upper - 1; + double range = positions[upper] - positions[lower]; + double interpolation = range <= 0D ? 0D : (position - positions[lower]) / range; + return widths[lower] + (widths[upper] - widths[lower]) * interpolation; + } + + public double maximum() { + return maximum; + } + + public int size() { + return widths.length; + } + + public double position(int index) { + return positions[index]; + } + + public double width(int index) { + return widths[index]; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof RiverWidthProfile profile)) { + return false; + } + return Arrays.equals(positions, profile.positions) && Arrays.equals(widths, profile.widths); + } + + @Override + public int hashCode() { + return 31 * Arrays.hashCode(positions) + Arrays.hashCode(widths); + } +} diff --git a/core/src/main/java/art/arcane/iris/engine/river/cave/RiverCaveContainmentPlanner.java b/core/src/main/java/art/arcane/iris/engine/river/cave/RiverCaveContainmentPlanner.java index 7ee1065d9..d4356b84f 100644 --- a/core/src/main/java/art/arcane/iris/engine/river/cave/RiverCaveContainmentPlanner.java +++ b/core/src/main/java/art/arcane/iris/engine/river/cave/RiverCaveContainmentPlanner.java @@ -656,6 +656,9 @@ public final class RiverCaveContainmentPlanner { return boundsRejection; } CaveVoxel voxel = voxelAt(view, position); + if (isGeneratedInletCarve(view, source, settings, position, voxel)) { + continue; + } RiverCaveRejection hazard = rejectionForHazard(voxel, settings); if (hazard != RiverCaveRejection.NONE) { return hazard; @@ -680,7 +683,8 @@ public final class RiverCaveContainmentPlanner { if (carve.contains(neighbor)) { continue; } - if (isInletOpening(source, neighbor)) { + if (isInletOpening(source, neighbor) + || isGeneratedInletOpening(view, source, settings, neighbor)) { continue; } if (!view.isInWorld(neighbor)) { @@ -726,7 +730,8 @@ public final class RiverCaveContainmentPlanner { RiverCaveAction action; if (position.y() <= source.waterHeadY()) { action = RiverCaveAction.WET_SOURCE; - } else if (source.mode() == RiverCaveMode.WATERFALL_POOL) { + } else if (source.mode() == RiverCaveMode.WATERFALL_POOL + || source.mode() == RiverCaveMode.GENERATED_GROTTO) { action = RiverCaveAction.FALLING_WATER; } else { action = RiverCaveAction.DRY_AIR; @@ -763,6 +768,40 @@ public final class RiverCaveContainmentPlanner { return position.equals(source.entry().offset(0, 1, 0)); } + private boolean isGeneratedInletCarve( + CaveVoxelView view, + RiverCaveSource source, + RiverCavePlannerSettings settings, + CavePosition position, + CaveVoxel voxel + ) { + if (voxel != CaveVoxel.CAVE_AIR && voxel != CaveVoxel.COMPATIBLE_FLUID) { + return false; + } + int extent = Math.max(0, settings.throatRadius() - 1); + long deltaX = (long) position.x() - source.entry().x(); + long deltaZ = (long) position.z() - source.entry().z(); + return position.y() >= source.entry().y() - extent + && position.y() <= source.entry().y() + && deltaX * deltaX + deltaZ * deltaZ <= (long) extent * extent + && view.isOpenToSurface(position); + } + + private boolean isGeneratedInletOpening( + CaveVoxelView view, + RiverCaveSource source, + RiverCavePlannerSettings settings, + CavePosition position + ) { + int radius = Math.max(1, settings.throatRadius()); + long deltaX = (long) position.x() - source.entry().x(); + long deltaZ = (long) position.z() - source.entry().z(); + return position.y() >= source.entry().y() + && position.y() <= source.entry().y() + 1 + && deltaX * deltaX + deltaZ * deltaZ < (long) radius * radius + && view.isOpenToSurface(position); + } + private RiverCaveRejection validateSource(RiverCaveSource source) { if (source.entry().y() < source.waterHeadY()) { return RiverCaveRejection.INVALID_SOURCE; diff --git a/core/src/main/java/art/arcane/iris/engine/river/runtime/IrisRiverRuntime.java b/core/src/main/java/art/arcane/iris/engine/river/runtime/IrisRiverRuntime.java index d72f591d9..879d4546e 100644 --- a/core/src/main/java/art/arcane/iris/engine/river/runtime/IrisRiverRuntime.java +++ b/core/src/main/java/art/arcane/iris/engine/river/runtime/IrisRiverRuntime.java @@ -34,6 +34,7 @@ import art.arcane.iris.engine.river.RiverTerrainSampler; import art.arcane.iris.engine.river.RiverTerminalPolicy; import art.arcane.iris.engine.river.RiverTile; import art.arcane.iris.engine.river.RiverTileCache; +import art.arcane.iris.engine.river.RiverTopologyComplexity; import art.arcane.iris.util.project.interpolation.NoiseBounds; import art.arcane.iris.util.project.noise.CNG; import art.arcane.iris.util.project.stream.ProceduralStream; @@ -65,6 +66,9 @@ public final class IrisRiverRuntime implements AutoCloseable { private static final long BIOME_NOISE_SALT = 0x2FFD72DBD01ADFB7L; private static final long CAVE_ENTRY_NOISE_SALT = 0xB8E1AFED6A267E96L; private static final long CAVE_ENTRY_GATE_SALT = 0xBA7C9045F12C7F99L; + private static final long TUNNEL_FLOOR_NOISE_SALT = 0x8CB92BA72F3D8DD7L; + private static final long TUNNEL_ROOF_NOISE_SALT = 0xDB4F0B9175AE2165L; + private static final long TUNNEL_WIDTH_NOISE_SALT = 0xC6EF372FE94F82BEL; private static final long FLOODED_CAVE_BIOME_SALT = 0x24A19947B3916CF7L; private static final long TERMINAL_CAVE_ANCHOR_SALT = 0x9E3779B97F4A7C15L; private static final int TILE_CACHE_SIZE = 32; @@ -86,6 +90,7 @@ public final class IrisRiverRuntime implements AutoCloseable { private final ProceduralStream region; private final IrisRiverTerrain terrain; private final IrisRiverWater water; + private final IrisRiverCaves caves; private final CNG sourceNoise; private final CNG continuationNoise; private final CNG incisionNoise; @@ -97,6 +102,9 @@ public final class IrisRiverRuntime implements AutoCloseable { private final CNG bedNoise; private final CNG biomeNoise; private final CNG caveEntryNoise; + private final CNG tunnelFloorNoise; + private final CNG tunnelRoofNoise; + private final CNG tunnelWidthNoise; private final art.arcane.iris.engine.river.RiverNetwork network; private final RuntimeTerrainSampler terrainSampler; private final RiverTileCache tileCache; @@ -121,7 +129,13 @@ public final class IrisRiverRuntime implements AutoCloseable { naturalBiome = context.naturalBiome(); region = context.region(); terrain = Objects.requireNonNull(configuration.getTerrain()); + RiverTopologyComplexity.requireSafeTunnelPlan( + terrain.getMaxChannelWidth(), + maximumTunnelWidthMultiplier(terrain), + terrain.getTunnelMouthBlend() + ); water = Objects.requireNonNull(configuration.getWater()); + caves = configuration.getCaves() == null ? new IrisRiverCaves() : configuration.getCaves(); IrisRiverTopology topology = Objects.requireNonNull(configuration.getTopology()); sourceNoise = noise(topology.getSource(), SOURCE_NOISE_SALT); continuationNoise = noise(topology.getContinuation(), CONTINUATION_NOISE_SALT); @@ -133,7 +147,10 @@ public final class IrisRiverRuntime implements AutoCloseable { meanderNoise = noise(terrain.getMeanderStyle(), MEANDER_NOISE_SALT); bedNoise = noise(terrain.getBedRoughnessStyle(), BED_NOISE_SALT); biomeNoise = noise(configuration.getBiomes().getSelectionStyle(), BIOME_NOISE_SALT); - caveEntryNoise = noise(caveSettings().getEntry(), CAVE_ENTRY_NOISE_SALT); + caveEntryNoise = noise(caves.getEntry(), CAVE_ENTRY_NOISE_SALT); + tunnelFloorNoise = noise(terrain.getTunnelFloorStyle(), TUNNEL_FLOOR_NOISE_SALT); + tunnelRoofNoise = noise(terrain.getTunnelRoofStyle(), TUNNEL_ROOF_NOISE_SALT); + tunnelWidthNoise = noise(terrain.getTunnelWidthMultiplier(), TUNNEL_WIDTH_NOISE_SALT); settingsCache = new ConcurrentHashMap<>(); biomePoolCache = new ConcurrentHashMap<>(); RiverNetworkOptions options = options(topology, terrain); @@ -190,34 +207,98 @@ public final class IrisRiverRuntime implements AutoCloseable { } public IrisRiverTunnelSample sampleTunnel(double x, double z) { - ResolvedRiverColumn column = resolveColumn(x, z); - if (column == null - || !column.subterranean() - || column.river().state() != RiverRouteState.WET - || column.river().section() != RiverSection.CHANNEL) { + double mouthBlend = terrain.getTunnelMouthBlend(); + double maximumMultiplier = maximumTunnelWidthMultiplier(terrain); + double maximumExtraRadius = terrain.getMaxChannelWidth() * 0.5D * (maximumMultiplier - 1D) + + mouthBlend; + RiverTile tile = tileAt(x, z); + RiverSample river = tile.sampleExpanded(x, z, maximumExtraRadius); + if (!river.present() || river.state() != RiverRouteState.WET) { + return null; + } + double widthMultiplier = styled( + terrain.getTunnelWidthMultiplier(), + tunnelWidthNoise, + (int) StrictMath.round(x), + (int) StrictMath.round(z), + 1D + ); + double channelRadius = river.width() * 0.5D * widthMultiplier; + if (river.distance() > channelRadius + mouthBlend) { + return null; + } + ResolvedRiverColumn column = resolveColumn(x, z, tile, river); + if (!column.subterranean()) { + return null; + } + if (isTunnelMouth(column, mouthBlend)) { + channelRadius += mouthBlend; + } + if (column.river().distance() > channelRadius) { return null; } - double channelRadius = column.river().width() * 0.5D; double normalizedDistance = channelRadius <= 0D ? 1D : clamp01(column.river().distance() / channelRadius); - double roofProfile = StrictMath.sqrt(Math.max(0D, 1D - normalizedDistance * normalizedDistance)); - int bedY = (int) Math.round(column.bedHeight()); + double profile = StrictMath.sqrt(Math.max(0D, 1D - normalizedDistance * normalizedDistance)); int waterHeadY = (int) Math.round(column.waterSurfaceY()); - int ceilingY = waterHeadY + (int) StrictMath.ceil(caveSettings().getDryHeadroom() * roofProfile); - if (bedY >= waterHeadY) { - return null; - } + double floorOffset = tunnelFloorNoise.fitDouble( + -terrain.getTunnelFloorVariation(), + terrain.getTunnelFloorVariation(), + x, + z + ); + int bedY = shapedTunnelBedY(waterHeadY, column.bedHeight(), profile, floorOffset); + double roofOffset = tunnelRoofNoise.fitDouble( + -terrain.getTunnelRoofVariation(), + terrain.getTunnelRoofVariation(), + x, + z + ); + int ceilingY = shapedTunnelCeilingY( + waterHeadY, caves.getDryHeadroom(), profile, roofOffset); return new IrisRiverTunnelSample(column.river(), bedY, waterHeadY, ceilingY); } + static int shapedTunnelBedY( + int waterHeadY, + double baseBedY, + double profile, + double floorOffset + ) { + double baseDepth = Math.max(1D, waterHeadY - baseBedY); + double shapedDepth = Math.max(1D, (baseDepth + floorOffset) * clamp01(profile)); + return waterHeadY - Math.max(1, (int) StrictMath.ceil(shapedDepth)); + } + + static int shapedTunnelCeilingY( + int waterHeadY, + double dryHeadroom, + double profile, + double roofOffset + ) { + double shapedHeadroom = Math.max( + 0D, + (Math.max(0D, dryHeadroom) + roofOffset) * clamp01(profile) + ); + return waterHeadY + (int) StrictMath.ceil(shapedHeadroom); + } + private ResolvedRiverColumn resolveColumn(double x, double z) { - double sampledNaturalHeight = naturalHeight.get(x, z); + return resolveColumn(x, z, 0D); + } + + private ResolvedRiverColumn resolveColumn(double x, double z, double additionalRadius) { RiverTile tile = tileAt(x, z); - RiverSample river = tile.sample(x, z); + RiverSample river = tile.sampleExpanded(x, z, additionalRadius); if (!river.present()) { return null; } + return resolveColumn(x, z, tile, river); + } + + private ResolvedRiverColumn resolveColumn(double x, double z, RiverTile tile, RiverSample river) { + double sampledNaturalHeight = naturalHeight.get(x, z); RiverReach reach = tile.reach(river.reachId()); IrisRegion sampledRegion = region.get(x, z); IrisBiome sampledBiome = naturalBiome.get(x, z); @@ -229,6 +310,12 @@ public final class IrisRiverRuntime implements AutoCloseable { ? waterSurfaceY - river.depth() + bedRoughness(x, z) : sampledNaturalHeight - river.depth() + bedRoughness(x, z); double maximumIncision = Math.max(0D, terrain.getMaxIncision() * settings.maxIncisionMultiplier()); + double cappedSurface = incisedHeight( + sampledNaturalHeight, + bedHeight, + 1D, + maximumIncision + ); return new ResolvedRiverColumn( river, reach, @@ -236,14 +323,27 @@ public final class IrisRiverRuntime implements AutoCloseable { waterSurfaceY, bedHeight, maximumIncision, - isSubterraneanSegment(reach, river.alongReach()) + boreMantleActive + && river.state() == RiverRouteState.WET + && Math.round(cappedSurface) >= Math.round(waterSurfaceY) ); } - private boolean isSubterraneanSegment(RiverReach reach, double alongReach) { - if (!boreMantleActive || reach.state() != RiverRouteState.WET) { + private boolean isTunnelMouth(ResolvedRiverColumn column, double mouthBlend) { + if (mouthBlend <= 0D) { return false; } + double length = column.reach().polyline().length(); + if (length <= 0D) { + return false; + } + double offset = mouthBlend / length; + double alongReach = column.river().alongReach(); + return !isCenterlineSubterranean(column.reach(), clamp01(alongReach - offset)) + || !isCenterlineSubterranean(column.reach(), clamp01(alongReach + offset)); + } + + private boolean isCenterlineSubterranean(RiverReach reach, double alongReach) { CenterlinePosition center = centerlinePosition(reach, alongReach); IrisRegion sampledRegion = region.get(center.x(), center.z()); IrisBiome sampledBiome = naturalBiome.get(center.x(), center.z()); @@ -363,14 +463,25 @@ public final class IrisRiverRuntime implements AutoCloseable { } public IrisRiverCaves caveSettings() { - IrisRiverCaves caves = configuration.getCaves(); - return caves == null ? new IrisRiverCaves() : caves; + return caves; } public double maximumChannelWidth() { return terrain.getMaxChannelWidth(); } + public double maximumTunnelWidthMultiplier() { + return maximumTunnelWidthMultiplier(terrain); + } + + public double tunnelMouthBlend() { + return terrain.getTunnelMouthBlend(); + } + + public int maximumTunnelHeadroom() { + return caves.getDryHeadroom() + (int) StrictMath.ceil(terrain.getTunnelRoofVariation()); + } + public boolean acceptsCaveAnchor(RiverAnchor anchor) { Objects.requireNonNull(anchor); IrisRiverCaves caves = caveSettings(); @@ -408,6 +519,9 @@ public final class IrisRiverRuntime implements AutoCloseable { if (!caveEntryEligible(caves, stableId, position.x(), position.z())) { continue; } + if (!isCaveAnchorSourceable(position.x(), position.z())) { + continue; + } if (index == anchor.index()) { return stableId == anchor.stableId() && accepted < caves.getMaximumPerReach(); } @@ -419,6 +533,17 @@ public final class IrisRiverRuntime implements AutoCloseable { return false; } + private boolean isCaveAnchorSourceable(double x, double z) { + IrisRiverSurfaceSample surface = sample(x, z); + if (surface.river().present() + && surface.river().state() == RiverRouteState.WET + && surface.river().section() == RiverSection.CHANNEL + && surface.surfaceFluid()) { + return true; + } + return sampleTunnel(x, z) != null; + } + public boolean isTerminalCaveAnchor(RiverAnchor anchor) { TerminalCaveAnchor terminal = terminalCaveAnchor(anchor); return terminal != null && anchor.stableId() == terminal.stableId(); @@ -490,6 +615,8 @@ public final class IrisRiverRuntime implements AutoCloseable { .minimumSourcesPerTile(topology.getMinimumSourcesPerTile()) .downstreamCandidateLimit(Math.max(1, Math.min(8, topology.getSinkSearchReaches() + 1))) .routingBasinCells(topology.getRoutingBasinCells()) + .routingDeviationScaleCells(topology.getRoutingDeviationScaleCells()) + .routingDeviationStrengthCells(topology.getRoutingDeviationStrengthCells()) .routingPlateauHeight(topology.getRoutingPlateauHeight()) .hydraulicBaseHeight(fluidHeight) .requireOcean(topology.isRequireOcean()) @@ -498,7 +625,10 @@ public final class IrisRiverRuntime implements AutoCloseable { .dryChannelChance(dryChance) .terrainHeightWeight(topology.getTerrainHeightWeight()) .routingNoiseWeight(0D) - .flowAlignmentWeight(topology.getRoutingNoiseWeight()) + .flowAlignmentWeight(topology.getFlowAlignmentWeight()) + .confluenceWeight(topology.getConfluenceWeight()) + .branchSoftCap(topology.getBranchSoftCap()) + .branchChildShrinkFactor(topology.getBranchChildShrinkFactor()) .oceanAttraction(topology.getOceanAttraction()) .channelWidth(mid(riverTerrain.getChannelWidth(), 12D)) .bankWidth(mid(riverTerrain.getBankWidth(), 8D)) @@ -792,11 +922,29 @@ public final class IrisRiverRuntime implements AutoCloseable { } double minimum = Math.min(range.getMin(), range.getMax()); double maximum = Math.max(range.getMin(), range.getMax()); + if (minimum == maximum) { + return minimum; + } return noise.fitDouble(minimum, maximum, x, z); } private static double maximumReachRadius(IrisRiverTopology topology, IrisRiverTerrain riverTerrain) { - return riverTerrain.getMaxChannelWidth() * 0.5D + riverTerrain.getMaxBankWidth(); + double surfaceRadius = riverTerrain.getMaxChannelWidth() * 0.5D + + riverTerrain.getMaxBankWidth(); + double tunnelRadius = riverTerrain.getMaxChannelWidth() * 0.5D + * maximumTunnelWidthMultiplier(riverTerrain) + + riverTerrain.getTunnelMouthBlend(); + return Math.max(surfaceRadius, tunnelRadius); + } + + private static double maximumTunnelWidthMultiplier(IrisRiverTerrain riverTerrain) { + IrisStyledRange configured = riverTerrain.getTunnelWidthMultiplier(); + if (configured == null + || !Double.isFinite(configured.getMin()) + || !Double.isFinite(configured.getMax())) { + return 1D; + } + return Math.max(1D, Math.max(configured.getMin(), configured.getMax())); } private static double clamp01(double value) { @@ -820,12 +968,14 @@ public final class IrisRiverRuntime implements AutoCloseable { @Override public RiverTerrainNodeSample sampleNode(int blockX, int blockZ) { + boolean oceanIntent = Boolean.TRUE.equals(naturalOcean.get(blockX, blockZ)); boolean naturalHeightRequired = topology.getTerrainHeightWeight() > 0D - || water.getMode() != IrisRiverWaterMode.SEA_LEVEL; + || water.getMode() != IrisRiverWaterMode.SEA_LEVEL + || oceanIntent; double sampledNaturalHeight = naturalHeightRequired ? naturalHeight.get(blockX, blockZ) : fluidHeight; - boolean sampledOcean = Boolean.TRUE.equals(naturalOcean.get(blockX, blockZ)); + boolean sampledOcean = oceanIntent && isSubmergedOutlet(sampledNaturalHeight); IrisRegion sampledRegion = region.get(blockX, blockZ); IrisBiome sampledBiome = biomeRiverOverridesPossible ? naturalBiome.get(blockX, blockZ) : null; EffectiveRiverSettings settings = settingsFor(sampledRegion, sampledBiome); @@ -846,7 +996,7 @@ public final class IrisRiverRuntime implements AutoCloseable { return new RiverTerrainSourceSample( chanceMultiplier, settings.routingPolicy() != IrisRiverRoutingPolicy.BLOCK, - Boolean.TRUE.equals(naturalOcean.get(blockX, blockZ)) + isSubmergedOceanIntent(blockX, blockZ) ); } @@ -857,7 +1007,16 @@ public final class IrisRiverRuntime implements AutoCloseable { @Override public boolean isOcean(int blockX, int blockZ) { - return Boolean.TRUE.equals(naturalOcean.get(blockX, blockZ)); + return isSubmergedOceanIntent(blockX, blockZ); + } + + private boolean isSubmergedOceanIntent(int blockX, int blockZ) { + return Boolean.TRUE.equals(naturalOcean.get(blockX, blockZ)) + && isSubmergedOutlet(naturalHeight.get(blockX, blockZ)); + } + + private boolean isSubmergedOutlet(double sampledNaturalHeight) { + return Math.round(sampledNaturalHeight) < Math.round(fluidHeight); } @Override @@ -999,6 +1158,24 @@ public final class IrisRiverRuntime implements AutoCloseable { * settings.widthMultiplier(); } + @Override + public double channelWidth( + RiverRoutingContext context, + double x, + double z, + double fallback + ) { + EffectiveRiverSettings settings = settingsAt(x, z); + return styled( + terrain.getChannelWidth(), + widthNoise, + (int) StrictMath.round(x), + (int) StrictMath.round(z), + fallback + ) + * settings.widthMultiplier(); + } + @Override public double bankWidth(RiverRoutingContext context, double fallback) { EffectiveRiverSettings settings = settingsAt(context.midpointX(), context.midpointZ()); diff --git a/core/src/main/java/art/arcane/iris/util/common/director/DirectorContextHandler.java b/core/src/main/java/art/arcane/iris/util/common/director/DirectorContextHandler.java index 94dbe65a1..83637371b 100644 --- a/core/src/main/java/art/arcane/iris/util/common/director/DirectorContextHandler.java +++ b/core/src/main/java/art/arcane/iris/util/common/director/DirectorContextHandler.java @@ -17,7 +17,6 @@ public interface DirectorContextHandler extends DirectorContextHandlerType ((DirectorContextHandler) h).getType(), e -> { IrisLogging.reportError(e); - e.printStackTrace(); }); } } diff --git a/core/src/main/java/art/arcane/iris/util/common/misc/SlimJar.java b/core/src/main/java/art/arcane/iris/util/common/misc/SlimJar.java index 00d18bbc5..46a492a9c 100644 --- a/core/src/main/java/art/arcane/iris/util/common/misc/SlimJar.java +++ b/core/src/main/java/art/arcane/iris/util/common/misc/SlimJar.java @@ -1,7 +1,6 @@ package art.arcane.iris.util.common.misc; import art.arcane.iris.platform.bukkit.BukkitPlatform; -import art.arcane.iris.spi.IrisLogging; import art.arcane.iris.util.common.plugin.VolmitPlugin; import io.github.slimjar.app.builder.ApplicationBuilder; import io.github.slimjar.app.builder.SpigotApplicationBuilder; @@ -13,7 +12,6 @@ import org.jetbrains.annotations.Nullable; import java.nio.file.Path; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; -import java.util.logging.Logger; public class SlimJar { private static final boolean DEBUG = Boolean.getBoolean("iris.debug-slimjar"); @@ -29,9 +27,7 @@ public class SlimJar { if (loaded.getAndSet(true)) return; VolmitPlugin plugin = BukkitPlatform.volmitPlugin(); Path downloadPath = plugin.getDataFolder("cache", "libraries").toPath(); - Logger logger = plugin.getLogger(); - - logger.info("Loading libraries..."); + debug(plugin, "Loading libraries..."); try { new SpigotApplicationBuilder(plugin) .downloadDirectoryPath(downloadPath) @@ -40,15 +36,14 @@ public class SlimJar { } catch (Throwable e) { // The Spigot builder is a probe: not every server exposes it, and the fallback is the // supported path on the ones that do not. - IrisLogging.info("Failed to inject the library loader, falling back to application builder"); + debug(plugin, "Failed to inject the library loader, falling back to application builder"); ApplicationBuilder.appending(plugin.getName()) .injectableFactory(InjectableFactory.selecting(InjectableFactory.ERROR, InjectableFactory.INJECTABLE, InjectableFactory.WRAPPED, InjectableFactory.UNSAFE)) .downloadDirectoryPath(downloadPath) .logger(new ProcessLogger() { @Override public void info(@NotNull String message, @Nullable Object... args) { - if (!DEBUG) return; - plugin.getLogger().info(message.formatted(args)); + SlimJar.debug(plugin, message.formatted(args)); } @Override @@ -58,15 +53,20 @@ public class SlimJar { @Override public void debug(@NotNull String message, @Nullable Object... args) { - if (!DEBUG) return; - plugin.getLogger().info(message.formatted(args)); + SlimJar.debug(plugin, message.formatted(args)); } }) .build(); } - logger.info("Libraries loaded successfully!"); + debug(plugin, "Libraries loaded successfully!"); } finally { lock.unlock(); } } + + private static void debug(VolmitPlugin plugin, String message) { + if (DEBUG) { + plugin.getLogger().info("[DEBUG] " + message); + } + } } diff --git a/core/src/main/java/art/arcane/iris/util/common/misc/getHardware.java b/core/src/main/java/art/arcane/iris/util/common/misc/getHardware.java index 0c470beaf..5bba139f6 100644 --- a/core/src/main/java/art/arcane/iris/util/common/misc/getHardware.java +++ b/core/src/main/java/art/arcane/iris/util/common/misc/getHardware.java @@ -1,6 +1,7 @@ package art.arcane.iris.util.common.misc; +import art.arcane.iris.spi.IrisLogging; import art.arcane.volmlib.util.collection.KList; import art.arcane.iris.util.common.format.C; import art.arcane.volmlib.util.format.Form; @@ -53,7 +54,7 @@ public class getHardware { String cpuModel = processor.getProcessorIdentifier().getName(); return cpuModel.isEmpty() ? "Unknown CPU Model" : cpuModel; } catch (Exception e) { - e.printStackTrace(); + logProbeFailure("CPU model", e); return "Unknown CPU Model"; } } @@ -67,7 +68,7 @@ public class getHardware { temps.add("Fan Speeds: " + Arrays.toString(systemInfo.getHardware().getSensors().getFanSpeeds())); return temps.copy(); } catch (Exception e) { - e.printStackTrace(); + logProbeFailure("sensors", e); } return null; } @@ -83,7 +84,7 @@ public class getHardware { } return gpus.copy(); } catch (Exception e) { - e.printStackTrace(); + logProbeFailure("graphics cards", e); } return null; } @@ -117,7 +118,7 @@ public class getHardware { } return systemDisks.copy(); } catch (Exception e) { - e.printStackTrace(); + logProbeFailure("disks", e); } return null; } @@ -140,7 +141,7 @@ public class getHardware { } return systemPowerSources.copy(); } catch (Exception e) { - e.printStackTrace(); + logProbeFailure("power sources", e); } return null; } @@ -159,7 +160,7 @@ public class getHardware { } return systemEDID.copy(); } catch (Exception e) { - e.printStackTrace(); + logProbeFailure("displays", e); } return null; } @@ -177,8 +178,13 @@ public class getHardware { } return interfaces.copy(); } catch (Exception e) { - e.printStackTrace(); + logProbeFailure("network interfaces", e); } return null; } -} \ No newline at end of file + + private static void logProbeFailure(String component, Exception failure) { + IrisLogging.debug("Hardware " + component + " probe failed: " + failure.getClass().getSimpleName() + + (failure.getMessage() == null ? "" : " - " + failure.getMessage())); + } +} diff --git a/core/src/main/java/art/arcane/iris/util/common/nbt/mca/NBTWorld.java b/core/src/main/java/art/arcane/iris/util/common/nbt/mca/NBTWorld.java index 9f916ce4f..807c1036c 100644 --- a/core/src/main/java/art/arcane/iris/util/common/nbt/mca/NBTWorld.java +++ b/core/src/main/java/art/arcane/iris/util/common/nbt/mca/NBTWorld.java @@ -78,10 +78,11 @@ public class NBTWorld { IrisLogging::info, IrisLogging::debug, (message, error) -> { - IrisLogging.error(message); if (error != null) { - error.printStackTrace(); + IrisLogging.reportError(message, error); + return; } + IrisLogging.error(message); } ), M::ms, diff --git a/core/src/main/java/art/arcane/iris/util/common/scheduling/J.java b/core/src/main/java/art/arcane/iris/util/common/scheduling/J.java index 69bce7cbd..ec54690d7 100644 --- a/core/src/main/java/art/arcane/iris/util/common/scheduling/J.java +++ b/core/src/main/java/art/arcane/iris/util/common/scheduling/J.java @@ -40,6 +40,8 @@ import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Entity; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; @@ -77,7 +79,6 @@ public class J { SchedulerBridge.setCancelScheduler(J::car); SchedulerBridge.setErrorHandler(e -> { IrisLogging.reportError(e); - e.printStackTrace(); }); SchedulerBridge.setInfoLogger(IrisLogging::debug); SchedulerBridge.setThreadRegistrar(thread -> { @@ -104,7 +105,6 @@ public class J { } catch (Throwable e) { IrisLogging.reportError(e); IrisLogging.error("Failed to run async task"); - e.printStackTrace(); } }); } @@ -116,7 +116,6 @@ public class J { } catch (Throwable e) { IrisLogging.reportError(e); IrisLogging.error("Failed to run async task"); - e.printStackTrace(); } }); } @@ -379,6 +378,7 @@ public class J { } public static void cancelPluginTasks() { + cancelTrackedRepeatingTasks(); if (!BukkitPlatform.hasPlugin()) { return; } @@ -393,6 +393,17 @@ public class J { } } + static void cancelTrackedRepeatingTasks() { + List cancelActions; + synchronized (REPEATING_CANCELLERS) { + cancelActions = new ArrayList<>(REPEATING_CANCELLERS.values()); + REPEATING_CANCELLERS.clear(); + } + for (Runnable cancelAction : cancelActions) { + cancelAction.run(); + } + } + public static void s(Runnable r) { if (!BUKKIT_PRESENT) { if (IrisPlatforms.isBound()) { @@ -587,7 +598,6 @@ public class J { r.run(); } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); } if (state.cancelled || !canSchedule()) { REPEATING_CANCELLERS.remove(taskId); @@ -677,7 +687,6 @@ public class J { r.run(); } catch (Throwable e) { IrisLogging.reportError(e); - e.printStackTrace(); } if (state.cancelled || !canSchedule()) { REPEATING_CANCELLERS.remove(taskId); @@ -708,13 +717,18 @@ public class J { } private static int trackRepeatingTask(Runnable cancelAction) { - int id = TASK_IDS.getAndIncrement(); - REPEATING_CANCELLERS.put(id, cancelAction); - return id; + synchronized (REPEATING_CANCELLERS) { + int id = TASK_IDS.getAndIncrement(); + REPEATING_CANCELLERS.put(id, cancelAction); + return id; + } } private static void cancelRepeatingTask(int id) { - Runnable cancelAction = REPEATING_CANCELLERS.remove(id); + Runnable cancelAction; + synchronized (REPEATING_CANCELLERS) { + cancelAction = REPEATING_CANCELLERS.remove(id); + } if (cancelAction != null) { cancelAction.run(); } diff --git a/core/src/main/java/art/arcane/iris/util/common/scheduling/jobs/DownloadJob.java b/core/src/main/java/art/arcane/iris/util/common/scheduling/jobs/DownloadJob.java index 3fe90c64b..0dc24a3d4 100644 --- a/core/src/main/java/art/arcane/iris/util/common/scheduling/jobs/DownloadJob.java +++ b/core/src/main/java/art/arcane/iris/util/common/scheduling/jobs/DownloadJob.java @@ -20,6 +20,7 @@ package art.arcane.iris.util.common.scheduling.jobs; import art.arcane.iris.core.localization.IrisLanguage; import art.arcane.iris.core.localization.RuntimeUiMessages; +import art.arcane.iris.spi.IrisLogging; import art.arcane.volmlib.util.network.DL; import art.arcane.volmlib.util.network.DownloadMonitor; @@ -63,7 +64,7 @@ public class DownloadJob implements Job { download.downloadChunk(); } } catch (IOException e) { - e.printStackTrace(); + IrisLogging.reportError("Iris pack download failed.", e); } cw = tw; diff --git a/core/src/main/java/art/arcane/iris/util/project/matter/IrisMatterSupport.java b/core/src/main/java/art/arcane/iris/util/project/matter/IrisMatterSupport.java index b7d9a1ed2..187f38498 100644 --- a/core/src/main/java/art/arcane/iris/util/project/matter/IrisMatterSupport.java +++ b/core/src/main/java/art/arcane/iris/util/project/matter/IrisMatterSupport.java @@ -112,10 +112,9 @@ public final class IrisMatterSupport { long oldSize = folder.length(); object.read(folder); from(object).write(folder); - IrisLogging.info("Converted " + folder.getPath() + " Saved " + (oldSize - folder.length())); + IrisLogging.debug("Converted " + folder.getPath() + " Saved " + (oldSize - folder.length())); } catch (Throwable e) { - IrisLogging.error("Failed to convert " + folder.getPath()); - e.printStackTrace(); + IrisLogging.reportError("Failed to convert " + folder.getPath() + ".", e); } return 0; diff --git a/core/src/main/java/art/arcane/iris/util/project/noise/CNG.java b/core/src/main/java/art/arcane/iris/util/project/noise/CNG.java index a4b5eea2e..9894e8dc7 100644 --- a/core/src/main/java/art/arcane/iris/util/project/noise/CNG.java +++ b/core/src/main/java/art/arcane/iris/util/project/noise/CNG.java @@ -217,7 +217,7 @@ public class CNG { r += cng.fit(-1000, 1000, i, i); } - System.out.println(Form.duration(p.getMilliseconds(), 10) + " merged = " + r); + IrisLogging.info(Form.duration(p.getMilliseconds(), 10) + " merged = " + r); } public CNG cellularize(RNG seed, double freq) { diff --git a/core/src/main/java/art/arcane/iris/util/project/stream/ProceduralStream.java b/core/src/main/java/art/arcane/iris/util/project/stream/ProceduralStream.java index 9d7f90946..d0c9567f5 100644 --- a/core/src/main/java/art/arcane/iris/util/project/stream/ProceduralStream.java +++ b/core/src/main/java/art/arcane/iris/util/project/stream/ProceduralStream.java @@ -81,7 +81,6 @@ public interface ProceduralStream extends ProceduralLayer, Interpolated { } catch (IncompatibleClassChangeError e) { IrisLogging.warn(f.toString()); IrisLogging.reportError(e); - e.printStackTrace(); return null; } } diff --git a/core/src/main/resources/languages/de_DE.json b/core/src/main/resources/languages/de_DE.json index 0eaf44672..c857e0930 100644 --- a/core/src/main/resources/languages/de_DE.json +++ b/core/src/main/resources/languages/de_DE.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§eVersuche eine der folgenden Optionen: overworld, vanilla, flat, theend", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§aVorbereitung der Welt abgeschlossen. Iris startet den Server jetzt neu, um \"{worldName}\" zu generieren/zu laden.", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§cBei der Erstellung ist eine Ausnahme aufgetreten. Weitere Details findest du in der Konsole.", "iris.bukkit.commandiris.successfully_created_your_world": "§aDeine Welt wurde erfolgreich erstellt!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eDie Erstellung von Welten zur Laufzeit ist unter Folia deaktiviert.", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§eWeltdateien und bukkit.yml werden für den nächsten Start vorbereitet...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§cDie Weltdateien für Dimension \"{value}\" konnten nicht vorbereitet werden.", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§aIris-Welt \"{name}\" mit Generator Iris:{value} und Seed {seed} vorbereitet.", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§cbukkit.yml konnte nicht aktualisiert werden: {value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§cDer angegebene Spieler existiert nicht.", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aIris v{value} von Volmit Software", "iris.bukkit.commandiris.to": "§a{value} bis {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} ist keine Iris-Welt.", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§cDie Iris-Dimension für {logicalWorldName} konnte nicht bestimmt werden.", "iris.bukkit.commandiris.loading_world": "§aWelt wird geladen: {logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia kann zur Laufzeit keine neuen Welten laden. Starte den Server neu, um \"{logicalWorldName}\" zu laden.", "iris.bukkit.commandiris.loaded_successfully": "§a{logicalWorldName} erfolgreich geladen.", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§cDies ist keine Iris-Welt. Iris-Welten: {value}", "iris.bukkit.commandiris.evacuating_world": "§aWelt wird evakuiert{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "Ungültiger Projektname '{nameRaw}' (zulässig: a-z, 0-9, _ und -)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "Pack '{name}' ist unter {value} bereits vorhanden.", "iris.modded.moddedstudiocommands.creating_project_from_template": "Erstelle Projekt '{name}' aus Vorlage '{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "Vorlage '{template}' ist nicht installiert; IrisDimensions/{template2} wird heruntergeladen...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "Vorlage '{template}' konnte nicht heruntergeladen werden. Installiere zuerst ein Pack mit dimensions/{template2}.json.", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "Das erforderliche Pack '{pack}' ist nicht installiert. Installiere es mit /iris download und starte den Server anschließend neu.", "iris.modded.moddedstudiocommands.created_project_at": "Projekt '{name}' unter {value} erstellt.", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "Bearbeite dimensions/{name}.json und den Rest des Pack. Ein VSCode-Arbeitsbereich mit JSON-Schema-Autovervollständigung wurde erstellt; öffne ihn oder führe /iris studio vscode erneut aus.", "iris.modded.moddedstudiocommands.project_creation_failed": "Projekterstellung gescheitert: {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "Verteilung der Probenahmeregionen in {value}x{value2} Chunks um dich herum...", "iris.modded.moddedstudiocommands.rarity": "{key}: Seltenheit ={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "Regionalbeprobung fehlgeschlagen: {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "Pack '{pack}' ist nicht installiert; IrisDimensions/{pack2} wird heruntergeladen...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "Pack '{pack}' konnte nicht heruntergeladen werden. Prüfe den Namen oder installiere es mit /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "Das erforderliche Pack '{pack}' ist nicht installiert. Installiere es mit /iris download und starte den Server anschließend neu.", "iris.modded.moddedworldcommands.failed_inject_iris_world": "Iris-Welt '{dimensionId}' konnte nicht eingebunden werden: {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "Iris-Welt {dimensionId} aus Pack '{pack}', Dimension '{packDimension}', erstellt (Seed {seed}).", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "Sie ist jetzt aktiv und wird bei jedem Start erneut eingebunden. Teleportiere dich über /iris world status oder ein Portal hinein; ein Neustart ist nicht erforderlich.", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "Stattdessen ist {dimensionId} jetzt als primäre Welt konfiguriert: Spieler in der vanilla-Oberwelt werden beim Beitritt dorthin geleitet, und die Welt wird bei jedem Start erneut eingebunden.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Überschreibung der Iris-Hauptwelt entfernt. Die Oberwelt behält ihren aktuellen Generator; bearbeite level-type in server.properties und starte den Server neu, um ihn zurückzuändern.", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "Ungültiger Seed '{seedRaw}'. Verwende eine Zahl oder 'random'.", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "Pack '{pack}' ist nicht installiert; IrisDimensions/{pack2} wird heruntergeladen...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "Pack '{pack}' konnte nicht heruntergeladen werden. Prüfe den Namen oder installiere es mit /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "Pack '{pack}' ist noch nicht bereit, da es noch geladen oder validiert wird. Versuche den Befehl gleich noch einmal.", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "server.properties konnte nicht geschrieben werden. Prüfe die Dateiberechtigungen und setze level-type manuell.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Iris-Hauptwelt auf '{pack}' gesetzt (Voreinstellung {preset}, Seed {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "Dich selbst oder einen angegebenen Spieler in eine geladene Iris-Dimension teleportieren", "iris.modded.help.entry.command.evacuate": "Alle Spieler aus einer Iris-Dimension zum Spawn der primären Welt teleportieren", "iris.modded.help.entry.command.seed": "Seed-Informationen der Welt und Engine anzeigen", - "iris.modded.help.entry.command.debug": "Iris-Debugprotokollierung umschalten und settings.json speichern", - "iris.modded.help.entry.command.reload": "settings.json neu laden; die Datei wird außerdem automatisch alle 3 Sekunden direkt neu geladen", + "iris.modded.help.entry.command.debug": "Iris-Debugprotokollierung umschalten und iris.json speichern", + "iris.modded.help.entry.command.reload": "iris.json neu laden; die Datei wird außerdem automatisch alle 3 Sekunden direkt neu geladen", "iris.modded.help.entry.command.download": "Ein Pack-Projekt herunterladen", "iris.modded.help.entry.command.metrics": "Generierungsmetriken der aktuellen Iris-Dimension anzeigen", "iris.modded.help.entry.command.regen": "Löschen und regenerieren Sie nahe gelegene Chunks an Ort und Stelle", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "Vorschau- oder Quarantänekandidaten für ungenutzte Ressourcen", "iris.modded.help.entry.command.restore": "Vorschau oder Wiederherstellung der neuesten Quarantäne", "iris.modded.help.entry.command.status_validation": "Validierungsstatus im Zwischenspeicher anzeigen", - "iris.modded.help.entry.command.enable": "Eine dauerhafte Iris-Dimension zur Laufzeit erstellen und einbinden; fehlendes Pack wird heruntergeladen, pack:dimensionKey in Anführungszeichen wählt eine bestimmte Dimension aus dem Pack", + "iris.modded.help.entry.command.enable": "Eine dauerhafte Iris-Dimension zur Laufzeit erstellen und einbinden; dafür muss das Pack installiert sein, und ein in Anführungszeichen gesetztes pack:dimensionKey wählt eine bestimmte Dimension.", "iris.modded.help.entry.command.replace_overworld": "Eine primäre Iris-Welt einbinden und Spieler statt in die vanilla-Oberwelt dorthin leiten", "iris.modded.help.entry.command.disable": "Eine Iris-Dimension evakuieren und entladen; ihre Weltdaten bleiben zum erneuten Aktivieren erhalten", "iris.modded.help.entry.command.delete": "Eine Iris-Dimension deaktivieren und ihre Chunk- und Mantle-Daten vom Datenträger löschen", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§cKonvertierung fehlgeschlagen: {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7Konvertiert: {get} in {value}", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§cEinige Schematic-Dateien konnten nicht konvertiert werden. Weitere Einzelheiten stehen in der Konsole.", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "Suche nach Paket: {type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "Welt wird erstellt", "iris.runtime.studio.stage.apply_world_rules": "Weltregeln werden angewendet", "iris.runtime.studio.stage.prepare_generator": "Generator wird vorbereitet", - "iris.runtime.studio.stage.request_entry_chunk": "Start-Chunk wird geladen", - "iris.runtime.studio.stage.resolve_safe_entry": "Sicherer Spawn wird gesucht", "iris.runtime.studio.stage.teleport_player": "Teleportation", "iris.runtime.studio.stage.finalize_open": "Abschluss", "iris.runtime.studio.stage.cleanup": "Bereinigung", "iris.runtime.world_create.teleport_failed": "§eDie Welt wurde erstellt, aber die automatische Teleportation ist fehlgeschlagen. Versuche /iris teleport world={world}", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/es_ES.json b/core/src/main/resources/languages/es_ES.json index 1e4207341..55ae43c94 100644 --- a/core/src/main/resources/languages/es_ES.json +++ b/core/src/main/resources/languages/es_ES.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§ePrueba una de estas opciones: overworld, vanilla, flat, theend", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§aPreparación del mundo completada. Iris está reiniciando el servidor para generar/cargar \"{worldName}\".", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§cSe produjo una excepción durante la creación. Consulta la consola para obtener más detalles.", "iris.bukkit.commandiris.successfully_created_your_world": "§a¡Tu mundo se creó correctamente!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eLa creación de mundos en tiempo de ejecución está desactivada en Folia.", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§ePreparando los archivos del mundo y bukkit.yml para el próximo inicio...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§cNo se pudieron preparar los archivos del mundo para la dimensión \"{value}\".", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§aSe preparó el mundo de Iris \"{name}\" con el generador Iris:{value} y la semilla {seed}.", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§cNo se pudo actualizar bukkit.yml: {value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§cEl jugador especificado no existe.", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aIris v{value} por Volmit Software", "iris.bukkit.commandiris.to": "§a{value} a {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} no es un mundo de Iris.", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§cNo se pudo determinar la dimensión de Iris para {logicalWorldName}.", "iris.bukkit.commandiris.loading_world": "§aCargando mundo: {logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia no puede cargar mundos nuevos en tiempo de ejecución. Reinicia el servidor para cargar \"{logicalWorldName}\".", "iris.bukkit.commandiris.loaded_successfully": "§a{logicalWorldName} se cargó correctamente.", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§cEste no es un mundo de Iris. Mundos de Iris: {value}", "iris.bukkit.commandiris.evacuating_world": "§aEvacuando mundo{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "Nombre de proyecto no válido '{nameRaw}' (permitido: a-z, 0-9, _ y -)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "El pack '{name}' ya existe en {value}", "iris.modded.moddedstudiocommands.creating_project_from_template": "Creando el proyecto '{name}' desde la plantilla '{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "La plantilla '{template}' no está instalada; descargando IrisDimensions/{template2}...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "No se pudo descargar la plantilla '{template}'; instala primero un pack con dimensions/{template2}.json.", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "El pack requerido '{pack}' no está instalado. Instálalo con /iris download y después reinicia el servidor.", "iris.modded.moddedstudiocommands.created_project_at": "Proyecto '{name}' creado en {value}", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "Edita dimensions/{name}.json y el resto del pack. Se generó un espacio de trabajo de VSCode con autocompletado mediante esquemas JSON; ábrelo o vuelve a ejecutar /iris studio vscode.", "iris.modded.moddedstudiocommands.project_creation_failed": "La creación del proyecto falló: {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "Muestreando la distribución de regiones en {value}x{value2} chunks a tu alrededor...", "iris.modded.moddedstudiocommands.rarity": "{key}: rareza={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "El muestreo de regiones falló: {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "El pack '{pack}' no está instalado; descargando IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "No se pudo descargar el pack '{pack}'; comprueba el nombre o instálalo con /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "El pack requerido '{pack}' no está instalado. Instálalo con /iris download y después reinicia el servidor.", "iris.modded.moddedworldcommands.failed_inject_iris_world": "No se pudo inyectar el mundo de Iris '{dimensionId}': {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "Se creó el mundo de Iris {dimensionId} desde el pack '{pack}', dimensión '{packDimension}' (semilla {seed}).", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "Ya está activo y se vuelve a inyectar en cada inicio. Teletranspórtate con /iris world status o mediante un portal; no es necesario reiniciar.", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "En su lugar, {dimensionId} es ahora el mundo principal configurado: los jugadores del overworld vanilla se dirigen allí al entrar y se vuelve a inyectar en cada inicio.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Se eliminó la sobrescritura del mundo principal de Iris. El overworld conserva su generador actual; edita level-type en server.properties y reinicia para restaurarlo.", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "Semilla no válida '{seedRaw}'. Usa un número o 'random'.", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "El pack '{pack}' no está instalado; descargando IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "No se pudo descargar el pack '{pack}'; comprueba el nombre o instálalo con /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "El pack '{pack}' todavía no está listo (se está cargando o validando). Vuelve a ejecutar el comando dentro de un momento.", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "No se pudo escribir server.properties; comprueba los permisos del archivo y establece level-type manualmente.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "El mundo principal de Iris se estableció en '{pack}' (preset {preset}, semilla {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "Teletransportarte o teletransportar a otro jugador a una dimensión de Iris cargada", "iris.modded.help.entry.command.evacuate": "Teletransportar a todos los jugadores desde una dimensión de Iris al spawn del mundo principal", "iris.modded.help.entry.command.seed": "Mostrar información sobre la semilla del mundo y del motor", - "iris.modded.help.entry.command.debug": "Alternar el registro de depuración de Iris y guardar settings.json", - "iris.modded.help.entry.command.reload": "Recargar settings.json (también se recarga en caliente automáticamente cada 3s)", + "iris.modded.help.entry.command.debug": "Alternar el registro de depuración de Iris y guardar iris.json", + "iris.modded.help.entry.command.reload": "Recargar iris.json (también se recarga en caliente automáticamente cada 3s)", "iris.modded.help.entry.command.download": "Descargar un proyecto de pack", "iris.modded.help.entry.command.metrics": "Mostrar métricas de generación de tu dimensión de Iris actual", "iris.modded.help.entry.command.regen": "Eliminar y regenerar chunks cercanos en el mismo lugar", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "Previsualizar o poner en cuarentena candidatos a recursos sin usar", "iris.modded.help.entry.command.restore": "Previsualizar o restaurar la cuarentena más reciente", "iris.modded.help.entry.command.status_validation": "Mostrar el estado de validación almacenado en caché", - "iris.modded.help.entry.command.enable": "Crear e inyectar una dimensión de Iris persistente en tiempo de ejecución; descargar el pack si falta y escribir pack:dimensionKey entre comillas para elegir una dimensión concreta del pack", + "iris.modded.help.entry.command.enable": "Crear e inyectar una dimensión Iris persistente en tiempo de ejecución; requiere un pack instalado y pack:dimensionKey entre comillas selecciona una dimensión concreta.", "iris.modded.help.entry.command.replace_overworld": "Inyectar un mundo principal de Iris y dirigir allí a los jugadores en lugar de al overworld vanilla", "iris.modded.help.entry.command.disable": "Evacuar y retirar de la memoria una dimensión de Iris; los datos del mundo se conservan en disco para volver a activarla", "iris.modded.help.entry.command.delete": "Desactivar una dimensión de Iris y borrar del disco sus datos de chunks y Mantle", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§cNo se pudo convertir: {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7Convertido: {get} en {value}", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§cNo se pudieron convertir algunos schematics. Consulta la consola para obtener más detalles.", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "Buscando pack: {type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "Creando mundo", "iris.runtime.studio.stage.apply_world_rules": "Aplicando reglas del mundo", "iris.runtime.studio.stage.prepare_generator": "Preparando generador", - "iris.runtime.studio.stage.request_entry_chunk": "Cargando chunk de entrada", - "iris.runtime.studio.stage.resolve_safe_entry": "Buscando un spawn seguro", "iris.runtime.studio.stage.teleport_player": "Teletransportando", "iris.runtime.studio.stage.finalize_open": "Finalizando", "iris.runtime.studio.stage.cleanup": "Limpiando", "iris.runtime.world_create.teleport_failed": "§eEl mundo se creó, pero falló el teletransporte automático. Prueba /iris teleport world={world}", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/fi_FI.json b/core/src/main/resources/languages/fi_FI.json index 73d8e705e..dc66d8267 100644 --- a/core/src/main/resources/languages/fi_FI.json +++ b/core/src/main/resources/languages/fi_FI.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§eKokeile yksi: maapallo, vanilla, litteä, theend", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§aMaailman valmistelu valmis. Iris käynnistää palvelimen nyt uudelleen, jotta \"{worldName}\" luodaan/ladataan.", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§cLuomisen aikana nostettu poikkeus. Katso lisätietoja konsolista.", "iris.bukkit.commandiris.successfully_created_your_world": "§aOnnistuneesti loin maailmasi!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eRuntime maailman luominen on pois käytöstä Folia.", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§eValmistellaan maailman tiedostoja ja bukkit.yml seuraavan käynnistyksen...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§cMaailmatiedostoja ei voitu lavastaa ulottuvuudeksi \"{value}\".", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§aIris-maailma \"{name}\" valmisteltiin generaattorilla Iris:{value} ja siemenellä {seed}.", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§cPäivitys epäonnistui bukkit.yml: {value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§cMääriteltyä pelaajaa ei ole olemassa.", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aIris{value} mennessä Volmit Software", "iris.bukkit.commandiris.to": "§a{value} - {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} Ei ole iirismaailma.", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§cEi voitu määrittää Iris mitat {logicalWorldName}.", "iris.bukkit.commandiris.loading_world": "§aLadataan maailmaa: {logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia ei voi ladata uusia maailmoja. Käynnistä palvelin uudelleen ladata \"{logicalWorldName}\".", "iris.bukkit.commandiris.loaded_successfully": "§a{logicalWorldName} Ladattu onnistuneesti.", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§cTämä ei ole Iris Maailma. Iris Maailmoja: {value}", "iris.bukkit.commandiris.evacuating_world": "§aMaailman evakuointi{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "Virheellinen projektin nimi \"{nameRaw}' ( sallittu: a-z, 0-9, ja -)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "Pakkaus{name}' on jo {value}", "iris.modded.moddedstudiocommands.creating_project_from_template": "Luodaan projektia \"{name}\" mallista \"{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "Malli \"{template}\"ei ole asennettu; lataaminen IrisDimensions/{template2}...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "Malli \"{template}\" ei voitu ladata; asentaa pakkaus dimensions/{template2}.json Ensin.", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "Vaadittua pakettia '{pack}' ei ole asennettu. Asenna se komennolla /iris download ja käynnistä palvelin sitten uudelleen.", "iris.modded.moddedstudiocommands.created_project_at": "Luotu projekti \"{name}' {value}", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "Muokkaa dimensions/{name}.json Ja loput laumasta. A VSCode työtila, JSON skeema automaattitäydennys syntyi; avaa se tai uudelleen /iris studio vscode.", "iris.modded.moddedstudiocommands.project_creation_failed": "Projektin luominen epäonnistui: {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "Näytteenottoalueen jakautuminen {value}x{value2} Pyörii ympärilläsi...", "iris.modded.moddedstudiocommands.rarity": "{key}: harvinaisuus ={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "Aluekohtainen näytteenotto epäonnistui: {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "Pakkaus{pack}\"ei ole asennettu; lataaminen IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "Pakkaus{pack}\" ei voitu ladata; tarkista nimi tai asentaa sen kanssa /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "Vaadittua pakettia '{pack}' ei ole asennettu. Asenna se komennolla /iris download ja käynnistä palvelin sitten uudelleen.", "iris.modded.moddedworldcommands.failed_inject_iris_world": "Injektio epäonnistui Iris maailma{dimensionId}': {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "Luotu Iris maailma {dimensionId} pakkauksesta \"{pack}' ulottuvuudella'{packDimension}\" (siemen {seed}).", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "Se on nyt suorassa lähetyksessä. Teleportata /iris world status tai portaali; uudelleenkäynnistystä ei tarvita.", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "Sen sijaan {dimensionId} on nyt konfiguroitu ensisijainen maailma: pelaajat vanilla overworld ohjataan siellä liittyä, ja se uudelleen injektoi jokaisen startup.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Iris Päämaailman ohitus selvä. Maailma pitää nykyisen generaattorinsa; muokkaa server.properties tason tyyppi ja käynnistää muuttaa sitä takaisin.", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "Virheelliset siemenet \"{seedRaw}'. Käytä numeroa tai satunnaisuutta.", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "Pakkaus{pack}\"ei ole asennettu; lataaminen IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "Pakkaus{pack}\" ei voitu ladata; tarkista nimi tai asentaa sen kanssa /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "Pakkaus{pack}\"ei ole vielä valmis (täysin lastaus tai validointi). Yritä komentoa uudelleen hetken päästä.", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "Kirjoittaminen epäonnistui server.properties; tarkista tiedostojen oikeudet ja aseta tasotyyppi käsin.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Iris tärkein maailma asetettu '{pack}' (esitetty {preset}, siemenet {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "Teleportaa itsesi tai nimetty pelaaja ladattu Iris ulottuvuus", "iris.modded.help.entry.command.evacuate": "Teleportoi kaikki pelaajat Iris-ulottuvuudesta päämaailman spawn-pisteeseen", "iris.modded.help.entry.command.seed": "Tulosta maailman ja generaattorin seed-tiedot", - "iris.modded.help.entry.command.debug": "Vaihda Iris vianetsintä ja tallenna settings.json", - "iris.modded.help.entry.command.reload": "Lataa uudelleen settings.json (myös hotloaded automaattisesti joka 3.s)", + "iris.modded.help.entry.command.debug": "Vaihda Iris vianetsintä ja tallenna iris.json", + "iris.modded.help.entry.command.reload": "Lataa uudelleen iris.json (myös hotloaded automaattisesti joka 3.s)", "iris.modded.help.entry.command.download": "Lataa pakettiprojekti", "iris.modded.help.entry.command.metrics": "Tulosta nykyisen sukupolven mittareita Iris ulottuvuus", "iris.modded.help.entry.command.regen": "Poista ja uudista lähistön palaset paikoilleen", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "Esikatselu tai karanteeni käyttämättömät resurssit", "iris.modded.help.entry.command.restore": "Esikatselu tai uusinta karanteenia", "iris.modded.help.entry.command.status_validation": "Näytä välimuistin validointistatus", - "iris.modded.help.entry.command.enable": "Luo ja pistä hitaasti Iris ulottuvuus ajon aikana; lataa pakkauksen, jos se puuttuu:dimensionKey valita tietty pakkauksen ulottuvuus", + "iris.modded.help.entry.command.enable": "Luo ja ota käyttöön pysyvä Iris-ulottuvuus ajon aikana; paketin on oltava asennettu, ja lainausmerkeissä oleva pack:dimensionKey valitsee tietyn ulottuvuuden.", "iris.modded.help.entry.command.replace_overworld": "Pistä Iris ensisijainen maailma ja reitti pelaajat siellä sijaan vanilla overworld", "iris.modded.help.entry.command.disable": "Evakuoi ja pura Iris ulottuvuus; maailman tiedot levyllä säilytetään uudelleen-enabling", "iris.modded.help.entry.command.delete": "Poista käytöstä Iris mitta ja pyyhi sen pala ja Mantle tiedot levyltä", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§cMuuntaminen epäonnistui: {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7Muunnettu: {get} sisään {value}", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§cJotkut kaaviot eivät muuttuneet. Tarkista konsolista yksityiskohtia.", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "Etsin pakettia: {type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "Luodaan maailmaa", "iris.runtime.studio.stage.apply_world_rules": "Otetaan maailmansäännöt käyttöön", "iris.runtime.studio.stage.prepare_generator": "Valmistellaan generaattoria", - "iris.runtime.studio.stage.request_entry_chunk": "Ladataan aloituschunkkia", - "iris.runtime.studio.stage.resolve_safe_entry": "Etsitään turvallista spawnia", "iris.runtime.studio.stage.teleport_player": "Teleportoidaan", "iris.runtime.studio.stage.finalize_open": "Viimeistellään", "iris.runtime.studio.stage.cleanup": "Siivotaan", "iris.runtime.world_create.teleport_failed": "§eMaailma luotiin, mutta automaattinen teleportti epäonnistui. Yritä /iris teleport world={world}", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/fr_FR.json b/core/src/main/resources/languages/fr_FR.json index d3f418b00..7fe3509e1 100644 --- a/core/src/main/resources/languages/fr_FR.json +++ b/core/src/main/resources/languages/fr_FR.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§eEssayez l'une des options suivantes : overworld, vanilla, flat, theend", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§aPréparation du monde terminée. Iris redémarre maintenant le serveur pour générer/charger \"{worldName}\".", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§cUne exception s'est produite pendant la création. Consultez la console pour plus de détails.", "iris.bukkit.commandiris.successfully_created_your_world": "§aVotre monde a été créé avec succès !", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eLa création de mondes pendant l'exécution est désactivée sur Folia.", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§ePréparation des fichiers du monde et de bukkit.yml pour le prochain démarrage...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§cImpossible de préparer les fichiers du monde pour la dimension \"{value}\".", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§aLe monde Iris \"{name}\" a été préparé avec le générateur Iris:{value} et la graine {seed}.", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§cImpossible de mettre à jour bukkit.yml : {value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§cLe joueur indiqué n'existe pas.", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aIris v{value} par Volmit Software", "iris.bukkit.commandiris.to": "§a{value} à {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} n'est pas un monde Iris.", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§cImpossible de déterminer la dimension Iris de {logicalWorldName}.", "iris.bukkit.commandiris.loading_world": "§aChargement du monde : {logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia ne peut pas charger de nouveaux mondes pendant l'exécution. Redémarrez le serveur pour charger \"{logicalWorldName}\".", "iris.bukkit.commandiris.loaded_successfully": "§a{logicalWorldName} a été chargé avec succès.", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§cCe monde n'est pas un monde Iris. Mondes Iris : {value}", "iris.bukkit.commandiris.evacuating_world": "§aÉvacuation du monde{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "Nom de projet non valide '{nameRaw}' (autorisé : a-z, 0-9, _ et -)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "Le pack '{name}' existe déjà dans {value}", "iris.modded.moddedstudiocommands.creating_project_from_template": "Création du projet '{name}' depuis le modèle '{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "Le modèle '{template}' n'est pas installé ; téléchargement de IrisDimensions/{template2}...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "Impossible de télécharger le modèle '{template}' ; installez d'abord un pack contenant dimensions/{template2}.json.", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "Le pack requis '{pack}' n’est pas installé. Installez-le avec /iris download, puis redémarrez le serveur.", "iris.modded.moddedstudiocommands.created_project_at": "Projet '{name}' créé dans {value}", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "Modifiez dimensions/{name}.json et le reste du pack. Un espace de travail VSCode avec autocomplétion par schémas JSON a été généré ; ouvrez-le ou relancez /iris studio vscode.", "iris.modded.moddedstudiocommands.project_creation_failed": "Échec de la création du projet : {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "Échantillonnage de la répartition des régions dans {value}x{value2} chunks autour de vous...", "iris.modded.moddedstudiocommands.rarity": "{key} : rareté={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "Échec de l'échantillonnage des régions : {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "Le pack '{pack}' n'est pas installé ; téléchargement de IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "Impossible de télécharger le pack '{pack}' ; vérifiez le nom ou installez-le avec /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "Le pack requis '{pack}' n’est pas installé. Installez-le avec /iris download, puis redémarrez le serveur.", "iris.modded.moddedworldcommands.failed_inject_iris_world": "Impossible d'injecter le monde Iris '{dimensionId}' : {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "Monde Iris {dimensionId} créé depuis le pack '{pack}', dimension '{packDimension}' (graine {seed}).", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "Il est maintenant actif et réinjecté à chaque démarrage. Téléportez-vous avec /iris world status ou un portail ; aucun redémarrage nécessaire.", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "À la place, {dimensionId} est maintenant le monde principal configuré : les joueurs de l'overworld vanilla y sont redirigés à leur connexion et il est réinjecté à chaque démarrage.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Le remplacement du monde principal Iris a été supprimé. L'overworld conserve son générateur actuel ; modifiez level-type dans server.properties et redémarrez pour le rétablir.", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "Graine non valide '{seedRaw}'. Utilisez un nombre ou 'random'.", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "Le pack '{pack}' n'est pas installé ; téléchargement de IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "Impossible de télécharger le pack '{pack}' ; vérifiez le nom ou installez-le avec /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "Le pack '{pack}' n'est pas encore prêt (chargement ou validation en cours). Réessayez la commande dans un instant.", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "Impossible d'écrire server.properties ; vérifiez les droits du fichier et définissez level-type manuellement.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Le monde principal Iris est défini sur '{pack}' (preset {preset}, graine {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "Vous téléporter ou téléporter un joueur nommé vers une dimension Iris chargée", "iris.modded.help.entry.command.evacuate": "Téléporter tous les joueurs d'une dimension Iris vers le spawn du monde principal", "iris.modded.help.entry.command.seed": "Afficher les informations de graine du monde et du moteur", - "iris.modded.help.entry.command.debug": "Basculer les journaux de débogage Iris et enregistrer settings.json", - "iris.modded.help.entry.command.reload": "Recharger settings.json (également rechargé à chaud automatiquement toutes les 3s)", + "iris.modded.help.entry.command.debug": "Basculer les journaux de débogage Iris et enregistrer iris.json", + "iris.modded.help.entry.command.reload": "Recharger iris.json (également rechargé à chaud automatiquement toutes les 3s)", "iris.modded.help.entry.command.download": "Télécharger un projet de pack", "iris.modded.help.entry.command.metrics": "Afficher les métriques de génération de votre dimension Iris actuelle", "iris.modded.help.entry.command.regen": "Supprimer et régénérer sur place les chunks proches", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "Prévisualiser ou mettre en quarantaine les ressources inutilisées candidates", "iris.modded.help.entry.command.restore": "Prévisualiser ou restaurer la quarantaine la plus récente", "iris.modded.help.entry.command.status_validation": "Afficher l'état de validation mis en cache", - "iris.modded.help.entry.command.enable": "Créer et injecter une dimension Iris persistante pendant l'exécution ; télécharger le pack s'il manque et mettre pack:dimensionKey entre guillemets pour choisir une dimension précise du pack", + "iris.modded.help.entry.command.enable": "Créer et injecter une dimension Iris persistante pendant l’exécution ; le pack doit être installé et pack:dimensionKey entre guillemets sélectionne une dimension précise.", "iris.modded.help.entry.command.replace_overworld": "Injecter un monde principal Iris et y rediriger les joueurs à la place de l'overworld vanilla", "iris.modded.help.entry.command.disable": "Évacuer et décharger de la mémoire une dimension Iris ; les données du monde restent sur le disque pour la réactiver", "iris.modded.help.entry.command.delete": "Désactiver une dimension Iris et effacer du disque ses données de chunks et de Mantle", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§cImpossible de convertir : {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7Converti : {get} dans {value}", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§cCertains schematics n'ont pas pu être convertis. Consultez la console pour plus de détails.", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "Recherche du pack : {type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "Création du monde", "iris.runtime.studio.stage.apply_world_rules": "Application des règles du monde", "iris.runtime.studio.stage.prepare_generator": "Préparation du générateur", - "iris.runtime.studio.stage.request_entry_chunk": "Chargement du chunk d'entrée", - "iris.runtime.studio.stage.resolve_safe_entry": "Recherche d'un spawn sûr", "iris.runtime.studio.stage.teleport_player": "Téléportation", "iris.runtime.studio.stage.finalize_open": "Finalisation", "iris.runtime.studio.stage.cleanup": "Nettoyage", "iris.runtime.world_create.teleport_failed": "§eLe monde a été créé, mais la téléportation automatique a échoué. Essayez /iris teleport world={world}", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/he_IL.json b/core/src/main/resources/languages/he_IL.json index 8247b1bb8..adc07e9bc 100644 --- a/core/src/main/resources/languages/he_IL.json +++ b/core/src/main/resources/languages/he_IL.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§eנסה: Overworld vanilla, שטוח, TheendXTry", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§aהכנת העולם הושלמה. Iris מפעיל כעת מחדש את השרת כדי ליצור/לטעון את \"{worldName}\".", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§cלמעטים שגדלו במהלך הבריאה. ראו את הקונסולה לפרטים נוספים.", "iris.bukkit.commandiris.successfully_created_your_world": "§aיצרתם בהצלחה את עולמכם!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eיצירת העולם של Runtime זמינה Folia.", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§eהכנת קבצים בעולם bukkit.yml עבור הסטארט-אפ הבא...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§cנכשלים בקבצי עולם הבמה לממד \"{value}\".", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§aעולם Iris ‏\"{name}\" הוכן עם המחולל Iris:{value} והזרע {seed}.", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§cנכשל לעדכן bukkit.yml: {value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§cהשחקן שצוין אינו קיים.", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aאיריס v{value} על ידי Volmit Software", "iris.bukkit.commandiris.to": "§a{value} כדי {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} זה לא עולם חג המולד.", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§cלא יכול לקבוע Iris מימד {logicalWorldName}.", "iris.bukkit.commandiris.loading_world": "§aלטעון את העולם: {logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia לא יכול לטעון עולמות חדשים בזמן ריצה. שמור את השרת כדי לטעון \"{logicalWorldName}\".", "iris.bukkit.commandiris.loaded_successfully": "§a{logicalWorldName} טעון בהצלחה.", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§cזה לא Iris עולם Iris עולמות: {value}", "iris.bukkit.commandiris.evacuating_world": "§aלהעריך את העולם{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "שם הפרויקט \"{nameRaw}(הופנה מהדף A-z, 0-9- ו-)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "Pack »{name}\"כבר קיים {value}", "iris.modded.moddedstudiocommands.creating_project_from_template": "יצירת פרויקט \"{name}מתוך תבנית \"{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "תבנית \"{template}\"לא מותקנים; הורדת IrisDimensions/{template2}...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "תבנית \"{template}\"לא ניתן להוריד; להתקין חבילה עם dimensions/{template2}.json קודם.", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "החבילה הנדרשת '{pack}' אינה מותקנת. התקן אותה באמצעות /iris download ולאחר מכן הפעל מחדש את השרת.", "iris.modded.moddedstudiocommands.created_project_at": "הפרויקט '{name}' נוצר ב-{value}", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "עריכהעריכה dimensions/{name}.json שאר החבילה. A VSCode סביבת עבודה עם JSON schema Auto Complete נוצר; פתח אותו או הרצה מחדש /iris studio vscode.", "iris.modded.moddedstudiocommands.project_creation_failed": "יצירת פרויקטים נכשלה: {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "חלוקת האזור ב {value}x{value2} חלקים סביבך...", "iris.modded.moddedstudiocommands.rarity": "{key}תגית: rarity={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "האזור נכשל: {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "Pack »{pack}\"לא מותקנים; הורדת IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "Pack »{pack}לא ניתן להוריד את השם או להתקין אותו עם /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "החבילה הנדרשת '{pack}' אינה מותקנת. התקן אותה באמצעות /iris download ולאחר מכן הפעל מחדש את השרת.", "iris.modded.moddedworldcommands.failed_inject_iris_world": "נכשל להזרק Iris עולם »{dimensionId}': {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "נוצר Iris עולם העולם {dimensionId} מתוך \"חבילה\"{pack}מימד \"{packDimension}(זה {seed}).", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "הוא חי עכשיו ומופץ מחדש על כל סטארט-אפ. טלפורט עם /iris world status או פורטל, אין צורך מחדש.", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "במקום, {dimensionId} עכשיו הוא העולם הראשון מוגדר: שחקנים בעולם של vanilla מתכנסים שם על מנת להצטרף, וזה מחדש זריקות על כל סטארט-אפ.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Iris העולם המרכזי התנקה. העולם שומר על הגנרטור הנוכחי שלו; לערוך server.properties סוג וחדש מחדש כדי לשנות אותו בחזרה.", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "זרע לא חוקי \"{seedRaw}'. השתמש במספר או ב'random'.", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "Pack »{pack}\"לא מותקנים; הורדת IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "Pack »{pack}לא ניתן להוריד את השם או להתקין אותו עם /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "Pack »{pack}\"עדיין לא מוכן (הטעינה או אימות). נסו שוב את הפקודה ברגע אחד.", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "נכשל לכתוב server.properties• בדוק הרשאות קובץ ולהגדיר באופן ידני סוג ברמה.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Iris העולם העיקרי מוגדר ל\"{pack}' (preset {preset}זרע {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "טלפורט בעצמך או שחקן בשם לתוך טעון Iris מימד", "iris.modded.help.entry.command.evacuate": "שגר את כל השחקנים מממד Iris לנקודת הספאון של העולם הראשי", "iris.modded.help.entry.command.seed": "הצג מידע על ה-seed של העולם והמנוע", - "iris.modded.help.entry.command.debug": "עקבו אחרי Iris debug bug , שמירה settings.json", - "iris.modded.help.entry.command.reload": "המונחים טעינה מחדש settings.json (גם חם כל 3s)", + "iris.modded.help.entry.command.debug": "עקבו אחרי Iris debug bug , שמירה iris.json", + "iris.modded.help.entry.command.reload": "המונחים טעינה מחדש iris.json (גם חם כל 3s)", "iris.modded.help.entry.command.download": "הורד פרויקט חבילה", "iris.modded.help.entry.command.metrics": "הדפסת דור מדדים עבור הנוכחי שלך Iris מימד", "iris.modded.help.entry.command.regen": "למחוק ולחדש חלקים סמוכים במקום", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "סקר או מועמדים ללא שימוש", "iris.modded.help.entry.command.restore": "סקירה או לשחזר את רעידת האדמה האחרונה", "iris.modded.help.entry.command.status_validation": "הצגת מצב אימות", - "iris.modded.help.entry.command.enable": "ליצור ולהזריק מתמשך Iris ממד בזמן ריצה; מוריד את החבילה אם חסר, ציטוט:dimensionKey בחירת מד החבילה הספציפי", + "iris.modded.help.entry.command.enable": "יצירה והזרקה של ממד Iris קבוע בזמן ריצה; החבילה חייבת להיות מותקנת, ו-pack:dimensionKey במירכאות בוחר ממד מסוים.", "iris.modded.help.entry.command.replace_overworld": "לזרוק Iris שחקני העולם הראשיים והכבישים שם במקום ה-Villa Overworld", "iris.modded.help.entry.command.disable": "להעריך ולטעום Iris מימד; נתונים עולמיים על דיסק נשמרים עבור הפעלה מחדש", "iris.modded.help.entry.command.delete": "אכזבה Iris ממד ומנגב את הנתונים הסטולטיביים שלו מדיסק", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§cנכשל להמיר: {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7המונחים: {get} פנימה {value}", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§cכמה סנטימטרים לא הצליחו להמיר. בדוק את הקונסולה לפרטים", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "מחפש חבילה: {type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "יוצר עולם", "iris.runtime.studio.stage.apply_world_rules": "מחיל חוקי עולם", "iris.runtime.studio.stage.prepare_generator": "מכין מחולל", - "iris.runtime.studio.stage.request_entry_chunk": "טוען צ'אנק כניסה", - "iris.runtime.studio.stage.resolve_safe_entry": "מחפש נקודת ספאון בטוחה", "iris.runtime.studio.stage.teleport_player": "משגר", "iris.runtime.studio.stage.finalize_open": "מסיים", "iris.runtime.studio.stage.cleanup": "מנקה", "iris.runtime.world_create.teleport_failed": "§eהעולם נברא, אך התקשורת האוטומטית נכשלה. נסו לנסות /iris teleport world={world}", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/it_IT.json b/core/src/main/resources/languages/it_IT.json index 479a0317f..d7e26295e 100644 --- a/core/src/main/resources/languages/it_IT.json +++ b/core/src/main/resources/languages/it_IT.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§eProva una delle seguenti opzioni: overworld, vanilla, flat, theend", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§aPreparazione del mondo completata. Iris sta riavviando il server per generare/caricare \"{worldName}\".", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§cSi è verificata un'eccezione durante la creazione. Consulta la console per maggiori dettagli.", "iris.bukkit.commandiris.successfully_created_your_world": "§aCreato con successo il tuo mondo!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eLa creazione di mondi durante l'esecuzione è disattivata su Folia.", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§ePreparazione dei file del mondo e di bukkit.yml per il prossimo avvio...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§cImpossibile preparare i file del mondo per la dimensione \"{value}\".", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§aMondo Iris \"{name}\" preparato con il generatore Iris:{value} e il seed {seed}.", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§cImpossibile aggiornare bukkit.yml: {value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§cIl giocatore specificato non esiste.", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aIris v{value} di Volmit Software", "iris.bukkit.commandiris.to": "§a{value} a {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} non è un mondo Iris.", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§cImpossibile determinare la dimensione Iris per {logicalWorldName}.", "iris.bukkit.commandiris.loading_world": "§aCaricamento del mondo: {logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia non può caricare nuovi mondi a runtime. Riavviare il server per caricare \"{logicalWorldName}\".", "iris.bukkit.commandiris.loaded_successfully": "§a{logicalWorldName} caricato con successo.", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§cQuesto non è un mondo Iris. Mondi Iris: {value}", "iris.bukkit.commandiris.evacuating_world": "§aEvacuazione del mondo{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "Nome progetto non valido '{nameRaw}' (caratteri ammessi: a-z, 0-9, _ e -)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "Il Pack '{name}' esiste già in {value}.", "iris.modded.moddedstudiocommands.creating_project_from_template": "Creazione del progetto '{name}' dal modello '{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "Il modello '{template}' non è installato; download di IrisDimensions/{template2}...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "Impossibile scaricare il modello '{template}'; installa prima un Pack con dimensions/{template2}.json.", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "Il pack richiesto '{pack}' non è installato. Installalo con /iris download, quindi riavvia il server.", "iris.modded.moddedstudiocommands.created_project_at": "Progetto '{name}' creato in {value}", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "Modifica dimensions/{name}.json e il resto del Pack. È stata generata un'area di lavoro VSCode con completamento automatico basato su schema JSON; aprila o riesegui /iris studio vscode.", "iris.modded.moddedstudiocommands.project_creation_failed": "La creazione di progetti è fallita: {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "Distribuzione della regione di campionamento {value}x{value2} chunk intorno a te...", "iris.modded.moddedstudiocommands.rarity": "{key}* rarità={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "Il campionamento della regione è fallito: {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "Il Pack '{pack}' non è installato; download di IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "Impossibile scaricare il Pack '{pack}'; controlla il nome o installalo con /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "Il pack richiesto '{pack}' non è installato. Installalo con /iris download, quindi riavvia il server.", "iris.modded.moddedworldcommands.failed_inject_iris_world": "Iniezione del mondo Iris '{dimensionId}' non riuscita: {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "Creato il mondo Iris {dimensionId} dal Pack '{pack}', dimensione '{packDimension}' (seed {seed}).", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "Ora è attivo e viene iniettato a ogni avvio. Teletrasportati con /iris world status o tramite un portale; non è necessario riavviare.", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "{dimensionId} è ora configurato come mondo principale: i giocatori nell'Overworld vanilla vengono indirizzati lì all'accesso e il mondo viene iniettato a ogni avvio.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Override del mondo principale Iris rimosso. L'Overworld mantiene il generatore corrente; modifica level-type in server.properties e riavvia per ripristinarlo.", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "Seed non valido '{seedRaw}'. Usa un numero o 'random'.", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "Il Pack '{pack}' non è installato; download di IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "Impossibile scaricare il Pack '{pack}'; controlla il nome o installalo con /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "Il Pack '{pack}' non è ancora pronto: è ancora in caricamento o in convalida. Riprova tra poco.", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "Scrittura di server.properties non riuscita; controlla i permessi del file e imposta level-type manualmente.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Mondo principale Iris impostato su '{pack}' (preset {preset}, seed {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "Teletrasporta te stesso o un giocatore specificato in una dimensione Iris caricata", "iris.modded.help.entry.command.evacuate": "Teletrasporta tutti i giocatori fuori da una dimensione Iris verso lo spawn del mondo principale", "iris.modded.help.entry.command.seed": "Mostra le informazioni sui seed del mondo e dell'Engine", - "iris.modded.help.entry.command.debug": "Attiva o disattiva la registrazione di debug Iris e salva settings.json", - "iris.modded.help.entry.command.reload": "Ricarica settings.json; il file viene anche ricaricato automaticamente ogni 3 secondi", + "iris.modded.help.entry.command.debug": "Attiva o disattiva la registrazione di debug Iris e salva iris.json", + "iris.modded.help.entry.command.reload": "Ricarica iris.json; il file viene anche ricaricato automaticamente ogni 3 secondi", "iris.modded.help.entry.command.download": "Scarica un progetto Pack", "iris.modded.help.entry.command.metrics": "Mostra le metriche di generazione della dimensione Iris corrente", "iris.modded.help.entry.command.regen": "Eliminare e rigenerare i chunk vicini al posto", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "Anteprima o quarantena candidati non utilizzati", "iris.modded.help.entry.command.restore": "Anteprima o ripristinare l'ultima quarantena", "iris.modded.help.entry.command.status_validation": "Mostra lo stato di convalida cache", - "iris.modded.help.entry.command.enable": "Crea e inietta una dimensione Iris persistente durante l'esecuzione; scarica il Pack se manca e usa pack:dimensionKey tra virgolette per scegliere una dimensione specifica del Pack", + "iris.modded.help.entry.command.enable": "Crea e inietta una dimensione Iris persistente durante l’esecuzione; richiede un pack installato e pack:dimensionKey tra virgolette seleziona una dimensione specifica.", "iris.modded.help.entry.command.replace_overworld": "Inietta un mondo Iris principale e indirizza lì i giocatori al posto dell'Overworld vanilla", "iris.modded.help.entry.command.disable": "Evacua e scarica una dimensione Iris; i dati del mondo sul disco vengono conservati per poterla riabilitare", "iris.modded.help.entry.command.delete": "Disabilita una dimensione Iris e cancella dal disco i relativi dati Chunk e Mantle", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§cConversione non riuscita: {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7Convertito: {get} nel {value}", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§cNon è stato possibile convertire alcuni file schematic. Controlla la console per i dettagli.", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "Ricerca del pacchetto: {type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "Creazione mondo", "iris.runtime.studio.stage.apply_world_rules": "Applicazione regole del mondo", "iris.runtime.studio.stage.prepare_generator": "Preparazione generatore", - "iris.runtime.studio.stage.request_entry_chunk": "Caricamento chunk di ingresso", - "iris.runtime.studio.stage.resolve_safe_entry": "Ricerca spawn sicuro", "iris.runtime.studio.stage.teleport_player": "Teletrasporto", "iris.runtime.studio.stage.finalize_open": "Finalizzazione", "iris.runtime.studio.stage.cleanup": "Pulizia", "iris.runtime.world_create.teleport_failed": "§eIl mondo è stato creato, ma il teletrasporto automatico non è riuscito. Prova /iris teleport world={world}", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/ja-JP.json b/core/src/main/resources/languages/ja-JP.json index a4536fa75..aefcdfee6 100644 --- a/core/src/main/resources/languages/ja-JP.json +++ b/core/src/main/resources/languages/ja-JP.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§e次のいずれかを指定してください: overworld、vanilla、flat、theend", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§aワールドの準備が完了しました。Iris は \"{worldName}\" を生成/読み込むためにサーバーを再起動しています。", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§c作成中に例外が発生しました。詳細はコンソールを確認してください。", "iris.bukkit.commandiris.successfully_created_your_world": "§aワールドを作成しました!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eFolia では実行中のワールド作成が無効です。", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§e次回起動用のワールドファイルと bukkit.yml を準備しています...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§cディメンション \"{value}\" のワールドファイルを準備できませんでした。", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§aIris ワールド \"{name}\" をジェネレーター Iris:{value}、シード {seed} で準備しました。", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§cbukkit.yml を更新できませんでした: {value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§c指定されたプレイヤーは存在しません。", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aIris v{value} by Volmit Software", "iris.bukkit.commandiris.to": "§a{value} から {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} は Iris ワールドではありません。", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§c{logicalWorldName} の Iris ディメンションを特定できませんでした。", "iris.bukkit.commandiris.loading_world": "§aワールドを読み込んでいます: {logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia は実行中に新しいワールドを読み込めません。\"{logicalWorldName}\" を読み込むにはサーバーを再起動してください。", "iris.bukkit.commandiris.loaded_successfully": "§a{logicalWorldName} を読み込みました。", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§cここは Iris ワールドではありません。Iris ワールド: {value}", "iris.bukkit.commandiris.evacuating_world": "§aワールドから退避しています{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "無効なプロジェクト名 '{nameRaw}' です(使用可能: a-z、0-9、_、-)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "パック '{name}' は {value} にすでに存在します", "iris.modded.moddedstudiocommands.creating_project_from_template": "テンプレート '{template}' からプロジェクト '{name}' を作成しています...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "テンプレート '{template}' がインストールされていません。IrisDimensions/{template2} をダウンロードしています...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "テンプレート '{template}' をダウンロードできませんでした。先に dimensions/{template2}.json を含むパックをインストールしてください。", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "必要なパック '{pack}' がインストールされていません。/iris download でインストールしてからサーバーを再起動してください。", "iris.modded.moddedstudiocommands.created_project_at": "プロジェクト '{name}' を {value} に作成しました", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "dimensions/{name}.json とパックのほかのファイルを編集してください。JSON スキーマの自動補完に対応した VSCode ワークスペースを生成しました。開くか、/iris studio vscode を再実行してください。", "iris.modded.moddedstudiocommands.project_creation_failed": "プロジェクトを作成できませんでした: {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "周囲 {value}x{value2} チャンクのリージョン分布をサンプリングしています...", "iris.modded.moddedstudiocommands.rarity": "{key}: レア度={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "リージョンのサンプリングに失敗しました: {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "パック '{pack}' がインストールされていません。IrisDimensions/{pack2} をダウンロードしています...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "パック '{pack}' をダウンロードできませんでした。名前を確認するか、/iris download {pack2} でインストールしてください。", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "必要なパック '{pack}' がインストールされていません。/iris download でインストールしてからサーバーを再起動してください。", "iris.modded.moddedworldcommands.failed_inject_iris_world": "Iris ワールド '{dimensionId}' を注入できませんでした: {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "Iris ワールド {dimensionId} を、パック '{pack}' のディメンション '{packDimension}'(シード {seed})から作成しました。", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "現在使用可能で、起動するたびに再注入されます。/iris world status またはポータルからテレポートできます。再起動は不要です。", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "代わりに {dimensionId} をプライマリーワールドとして設定しました。バニラのオーバーワールドにいるプレイヤーは参加時にそこへ移動し、起動するたびに再注入されます。", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Iris メインワールドの上書きを解除しました。オーバーワールドは現在のジェネレーターを維持します。元に戻すには server.properties の level-type を編集して再起動してください。", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "無効なシード '{seedRaw}' です。数値または 'random' を指定してください。", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "パック '{pack}' がインストールされていません。IrisDimensions/{pack2} をダウンロードしています...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "パック '{pack}' をダウンロードできませんでした。名前を確認するか、/iris download {pack2} でインストールしてください。", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "パック '{pack}' はまだ準備中です(読み込みまたは検証中)。しばらくしてからもう一度コマンドを実行してください。", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "server.properties を書き込めませんでした。ファイル権限を確認し、level-type を手動で設定してください。", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Iris メインワールドを '{pack}'(プリセット {preset}、シード {value})に設定しました。", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "自分または指定したプレイヤーを読み込み済みの Iris ディメンションへテレポートします", "iris.modded.help.entry.command.evacuate": "Iris ディメンション内の全プレイヤーをプライマリーワールドのスポーン地点へ退避させます", "iris.modded.help.entry.command.seed": "ワールドとエンジンのシード情報を表示します", - "iris.modded.help.entry.command.debug": "Iris のデバッグログを切り替えて settings.json を保存します", - "iris.modded.help.entry.command.reload": "settings.json を再読み込みします(3s ごとの自動ホットロードにも対応)", + "iris.modded.help.entry.command.debug": "Iris のデバッグログを切り替えて iris.json を保存します", + "iris.modded.help.entry.command.reload": "iris.json を再読み込みします(3s ごとの自動ホットロードにも対応)", "iris.modded.help.entry.command.download": "パックプロジェクトをダウンロードします", "iris.modded.help.entry.command.metrics": "現在の Iris ディメンションの生成メトリクスを表示します", "iris.modded.help.entry.command.regen": "周囲のチャンクを削除し、その場で再生成します", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "未使用リソース候補をプレビューまたは隔離します", "iris.modded.help.entry.command.restore": "最新の隔離内容をプレビューまたは復元します", "iris.modded.help.entry.command.status_validation": "キャッシュ済みの検証状態を表示します", - "iris.modded.help.entry.command.enable": "永続的な Iris ディメンションを実行中に作成して注入します。不足しているパックはダウンロードします。特定のパックディメンションを選ぶには pack:dimensionKey を引用符で囲んで指定します", + "iris.modded.help.entry.command.enable": "永続的な Iris ディメンションを実行中に作成して追加します。パックは事前にインストールする必要があり、引用符で囲んだ pack:dimensionKey で特定のディメンションを選択できます。", "iris.modded.help.entry.command.replace_overworld": "Iris プライマリーワールドを注入し、バニラのオーバーワールドではなくそちらへプレイヤーを移動します", "iris.modded.help.entry.command.disable": "Iris ディメンションから退避してアンロードします。再び有効にできるよう、ディスク上のワールドデータは保持します", "iris.modded.help.entry.command.delete": "Iris ディメンションを無効にし、ディスク上のチャンクと Mantle データを消去します", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§c変換に失敗しました: {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7変換完了: {get}({value})", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§c一部の schematic を変換できませんでした。詳細はコンソールを確認してください。", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "パッケージを探します: {type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "ワールドを作成中", "iris.runtime.studio.stage.apply_world_rules": "ワールドルールを適用中", "iris.runtime.studio.stage.prepare_generator": "ジェネレーターを準備中", - "iris.runtime.studio.stage.request_entry_chunk": "開始チャンクを読み込み中", - "iris.runtime.studio.stage.resolve_safe_entry": "安全なスポーン地点を検索中", "iris.runtime.studio.stage.teleport_player": "テレポート中", "iris.runtime.studio.stage.finalize_open": "完了処理中", "iris.runtime.studio.stage.cleanup": "クリーンアップ中", "iris.runtime.world_create.teleport_failed": "§eワールドを作成しましたが、自動テレポートに失敗しました。/iris teleport world={world} をお試しください", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/ko_KR.json b/core/src/main/resources/languages/ko_KR.json index 084ed2923..567af8bab 100644 --- a/core/src/main/resources/languages/ko_KR.json +++ b/core/src/main/resources/languages/ko_KR.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§e중 하나를 시도: overworld, 바닐라, 플랫, theend", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§a월드 준비가 완료되었습니다. Iris가 \"{worldName}\"을 생성/로드하기 위해 서버를 재시작하고 있습니다.", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§c창조 중 발생한 예외. 자세한 내용은 콘솔을 참조하십시오.", "iris.bukkit.commandiris.successfully_created_your_world": "§a세상을 성공적으로 만들었습니다!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eRuntime 세계 생성은 비활성화됩니다 Folia.", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§e세계 파일 및 준비 bukkit.yml 다음 시작...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§c차원을 위한 단계 세계 파일에 실패 \"{value}\".", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§aIris 월드 \"{name}\"을 생성기 Iris:{value}, 시드 {seed}로 준비했습니다.", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§c업데이트 실패 bukkit.yml: {value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§c지정된 플레이어는 존재하지 않습니다.", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§a아이리스 v{value} Volmit 소프트웨어", "iris.bukkit.commandiris.to": "§a{value} 으로 {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} 아이리스 세계는 아닙니다.", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§c결정할 수 없습니다. Iris 차원을 위한 {logicalWorldName}.", "iris.bukkit.commandiris.loading_world": "§a선적 세계: {logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia는 런타임에 새로운 세상을 로드할 수 없습니다. 서버를 다시로드 \"{logicalWorldName}\".", "iris.bukkit.commandiris.loaded_successfully": "§a{logicalWorldName} 로드 되었습니다.", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§c이것은 아니다 Iris 세계. Iris 세계: {value}", "iris.bukkit.commandiris.evacuating_world": "§a관련 제품{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "잘못된 프로젝트 이름 '{nameRaw}' (사용 가능: a-z, 0-9, _, -)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "팩 '{name}' 이미 존재 {value}", "iris.modded.moddedstudiocommands.creating_project_from_template": "프로젝트 만들기{name}템플릿에서 '{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "템플릿 '{template}'는 설치되지 않습니다; 다운로드 IrisDimensions/{template2}...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "템플릿 '{template}'다운로드 할 수 없습니다; 팩을 설치 dimensions/{template2}.json 처음.", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "필수 팩 '{pack}'이(가) 설치되어 있지 않습니다. /iris download로 설치한 다음 서버를 다시 시작하세요.", "iris.modded.moddedstudiocommands.created_project_at": "프로젝트 '{name}'을(를) {value}에 만들었습니다", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "dimensions/{name}.json를 편집하고 팩의 나머지.JSON 스키마 자동 완성이 생성 된 VSCode 작업 공간; /iris studio vscode를 엽니 다.", "iris.modded.moddedstudiocommands.project_creation_failed": "프로젝트 생성 실패: {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "샘플링 지역 배포 {value}x{value2} 너 주변의 청크...", "iris.modded.moddedstudiocommands.rarity": "{key}: rarity ={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "지역 표본 추출 실패: {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "팩 '{pack}'는 설치되지 않습니다; 다운로드 IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "팩 '{pack}'다운로드 할 수 없습니다; 이름을 확인하거나 함께 설치 /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "필수 팩 '{pack}'이(가) 설치되어 있지 않습니다. /iris download로 설치한 다음 서버를 다시 시작하세요.", "iris.modded.moddedworldcommands.failed_inject_iris_world": "Iris 월드 '{dimensionId}' 주입 실패: {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "팩 '{pack}'의 차원 '{packDimension}'(시드 {seed})에서 Iris 월드 {dimensionId}을(를) 만들었습니다.", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "그것은 지금 살고 모든 시작에 거부. Teleport와 함께 /iris world status 또는 포털; 재시작 필요 없음.", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "대신 {dimensionId}을(를) 기본 월드로 설정했습니다. 바닐라 오버월드의 플레이어는 접속 시 해당 월드로 이동하며, 시작할 때마다 다시 주입됩니다.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Iris 뚜렷한 주요 세계. overworld는 그것의 현재 생성기를 지킵니다; 편집 server.properties 레벨 타입과 다시 변경합니다.", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "잘못된 씨앗 '{seedRaw}'. 숫자 또는 'random'를 사용합니다.", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "팩 '{pack}'는 설치되지 않습니다; 다운로드 IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "팩 '{pack}'다운로드 할 수 없습니다; 이름을 확인하거나 함께 설치 /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "팩 '{pack}'는 아직 준비되지 않습니다 (실 적재 또는 검증). 현재 명령을 다시 시도하십시오.", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "쓰기 실패 server.properties; 파일 권한 확인 및 수동 레벨 유형 설정.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Iris 주요 세계는 '{pack}' (이전 {preset}, 씨 {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "자신 또는 지정한 플레이어를 로드된 Iris 차원으로 순간이동합니다", "iris.modded.help.entry.command.evacuate": "Iris 차원의 모든 플레이어를 기본 월드 스폰으로 대피시킵니다", "iris.modded.help.entry.command.seed": "월드와 엔진의 시드 정보를 표시합니다", - "iris.modded.help.entry.command.debug": "Iris 디버그 로깅을 전환하고 settings.json을 저장합니다", - "iris.modded.help.entry.command.reload": "settings.json을 다시 로드합니다(3s마다 자동 핫로드도 수행)", + "iris.modded.help.entry.command.debug": "Iris 디버그 로깅을 전환하고 iris.json을 저장합니다", + "iris.modded.help.entry.command.reload": "iris.json을 다시 로드합니다(3s마다 자동 핫로드도 수행)", "iris.modded.help.entry.command.download": "팩 프로젝트를 다운로드합니다", "iris.modded.help.entry.command.metrics": "현재 Iris 차원의 생성 측정값을 표시합니다", "iris.modded.help.entry.command.regen": "주변 청크를 삭제하고 제자리에서 다시 생성합니다", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "미사용 리소스 후보를 미리 보거나 격리합니다", "iris.modded.help.entry.command.restore": "최근 격리 항목을 미리 보거나 복원합니다", "iris.modded.help.entry.command.status_validation": "캐시된 검증 상태를 표시합니다", - "iris.modded.help.entry.command.enable": "영구 Iris 차원을 실행 중에 만들고 주입합니다. 없는 팩은 다운로드합니다. 특정 팩 차원을 선택하려면 pack:dimensionKey를 따옴표로 감싸세요", + "iris.modded.help.entry.command.enable": "영구 Iris 차원을 실행 중에 만들고 주입합니다. 팩이 설치되어 있어야 하며 따옴표로 묶은 pack:dimensionKey로 특정 차원을 선택할 수 있습니다.", "iris.modded.help.entry.command.replace_overworld": "Iris 기본 월드를 주입하고 바닐라 오버월드 대신 그곳으로 플레이어를 이동합니다", "iris.modded.help.entry.command.disable": "Iris 차원에서 대피하고 언로드합니다. 다시 활성화할 수 있도록 디스크의 월드 데이터는 유지합니다", "iris.modded.help.entry.command.delete": "Iris 차원을 비활성화하고 디스크의 청크와 Mantle 데이터를 삭제합니다", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§c변환 실패: {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7변환 완료: {get}, 소요 {value}", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§c일부 설계도 변환 실패. 자세한 내용은 콘솔을 확인하십시오.", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "포장을 위한 보기: {type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "월드 생성 중", "iris.runtime.studio.stage.apply_world_rules": "월드 규칙 적용 중", "iris.runtime.studio.stage.prepare_generator": "생성기 준비 중", - "iris.runtime.studio.stage.request_entry_chunk": "입장 청크 로드 중", - "iris.runtime.studio.stage.resolve_safe_entry": "안전한 스폰 지점 검색 중", "iris.runtime.studio.stage.teleport_player": "순간이동 중", "iris.runtime.studio.stage.finalize_open": "마무리 중", "iris.runtime.studio.stage.cleanup": "정리 중", "iris.runtime.world_create.teleport_failed": "§e월드를 만들었지만 자동 순간이동에 실패했습니다. /iris teleport world={world}을(를) 사용해 보세요", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/lt_LT.json b/core/src/main/resources/languages/lt_LT.json index e077b4446..6bf4b5cb5 100644 --- a/core/src/main/resources/languages/lt_LT.json +++ b/core/src/main/resources/languages/lt_LT.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§ePabandykite vieną iš: per pasaulį, vanilla, butas, pabaiga", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§aPasaulio paruošimas baigtas. Iris dabar paleidžia serverį iš naujo, kad sugeneruotų / įkeltų „{worldName}“.", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§cSukūrimo metu iškelta išimtis. Daugiau informacijos rasite konsolėje.", "iris.bukkit.commandiris.successfully_created_your_world": "§aSėkmingai sukūrė savo pasaulį!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eweather forecast Folia.", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§eRuošiami pasauliniai failai ir bukkit.yml kitam paleidimui...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§cNepavyko sukurti pasaulinių matmenų failų \"{value}\".", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§aParuoštas Iris pasaulis „{name}“ su generatoriumi Iris:{value} ir pradine reikšme {seed}.", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§cNepavyko atnaujinti bukkit.yml: {value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§cNurodytasis žaidėjas neegzistuoja.", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aIris prieš{value} pagal Volmit programinė įranga", "iris.bukkit.commandiris.to": "§a{value} iki {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} nėra rainelės pasaulis.", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§cNepavyko nustatyti Iris dydis {logicalWorldName}.", "iris.bukkit.commandiris.loading_world": "§aĮkeliamas pasaulis: {logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolicija negali pakrauti naujų pasaulių. Iš naujo įkelti serverį \"{logicalWorldName}\".", "iris.bukkit.commandiris.loaded_successfully": "§a{logicalWorldName} sėkmingai įkelta.", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§cTai nėra Iris Pasaulis. Iris pasauliai: {value}", "iris.bukkit.commandiris.evacuating_world": "§aEvakuojantis pasaulis{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "Netinkamas projekto pavadinimas \"{nameRaw}\"(leidžiama: a- z, 0-9, _ ir -)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "Pakuotė \"{name}\"jau egzistuoja {value}", "iris.modded.moddedstudiocommands.creating_project_from_template": "Kuriamas projektas \"{name}\"iš šablono\"{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "Šablonas \"{template}\"nėra įdiegtas; parsisiuntimas IrisDimensions/{template2}...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "Šablonas \"{template}\"negali būti atsisiųsti; įdiegti pakuotės dimensions/{template2}.json Pirma.", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "Reikalingas paketas '{pack}' neįdiegtas. Įdiekite jį naudodami /iris download, tada paleiskite serverį iš naujo.", "iris.modded.moddedstudiocommands.created_project_at": "Projektas '{name}' sukurtas adresu {value}", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "Keisti dimensions/{name}.json ir likusią pakuotę. A VSCode darbo erdvė, JSON schema autocomplete buvo sukurta; atidaryti arba release it /iris studio vscode.", "iris.modded.moddedstudiocommands.project_creation_failed": "Projekto kūrimas nepavyko: {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "Mėginių ėmimo regiono pasiskirstymas {value}x{value2} kekšės aplink tave...", "iris.modded.moddedstudiocommands.rarity": "{key}: retumas ={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "Regiono mėginių ėmimas nepavyko: {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "Pakuotė \"{pack}\"nėra įdiegtas; parsisiuntimas IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "Pakuotė \"{pack}\"negali būti atsisiųsti; patikrinti pavadinimą arba įdiegti jį su /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "Reikalingas paketas '{pack}' neįdiegtas. Įdiekite jį naudodami /iris download, tada paleiskite serverį iš naujo.", "iris.modded.moddedworldcommands.failed_inject_iris_world": "Nepavyko sušvirkšti Iris pasaulis \"{dimensionId}': {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "Sukurta Iris pasaulis {dimensionId} iš pakuotės \"{pack}\"matmuo\"{packDimension}\"(sėklos {seed}).", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "Jis gyvena dabar ir pakartotinai įšvirkščiamas į kiekvieną paleidimą. Teleportas su /iris world status arba portalas; nereikia paleisti iš naujo.", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "Vietoj to, {dimensionId} dabar sukonfigūruotas pirminis pasaulis: žaidėjai vanilla overworld yra nukreipiami ten prisijungti, ir jis re- objektai kiekvieną startuolį.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Iris pagrindinis pasaulis išsilaisvino. Pasaulis išlaiko savo dabartinį generatorių; redaguoti server.properties lygiuoti ir iš naujo jį pakeisti atgal.", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "Netinkama sėkla \"{seedRaw}'. Naudokite skaičių arba \"atsitiktinis\".", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "Pakuotė \"{pack}\"nėra įdiegtas; parsisiuntimas IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "Pakuotė \"{pack}\"negali būti atsisiųsti; patikrinti pavadinimą arba įdiegti jį su /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "Pakuotė \"{pack}\"dar neparuoštas (vis dar pakraunamas arba patvirtinamas). Pabandyk dar kartą.", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "Nepavyko įrašyti server.properties; patikrinti failų leidimus ir nustatyti lygio tipo rankiniu būdu.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Iris pagrindinis pasaulis nustatytas į \"{pack}\"(preset {preset}, sėklos {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "Teleportuoti save arba pavadintą žaidėją į pakrautą Iris matmuo", "iris.modded.help.entry.command.evacuate": "Teleportuoti visus žaidėjus iš Iris matmens į pagrindinio pasaulio atsiradimo vietą", "iris.modded.help.entry.command.seed": "Rodyti pasaulio ir generatoriaus seed informaciją", - "iris.modded.help.entry.command.debug": "Perjungti Iris Derinti prisijungimą ir įrašymą settings.json", - "iris.modded.help.entry.command.reload": "Įkelti iš naujo settings.json (taip pat įkeliamas automatiškai kas 3s)", + "iris.modded.help.entry.command.debug": "Perjungti Iris Derinti prisijungimą ir įrašymą iris.json", + "iris.modded.help.entry.command.reload": "Įkelti iš naujo iris.json (taip pat įkeliamas automatiškai kas 3s)", "iris.modded.help.entry.command.download": "Parsisiųsti paketo projektą", "iris.modded.help.entry.command.metrics": "Spausdinti kartos metrikos savo dabartinę Iris matmuo", "iris.modded.help.entry.command.regen": "Pašalinti ir regeneruoti netoliese gabaliukus vietoje", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "Peržiūra arba karantinas nenaudojami ištekliai kandidatai", "iris.modded.help.entry.command.restore": "Peržiūrėti arba atkurti naujausią karantiną", "iris.modded.help.entry.command.status_validation": "Rodyti kabineto patvirtinimo būseną", - "iris.modded.help.entry.command.enable": "Sukurti ir švirkšti nuolatinį Iris matmuo kilimo metu; parsisiunčia pakuotę, jei jos nėra, citavimo pakuotę:dimensionKey pasirinkti konkretų pakuotės matmenį", + "iris.modded.help.entry.command.enable": "Sukurti ir įkelti nuolatinį Iris matmenį vykdymo metu; paketas turi būti įdiegtas, o kabutėse pateiktas pack:dimensionKey parenka konkretų matmenį.", "iris.modded.help.entry.command.replace_overworld": "Švirkšti Iris pagrindinis pasaulis ir maršruto žaidėjai ten vietoj vanilla pasaulyje", "iris.modded.help.entry.command.disable": "Evakuoti ir iškrauti Iris dimensija; pasaulio duomenys diske yra saugomi įgalinimui", "iris.modded.help.entry.command.delete": "Išjungti Iris dimensija ir nušluostyti savo gabalas ir Mantle duomenis iš disko", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§cNepavyko konvertuoti: {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7Konvertuotas: {get} yra {value}", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§cKai schemos nepavyko konvertuoti. Patikrinkite konsolės detales.", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "Ieškoma paketo: {type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "Kuriamas pasaulis", "iris.runtime.studio.stage.apply_world_rules": "Taikomos pasaulio taisyklės", "iris.runtime.studio.stage.prepare_generator": "Ruošiamas generatorius", - "iris.runtime.studio.stage.request_entry_chunk": "Įkeliamas pradinis chunkas", - "iris.runtime.studio.stage.resolve_safe_entry": "Ieškoma saugios atsiradimo vietos", "iris.runtime.studio.stage.teleport_player": "Teleportuojama", "iris.runtime.studio.stage.finalize_open": "Užbaigiama", "iris.runtime.studio.stage.cleanup": "Valoma", "iris.runtime.world_create.teleport_failed": "§ePasaulis buvo sukurtas, bet automatinis teleportas nepavyko. Stenkitės /iris teleport world={world}", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/nl_NL.json b/core/src/main/resources/languages/nl_NL.json index 3ac712283..4b9ba84de 100644 --- a/core/src/main/resources/languages/nl_NL.json +++ b/core/src/main/resources/languages/nl_NL.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§eProbeer een van: overwereld, vanilla, plat, einde", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§aVoorbereiding van de wereld voltooid. Iris herstart de server nu om \"{worldName}\" te genereren/laden.", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§cUitzondering bij de schepping. Zie de console voor meer details.", "iris.bukkit.commandiris.successfully_created_your_world": "§aSuccesvol jullie wereld gecreëerd!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eRuntime wereld creatie is uitgeschakeld op Folia.", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§eVoorbereiden van wereldbestanden en bukkit.yml voor volgende opstart...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§cKon wereldbestanden voor dimensie niet stagen \"{value}\".", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§aIris-wereld \"{name}\" voorbereid met generator Iris:{value} en seed {seed}.", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§cBijwerken is mislukt bukkit.yml: {value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§cDe opgegeven speler bestaat niet.", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aIris /{value} door Volmit Software", "iris.bukkit.commandiris.to": "§a{value} tot {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} is geen iris wereld.", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§cKon niet bepalen Iris dimensie voor {logicalWorldName}.", "iris.bukkit.commandiris.loading_world": "§aLaadwereld: {logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia kan geen nieuwe werelden laden op runtime. Herstart de server om \" te laden{logicalWorldName}\".", "iris.bukkit.commandiris.loaded_successfully": "§a{logicalWorldName} geladen.", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§cDit is geen Iris wereld. Iris werelden: {value}", "iris.bukkit.commandiris.evacuating_world": "§aEvacuatiewereld{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "Ongeldige projectnaam '{nameRaw}\" (toegestaan: a-z, 0-9, en -)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "Verpakking{name}' bestaat reeds op {value}", "iris.modded.moddedstudiocommands.creating_project_from_template": "Project aanmaken '{name}\"uit template \"{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "Sjabloon '{template}' is niet geïnstalleerd; downloaden IrisDimensions/{template2}...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "Sjabloon '{template}' kon niet worden gedownload; installeer een pakket met dimensions/{template2}.json Eerst.", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "Het vereiste pack '{pack}' is niet geïnstalleerd. Installeer het met /iris download en start de server daarna opnieuw.", "iris.modded.moddedstudiocommands.created_project_at": "Project aangemaakt '{name}\" op {value}", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "Bewerken dimensions/{name}.json En de rest van de roedel. A VSCode werkruimte met JSON schema automatisch aanvullen werd gegenereerd; open het of rerun /iris studio vscode.", "iris.modded.moddedstudiocommands.project_creation_failed": "Aanmaken van project mislukt: {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "Verdeling van de steekproefgebieden in {value}x{value2} chunks om je heen...", "iris.modded.moddedstudiocommands.rarity": "{key}: zeldzaamheid={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "Regionale bemonstering mislukt: {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "Verpakking{pack}' is niet geïnstalleerd; downloaden IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "Verpakking{pack}' kon niet worden gedownload; controleer de naam of installeer deze met /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "Het vereiste pack '{pack}' is niet geïnstalleerd. Installeer het met /iris download en start de server daarna opnieuw.", "iris.modded.moddedworldcommands.failed_inject_iris_world": "Spuiten mislukt Iris wereld{dimensionId}': {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "Aangemaakt Iris wereld {dimensionId} uit verpakking{pack}' dimensie'{packDimension}\" (zaad {seed}).", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "Het is nu live en opnieuw bij elke opstart. Teleporteren met /iris world status of een portaal; geen herstart vereist.", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "In plaats daarvan, {dimensionId} is nu de geconfigureerde primaire wereld: spelers in de vanilla-overwereld worden daarheen geleid op join, en het opnieuw objecteert bij elke startup.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Iris De belangrijkste wereld override is vrijgegeven. De overworld houdt zijn huidige generator; bewerken server.properties niveau-type en herstart om het terug te veranderen.", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "Ongeldig zaad '{seedRaw}'. Gebruik een getal of 'random'.", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "Verpakking{pack}' is niet geïnstalleerd; downloaden IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "Verpakking{pack}' kon niet worden gedownload; controleer de naam of installeer deze met /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "Verpakking{pack}' is nog niet klaar (nog laden of valideren). Probeer het commando zo weer.", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "Schrijven is mislukt server.properties; controleer bestandsrechten en stel het niveau-type handmatig in.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Iris belangrijkste wereld ingesteld op '{pack}\" (vooruitgezet {preset}, zaad {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "Teleporteer jezelf of een genoemde speler naar een geladen Iris dimensie", "iris.modded.help.entry.command.evacuate": "Teleporteer alle spelers uit een Iris-dimensie naar de spawn van de primaire wereld", "iris.modded.help.entry.command.seed": "Wereld- en engine-seedinformatie tonen", - "iris.modded.help.entry.command.debug": "Schakelen Iris debug loggen en opslaan settings.json", - "iris.modded.help.entry.command.reload": "Herladen settings.json (ook automatisch gehotload elke 3s)", + "iris.modded.help.entry.command.debug": "Schakelen Iris debug loggen en opslaan iris.json", + "iris.modded.help.entry.command.reload": "Herladen iris.json (ook automatisch gehotload elke 3s)", "iris.modded.help.entry.command.download": "Een pakketproject downloaden", "iris.modded.help.entry.command.metrics": "Generatiegegevens afdrukken voor uw huidige Iris dimensie", "iris.modded.help.entry.command.regen": "Verwijderen en regenereren nabijgelegen chunks in plaats", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "Preview of quarantaine kandidaten voor niet-gebruikte bronnen", "iris.modded.help.entry.command.restore": "De nieuwste quarantaine bekijken of herstellen", "iris.modded.help.entry.command.status_validation": "Gecachede validatiestatus tonen", - "iris.modded.help.entry.command.enable": "Creëer en injecteer een persistent Iris dimensie op runtime; downloadt het pakket indien ontbreken, offertepakket:dimensionKey om een specifieke verpakkingsdimensie te kiezen", + "iris.modded.help.entry.command.enable": "Maak en injecteer tijdens runtime een blijvende Iris-dimensie; het pack moet geïnstalleerd zijn en een aangehaalde pack:dimensionKey selecteert een specifieke dimensie.", "iris.modded.help.entry.command.replace_overworld": "Injecteer een Iris primaire wereld en route spelers er in plaats van de vanilla bovenwereld", "iris.modded.help.entry.command.disable": "Evacueer en ontlaad een Iris dimensie; wereldgegevens op schijf worden bewaard voor her-enabling", "iris.modded.help.entry.command.delete": "Schakel een Iris dimensie en wis zijn chunk en Mantle data van schijf", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§cConverteren mislukt: {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7Omgezet: {get} de {value}", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§cSommige schema's konden niet worden omgezet. Controleer de console voor details.", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "Op zoek naar pakket: {type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "Wereld maken", "iris.runtime.studio.stage.apply_world_rules": "Wereldregels toepassen", "iris.runtime.studio.stage.prepare_generator": "Generator voorbereiden", - "iris.runtime.studio.stage.request_entry_chunk": "Ingangschunk laden", - "iris.runtime.studio.stage.resolve_safe_entry": "Veilige spawn zoeken", "iris.runtime.studio.stage.teleport_player": "Teleporteren", "iris.runtime.studio.stage.finalize_open": "Afronden", "iris.runtime.studio.stage.cleanup": "Opruimen", "iris.runtime.world_create.teleport_failed": "§eDe wereld werd gecreëerd, maar de automatische teleportatie mislukte. Probeer /iris teleport world={world}", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/pl_PL.json b/core/src/main/resources/languages/pl_PL.json index 20c4ab757..4741366ac 100644 --- a/core/src/main/resources/languages/pl_PL.json +++ b/core/src/main/resources/languages/pl_PL.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§eSpróbuj jeden z: zaświaty, vanilla, płaski, koniec", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§aPrzygotowanie świata ukończone. Iris uruchamia teraz serwer ponownie, aby wygenerować/wczytać „{worldName}“.", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§cWyjątek podniesiony podczas tworzenia. Więcej szczegółów znajdziesz w konsoli.", "iris.bukkit.commandiris.successfully_created_your_world": "§aZ powodzeniem stworzyłeś swój świat!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eRuntime world creation jest wyłączone na Folia.", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§ePrzygotowanie światowych plików i bukkit.yml dla następnego startup...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§cNie udało się sscenizować plików świata dla wymiaru \"{value}\".", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§aPrzygotowano świat Iris „{name}” z generatorem Iris:{value} i ziarnem {seed}.", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§cNie udało się zaktualizować bukkit.yml: {value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§cOkreślony gracz nie istnieje.", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aIris v.{value} przez Volmit Software", "iris.bukkit.commandiris.to": "§a{value} do {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} nie jest światem tęczówki.", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§cNie można określić Iris wymiar {logicalWorldName}.", "iris.bukkit.commandiris.loading_world": "§aWczytywanie świata: {logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia nie może ładować nowych światów. Przywróć serwer do wczytania \"{logicalWorldName}\".", "iris.bukkit.commandiris.loaded_successfully": "§a{logicalWorldName} załadowany pomyślnie.", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§cTo nie jest... Iris Świat. Iris światy: {value}", "iris.bukkit.commandiris.evacuating_world": "§aEwakuacja świata{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "Nieprawidłowa nazwa projektu \"{nameRaw}\"(dozwolone: a- z, 0-9, _ i -)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "Paczka \"{name}\"już istnieje w {value}", "iris.modded.moddedstudiocommands.creating_project_from_template": "Tworzenie projektu \"{name}\"od wzoru\"{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "Szablon \"{template}\"nie jest zainstalowany; pobieranie IrisDimensions/{template2}...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "Szablon \"{template}'nie można pobrać; zainstalować opakowanie z dimensions/{template2}.json Najpierw.", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "Wymagany pakiet '{pack}' nie jest zainstalowany. Zainstaluj go poleceniem /iris download, a następnie uruchom serwer ponownie.", "iris.modded.moddedstudiocommands.created_project_at": "Utworzono projekt '{name}' w {value}", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "Edycja dimensions/{name}.json i resztę stada. A VSCode przestrzeń robocza z JSON wygenerowano schemat autocomplete; otwórz go lub uruchom ponownie /iris studio vscode.", "iris.modded.moddedstudiocommands.project_creation_failed": "Tworzenie projektu nie powiodło się: {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "Dystrybucja w regionie pobierania próbek {value}x{value2} chunki wokół ciebie...", "iris.modded.moddedstudiocommands.rarity": "{key}: rzadkość ={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "Pobieranie próbek w regionie nie powiodło się: {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "Paczka \"{pack}\"nie jest zainstalowany; pobieranie IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "Paczka \"{pack}'nie można pobrać; sprawdź nazwę lub zainstalować go /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "Wymagany pakiet '{pack}' nie jest zainstalowany. Zainstaluj go poleceniem /iris download, a następnie uruchom serwer ponownie.", "iris.modded.moddedworldcommands.failed_inject_iris_world": "Nie można wykonać wstrzyknięcia Iris Świat \"{dimensionId}': {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "Stworzony Iris świat {dimensionId} od opakowania \"{pack}\"wymiar\"{packDimension}\"(nasiona {seed}).", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "To jest teraz na żywo i ponownie wstrzyknięte na każdym startup. Teleport /iris world status lub portalu; nie jest wymagane ponowne uruchomienie.", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "Zamiast tego, {dimensionId} jest teraz skonfigurowanym światem podstawowym: gracze w zaświatach vanilla są tam kierowane na joint, i to ponownych injects na każdym startup.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Iris Główna kontrola świata czysta. Overworld utrzymuje obecny generator; edytuj server.properties typu poziomego i ponownie zacząć zmieniać go z powrotem.", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "Nieprawidłowy materiał siewny \"{seedRaw}'. Użyj liczby lub \"losowe\".", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "Paczka \"{pack}\"nie jest zainstalowany; pobieranie IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "Paczka \"{pack}'nie można pobrać; sprawdź nazwę lub zainstalować go /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "Paczka \"{pack}\"nie jest jeszcze gotowy (nadal załadunek lub walidacja). Spróbuj jeszcze raz.", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "Nie udało się zapisać server.properties; sprawdzić uprawnienia plików i ręcznie ustawić typ level- type.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Iris główny świat ustawiony na \"{pack}\"(wstępnie {preset}, nasiona {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "Teleport samodzielnie lub nazwany gracz do załadowanego Iris wymiar", "iris.modded.help.entry.command.evacuate": "Teleportuj wszystkich graczy z wymiaru Iris do spawnu głównego świata", "iris.modded.help.entry.command.seed": "Wyświetl informacje o seedzie świata i silnika", - "iris.modded.help.entry.command.debug": "Włączenie / wyłączenie Iris debugowanie logowania i zapisywanie settings.json", - "iris.modded.help.entry.command.reload": "Wczytaj ponownie settings.json (także automatycznie naświetlane co 3)", + "iris.modded.help.entry.command.debug": "Włączenie / wyłączenie Iris debugowanie logowania i zapisywanie iris.json", + "iris.modded.help.entry.command.reload": "Wczytaj ponownie iris.json (także automatycznie naświetlane co 3)", "iris.modded.help.entry.command.download": "Pobierz projekt pakietu", "iris.modded.help.entry.command.metrics": "Wydrukuj wskaźniki generacji dla bieżącego Iris wymiar", "iris.modded.help.entry.command.regen": "Usuń i zregeneruj pobliskie chunki w miejscu", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "Podgląd lub kwarantanna nieużytkowanych kandydatów zasobów", "iris.modded.help.entry.command.restore": "Podgląd lub przywrócenie najnowszej kwarantanny", "iris.modded.help.entry.command.status_validation": "Pokaż status walidacji w pamięci masowej", - "iris.modded.help.entry.command.enable": "Utwórz i wstrzyknij trwałe Iris wymiar w runtime; pobiera opakowanie jeśli brakuje, cudzysłów:dimensionKey wybrać określony wymiar opakowania", + "iris.modded.help.entry.command.enable": "Utwórz i wstrzyknij trwały wymiar Iris w czasie działania; pakiet musi być zainstalowany, a pack:dimensionKey w cudzysłowie wybiera konkretny wymiar.", "iris.modded.help.entry.command.replace_overworld": "Wstrzyknąć Iris podstawowy świat i trasa zawodników tam zamiast vanilla roverworld", "iris.modded.help.entry.command.disable": "Ewakuować i rozładować Iris wymiar; światowe dane na dysku są przechowywane do ponownego włączenia", "iris.modded.help.entry.command.delete": "Wyłącz Iris wymiar i wytrzeć jego chunki i Mantle dane z dysku", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§cNie można przekonwertować: {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7Przekonwertowany: {get} w {value}", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§cNiektóre schematy nie mogły się zmienić. Sprawdź w konsoli szczegóły.", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "Szukam pakietu: {type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "Tworzenie świata", "iris.runtime.studio.stage.apply_world_rules": "Stosowanie zasad świata", "iris.runtime.studio.stage.prepare_generator": "Przygotowywanie generatora", - "iris.runtime.studio.stage.request_entry_chunk": "Ładowanie chunku wejściowego", - "iris.runtime.studio.stage.resolve_safe_entry": "Szukanie bezpiecznego spawnu", "iris.runtime.studio.stage.teleport_player": "Teleportowanie", "iris.runtime.studio.stage.finalize_open": "Finalizowanie", "iris.runtime.studio.stage.cleanup": "Czyszczenie", "iris.runtime.world_create.teleport_failed": "§eŚwiat został stworzony, ale automatyczny teleport zawiódł. Spróbuj /iris teleport world={world}", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/pt_PT.json b/core/src/main/resources/languages/pt_PT.json index b66a37265..a2e4dcbe4 100644 --- a/core/src/main/resources/languages/pt_PT.json +++ b/core/src/main/resources/languages/pt_PT.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§eExperimente um de: overworld, vanilla, flat, theend", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§aPreparação do mundo concluída. O Iris está agora a reiniciar o servidor para gerar/carregar \"{worldName}\".", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§cExceção levantada durante a criação. Veja o console para mais detalhes.", "iris.bukkit.commandiris.successfully_created_your_world": "§aCriou com sucesso o seu mundo!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eA criação do mundo em execução está desactivada Folia.", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§ePreparando arquivos mundiais e bukkit.yml para a próxima inicialização...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§cFalha ao encenar arquivos mundiais para a dimensão \"{value}\".", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§aMundo Iris \"{name}\" preparado com o gerador Iris:{value} e a seed {seed}.", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§cFalha ao atualizar bukkit.yml: {value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§cO jogador especificado não existe.", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aÍris v{value} por Volmit Software", "iris.bukkit.commandiris.to": "§a{value} para {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} Não é um mundo íris.", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§cNão foi possível determinar Iris dimensão para {logicalWorldName}.", "iris.bukkit.commandiris.loading_world": "§aMundo de carregamento: {logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia não pode carregar novos mundos em tempo de execução. Reiniciar o servidor para carregar \"{logicalWorldName}\".", "iris.bukkit.commandiris.loaded_successfully": "§a{logicalWorldName} carregado com sucesso.", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§cIsto não é um Iris mundo. Iris mundos: {value}", "iris.bukkit.commandiris.evacuating_world": "§aEvacuar o mundo{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "Nome do projeto inválido '{nameRaw}« (permitido: a-z, 0-9, e -)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "Embalar '{name}' já existe em {value}", "iris.modded.moddedstudiocommands.creating_project_from_template": "Criando projeto '{name}« a partir do modelo »{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "Modelo '{template}' não está instalado; download IrisDimensions/{template2}...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "Modelo '{template}' não foi possível transferir; instale um pacote com dimensions/{template2}.json Primeiro.", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "O pacote necessário '{pack}' não está instalado. Instale-o com /iris download e depois reinicie o servidor.", "iris.modded.moddedstudiocommands.created_project_at": "Projecto criado '{name}' em {value}", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "Editar dimensions/{name}.json e o resto da alcateia. A VSCode espaço de trabalho com JSON schema autocomplete foi gerado; abra- o ou reexecute /iris studio vscode.", "iris.modded.moddedstudiocommands.project_creation_failed": "A criação do projecto falhou: {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "Distribuição da região de amostragem em {value}x{value2} chunks à tua volta...", "iris.modded.moddedstudiocommands.rarity": "{key}: raridade={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "A amostragem da região falhou: {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "Embalar '{pack}' não está instalado; download IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "Embalar '{pack}' não foi possível transferir; verifique o nome ou instale- o com /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "O pacote necessário '{pack}' não está instalado. Instale-o com /iris download e depois reinicie o servidor.", "iris.modded.moddedworldcommands.failed_inject_iris_world": "Não foi possível injectar Iris mundo'{dimensionId}': {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "Criado Iris mundo {dimensionId} da pack{pack}« dimensão »{packDimension}« (sementes {seed}).", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "É ao vivo agora e reinjectado em cada startup. Teletransporte com /iris world status ou um portal; não é necessário reiniciar.", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "Em vez disso, {dimensionId} é agora o mundo primário configurado: os jogadores no overworld vanilla são encaminhados para lá na junção, e ele reinjeta em cada startup.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Iris A sobreposição do mundo principal está limpa. O overworld mantém seu gerador atual; edite server.properties nível-tipo e reiniciar para mudá-lo de volta.", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "Sementes inválidas '{seedRaw}'. Use um número ou 'random'.", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "Embalar '{pack}' não está instalado; download IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "Embalar '{pack}' não foi possível transferir; verifique o nome ou instale- o com /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "Embalar '{pack}' ainda não está pronto (carregamento ou validação). Tente o comando novamente em um momento.", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "Falha ao escrever server.properties; verificar permissões de arquivos e definir o tipo de nível manualmente.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Iris mundo principal definido para '{pack}« (predefinição {preset}, sementes {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "Teletransportar- se a si próprio ou a um jogador com o nome num ficheiro carregado Iris dimensão", "iris.modded.help.entry.command.evacuate": "Teletransportar todos os jogadores de uma dimensão Iris para o spawn do mundo principal", "iris.modded.help.entry.command.seed": "Mostrar informações da seed do mundo e do motor", - "iris.modded.help.entry.command.debug": "Alternar Iris debug registrar e salvar settings.json", - "iris.modded.help.entry.command.reload": "Recarregar settings.json (também carregado automaticamente a cada 3s)", + "iris.modded.help.entry.command.debug": "Alternar Iris debug registrar e salvar iris.json", + "iris.modded.help.entry.command.reload": "Recarregar iris.json (também carregado automaticamente a cada 3s)", "iris.modded.help.entry.command.download": "Baixar um projeto de pacote", "iris.modded.help.entry.command.metrics": "Imprimir métricas de geração para o seu atual Iris dimensão", "iris.modded.help.entry.command.regen": "Apagar e regenerar chunks próximos no lugar", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "Previsão ou quarentena de candidatos a recursos não utilizados", "iris.modded.help.entry.command.restore": "Visualizar ou restaurar a quarentena mais recente", "iris.modded.help.entry.command.status_validation": "Mostrar o estado de validação em 'cache'", - "iris.modded.help.entry.command.enable": "Criar e injetar um persistente Iris dimensão em tempo de execução; baixa o pacote se faltar, cotação do pacote:dimensionKey para escolher uma dimensão específica do pacote", + "iris.modded.help.entry.command.enable": "Criar e injetar uma dimensão Iris persistente durante a execução; requer um pacote instalado e pack:dimensionKey entre aspas seleciona uma dimensão específica.", "iris.modded.help.entry.command.replace_overworld": "Injectar Iris mundo primário e roteia jogadores lá em vez do overworld vanilla", "iris.modded.help.entry.command.disable": "Evacuar e descarregar um Iris dimensional; os dados do mundo no disco são mantidos para voltar a activar", "iris.modded.help.entry.command.delete": "Desabilitar um Iris dimensão e limpar seus dados de chunk e Mantle do disco", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§cFalha ao converter: {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7Convertido: {get} em {value}", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§cAlguns esquemas não conseguiram converter. Verifique o console para obter detalhes.", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "Procurando pelo pacote: {type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "A criar o mundo", "iris.runtime.studio.stage.apply_world_rules": "A aplicar regras do mundo", "iris.runtime.studio.stage.prepare_generator": "A preparar o gerador", - "iris.runtime.studio.stage.request_entry_chunk": "A carregar o chunk de entrada", - "iris.runtime.studio.stage.resolve_safe_entry": "A procurar um spawn seguro", "iris.runtime.studio.stage.teleport_player": "A teletransportar", "iris.runtime.studio.stage.finalize_open": "A finalizar", "iris.runtime.studio.stage.cleanup": "A limpar", "iris.runtime.world_create.teleport_failed": "§eO mundo foi criado, mas o teletransporte automático falhou. Tenta. /iris teleport world={world}", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/ru_RU.json b/core/src/main/resources/languages/ru_RU.json index 1f23d1aa4..c4616235f 100644 --- a/core/src/main/resources/languages/ru_RU.json +++ b/core/src/main/resources/languages/ru_RU.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§eПопробуйте один из вариантов: overworld, vanilla, flat, theend", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§aПодготовка мира завершена. Iris перезапускает сервер, чтобы сгенерировать/загрузить «{worldName}».", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§cИсключение, возникшее при сотворении. Смотрите консоль для более подробной информации.", "iris.bukkit.commandiris.successfully_created_your_world": "§aУспешно создал свой мир!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "Создание мира §eRuntime отключено на Folia.", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§eПодготовка мировых файлов и bukkit.yml Для следующего стартапа...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§cНе удалось создать мировые файлы для измерения\".{value}\".", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§aМир Iris «{name}» подготовлен с генератором Iris:{value} и сидом {seed}.", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§cНе удалось обновить bukkit.yml: {value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§cУказанного игрока не существует.", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aИрис v{value} Разработчик Volmit Software", "iris.bukkit.commandiris.to": "§a{value} то {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} Это не мир ирисов.", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§cНе могу определить Iris размерность для {logicalWorldName}.", "iris.bukkit.commandiris.loading_world": "§aЗагрузочный мир: {logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia не может загружать новые миры во время выполнения. Перезагрузить сервер для загрузки\".{logicalWorldName}\".", "iris.bukkit.commandiris.loaded_successfully": "§a{logicalWorldName} Заряжен успешно.", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§cЭто не является Iris Мир. Iris Миры: {value}", "iris.bukkit.commandiris.evacuating_world": "§aЭвакуация мира{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "Недействительное название проекта «{nameRaw}» (допускается: a-z, 0-9, и -)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "Пакуй.{name}уже существует в {value}", "iris.modded.moddedstudiocommands.creating_project_from_template": "Создание проекта '{name}\"Из шаблона\"{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "Шаблон{template}не установлен; скачивание IrisDimensions/{template2}...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "Шаблон{template}Не может быть загружен; установить пакет с dimensions/{template2}.json Сначала.", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "Необходимый пакет '{pack}' не установлен. Установите его командой /iris download, затем перезапустите сервер.", "iris.modded.moddedstudiocommands.created_project_at": "Созданный проект{name}в {value}", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "Редактировать dimensions/{name}.json и остальную часть упаковки. A VSCode рабочее пространство с JSON Схема автозаполнения была сгенерирована; откройте ее или повторите /iris studio vscode.", "iris.modded.moddedstudiocommands.project_creation_failed": "Создание проекта провалилось: {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "Распределение региона выборки в {value}x{value2} Куски вокруг тебя...", "iris.modded.moddedstudiocommands.rarity": "{key}: редкость ={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "Выборка регионов не удалась: {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "Пакуй.{pack}не установлен; скачивание IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "Пакуй.{pack}Не может быть загружен; проверить имя или установить его /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "Необходимый пакет '{pack}' не установлен. Установите его командой /iris download, затем перезапустите сервер.", "iris.modded.moddedworldcommands.failed_inject_iris_world": "Не удалось ввести Iris мир{dimensionId}': {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "созданный Iris мир {dimensionId} из пакета{pack}Измерение{packDimension}' (seed {seed}).", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "Теперь он жив и повторно вводится в каждый стартап. Телепортируйте с /iris world status Портал; перезагрузка не требуется.", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "Вместо этого {dimensionId} Теперь это сконфигурированный основной мир: игроки в мире vanilla направляются туда по присоединению, и он повторно внедряется в каждый стартап.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Iris Главный мировой контроль расчищен. Overworld сохраняет свой текущий генератор server.properties Тип уровня и перезапустить, чтобы изменить его обратно.", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "Недействительное семя{seedRaw}'. Используйте число или «случай».", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "Пакуй.{pack}не установлен; скачивание IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "Пакуй.{pack}Не может быть загружен; проверить имя или установить его /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "Пакуй.{pack}Не готов (по-прежнему загружается или проверяется). Попробуйте команду еще раз через минуту.", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "Не удалось написать server.properties; Проверьте разрешения файлов и установите тип уровня вручную.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Iris Основной мир настроен на{pack}' (preset {preset}Семя {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "Телепортируйте себя или названного игрока в загруженный Iris измерение", "iris.modded.help.entry.command.evacuate": "Телепортировать всех игроков из измерения Iris на точку спавна основного мира", "iris.modded.help.entry.command.seed": "Показать информацию о сиде мира и движка", - "iris.modded.help.entry.command.debug": "переключаться Iris Отладка и сохранение settings.json", - "iris.modded.help.entry.command.reload": "Перезагрузить settings.json (также автоматически загружается каждые 3 секунды)", + "iris.modded.help.entry.command.debug": "переключаться Iris Отладка и сохранение iris.json", + "iris.modded.help.entry.command.reload": "Перезагрузить iris.json (также автоматически загружается каждые 3 секунды)", "iris.modded.help.entry.command.download": "Скачать пакетный проект", "iris.modded.help.entry.command.metrics": "Метрики генерации печати для вашего текущего Iris измерение", "iris.modded.help.entry.command.regen": "Удалить и регенерировать близлежащие чанки на месте", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "Предварительный просмотр или карантин неиспользуемых ресурсов кандидатов", "iris.modded.help.entry.command.restore": "Просмотр или восстановление последнего карантина", "iris.modded.help.entry.command.status_validation": "Показать статус валидации", - "iris.modded.help.entry.command.enable": "Создавать и вводить постоянный Iris Размер во время выполнения; загружает пакет, если его нет, цитирую:dimensionKey Чтобы выбрать конкретный размер упаковки", + "iris.modded.help.entry.command.enable": "Создать и подключить постоянное измерение Iris во время работы; пакет должен быть установлен, а pack:dimensionKey в кавычках выбирает конкретное измерение.", "iris.modded.help.entry.command.replace_overworld": "Введите Iris Основной мир и маршрутные игроки вместо vanilla мира", "iris.modded.help.entry.command.disable": "эвакуировать и разгрузить Iris размерность; мировые данные на диске сохраняются для повторного включения", "iris.modded.help.entry.command.delete": "инвалид Iris размерность и стереть данные о куске и Mantle с диска", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§cНе удалось конвертировать: {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7Преобразовано: {get} в {value}", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§cНекоторые схемы не удалось конвертировать. Проверьте консоль для деталей.", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "В поисках пакета: {type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "Создание мира", "iris.runtime.studio.stage.apply_world_rules": "Применение правил мира", "iris.runtime.studio.stage.prepare_generator": "Подготовка генератора", - "iris.runtime.studio.stage.request_entry_chunk": "Загрузка входного чанка", - "iris.runtime.studio.stage.resolve_safe_entry": "Поиск безопасной точки спавна", "iris.runtime.studio.stage.teleport_player": "Телепортация", "iris.runtime.studio.stage.finalize_open": "Завершение", "iris.runtime.studio.stage.cleanup": "Очистка", "iris.runtime.world_create.teleport_failed": "§eМир был создан, но автоматический телепорт провалился. Попробуйте /iris teleport world={world}", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/tr_TR.json b/core/src/main/resources/languages/tr_TR.json index 0bee9564a..43aab055b 100644 --- a/core/src/main/resources/languages/tr_TR.json +++ b/core/src/main/resources/languages/tr_TR.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§eBirini deneyin: Overworld, vanilla, düz, son", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§aDünya hazırlığı tamamlandı. Iris, \"{worldName}\" dünyasını oluşturmak/yüklemek için sunucuyu yeniden başlatıyor.", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§cYaratılış sırasında ortaya çıktı. Konsolu daha fazla ayrıntı için görün.", "iris.bukkit.commandiris.successfully_created_your_world": "§aBaşarılı bir şekilde dünyanızı yarattı!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eRuntime dünya yaratımı engellidir Folia.", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§eDünya dosyalarını hazırlamak ve bukkit.yml Bir sonraki başlangıç için...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§cBoyut için dünya dosyalarını sahnelemek için başarısız oldu \"{value}\".", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§aIris dünyası \"{name}\", Iris:{value} jeneratörü ve {seed} tohumu ile hazırlandı.", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§cgüncellemek için başarısız oldu bukkit.yml: {value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§cBelirtilen oyuncu mevcut değildir.", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aIris{value} Volmit Software", "iris.bukkit.commandiris.to": "§a{value} tom için {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} Bir iris dünyası değil.", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§cTanımlamayabilir Iris Boyut için boyut {logicalWorldName}.", "iris.bukkit.commandiris.loading_world": "§aYükleniyor dünya: {logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia, runtime'da yeni dünyaları yükleyemez. Sunucuyu yüklemek \"{logicalWorldName}\".", "iris.bukkit.commandiris.loaded_successfully": "§a{logicalWorldName} başarıyla yüklenen.", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§cBu bir şey değil Iris Dünya. Iris Dünyalar: {value}", "iris.bukkit.commandiris.evacuating_world": "§aEvacuating dünya{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "geçersiz projesi adı ‘{nameRaw}\" (tamam: a-z, 0-9, ve -)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "Pack \"{name}‘ zaten var {value}", "iris.modded.moddedstudiocommands.creating_project_from_template": "Proje yaratmak \"{name}\"-den template\"{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "Şablon ‘{template}‘Kolanmıyor; indir IrisDimensions/{template2}...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "Şablon ‘{template}\" indirilemez; bir paketle birlikte yükleme dimensions/{template2}.json İlk önce.", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "Gerekli '{pack}' paketi kurulu değil. /iris download ile kurun, ardından sunucuyu yeniden başlatın.", "iris.modded.moddedstudiocommands.created_project_at": "'{name}' projesi {value} konumunda oluşturuldu", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "EditDüzenle dimensions/{name}.json Ve paketin geri kalanı. A VSCode İş Alanı ile JSON şema autocomplete üretildi; açık ya da yeniden işlendi /iris studio vscode.", "iris.modded.moddedstudiocommands.project_creation_failed": "Proje yaratımı başarısız oldu: {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "Etrafınızdaki {value}x{value2} chunk içinde bölge dağılımı örnekleniyor...", "iris.modded.moddedstudiocommands.rarity": "{key}: rarity ={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "Bölge örneği başarısız oldu: {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "Pack \"{pack}‘Kolanmıyor; indir IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "Pack \"{pack}\" indirilemez; adını kontrol edin veya onu onunla birlikte yükleme /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "Gerekli '{pack}' paketi kurulu değil. /iris download ile kurun, ardından sunucuyu yeniden başlatın.", "iris.modded.moddedworldcommands.failed_inject_iris_world": "enjekte etmek için başarısız oldu Iris Dünya{dimensionId}': {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "Oluşturuluş Iris dünya dünyası {dimensionId} paketten ‘{pack}‘ Boyut’{packDimension}\"(s. {seed}).", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "Şimdi yaşıyor ve her başlangıçta yeniden canlanıyor. Teleport ile birlikte /iris world status veya bir portal; hiçbir yeniden başlama gerekli değildir.", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "Bunun yerine, {dimensionId} Şu anda yapılandırılmış birincil dünya: vanilla overworld'deki oyuncular oraya katılmakta ve her başlangıçta yeniden seçilmişlerdir.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Iris Ana dünya aşırı temizlendi. Overworld mevcut jeneratörünü tutar; düzenleme server.properties seviye tipi ve onu geri değiştirmeye yeniden başlayın.", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "geçersiz tohum \"{seedRaw}'. Bir numara veya 'random' kullanın.", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "Pack \"{pack}‘Kolanmıyor; indir IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "Pack \"{pack}\" indirilemez; adını kontrol edin veya onu onunla birlikte yükleme /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "Pack \"{pack}\" henüz hazır değil (herhangi bir yükleme veya doğrulama). Bir anda tekrar kumandayı deneyin.", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "yazmak için başarısız oldu server.properties; dosya izinlerini kontrol edin ve seviye tipi manuel olarak ayarlayın.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Iris Ana dünya \"{pack}\" (preset {preset}tohum, {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "Kendinizi veya bir telefon name Bir oyuncuya installed Iris Dimension", "iris.modded.help.entry.command.evacuate": "Tüm oyuncuları bir Iris boyutundan ana dünyanın spawn noktasına ışınla", "iris.modded.help.entry.command.seed": "Dünya ve motor seed bilgilerini göster", - "iris.modded.help.entry.command.debug": "değiştir Iris Debug login ve kaydet settings.json", - "iris.modded.help.entry.command.reload": "Yeniden yükleme settings.json (Ayrıca her 3s otomatik olarak ısındı)", + "iris.modded.help.entry.command.debug": "değiştir Iris Debug login ve kaydet iris.json", + "iris.modded.help.entry.command.reload": "Yeniden yükleme iris.json (Ayrıca her 3s otomatik olarak ısındı)", "iris.modded.help.entry.command.download": "Bir paket projesi indirin", "iris.modded.help.entry.command.metrics": "Mevcut nesil ölçümler için Iris boyut boyutu", "iris.modded.help.entry.command.regen": "Yakınlarda silme ve yenidenjenere", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "Preview veya kurantine alışılmamış adaylar", "iris.modded.help.entry.command.restore": "Preview veya en son karantinayı geri yükleme", "iris.modded.help.entry.command.status_validation": "Önbellek doğrulama durumu", - "iris.modded.help.entry.command.enable": "Oluşturun ve kalıcı olun Iris Runtime'da boyut; eksikse paketi indirin, alıntı paketi:dimensionKey Belirli bir paket boyutu seçmek için", + "iris.modded.help.entry.command.enable": "Çalışma zamanında kalıcı bir Iris boyutu oluşturup ekler; paket kurulu olmalıdır ve tırnak içindeki pack:dimensionKey belirli bir boyutu seçer.", "iris.modded.help.entry.command.replace_overworld": "İnjek bir an Iris birincil dünya ve rota oyuncuları orada vanilla overworld yerine", "iris.modded.help.entry.command.disable": "Evacuate ve bir yük Iris Boyut; diskteki dünya verileri yeniden etkinleştirilir", "iris.modded.help.entry.command.delete": "Başarısız bir şekilde Iris Boyut ve onun chunk ve mantle verilerini disklerden sil", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§cdönüştürmeye başarısız oldu: {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7Dönüştürüldü: {get}, {value} sürede", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§cBazı şematik dönüştürmeye başarısız oldu. Bilgi için konsolu kontrol edin.", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "Paket arıyorsunuz: {type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "Dünya oluşturuluyor", "iris.runtime.studio.stage.apply_world_rules": "Dünya kuralları uygulanıyor", "iris.runtime.studio.stage.prepare_generator": "Jeneratör hazırlanıyor", - "iris.runtime.studio.stage.request_entry_chunk": "Giriş chunk'ı yükleniyor", - "iris.runtime.studio.stage.resolve_safe_entry": "Güvenli spawn aranıyor", "iris.runtime.studio.stage.teleport_player": "Işınlanıyor", "iris.runtime.studio.stage.finalize_open": "Tamamlanıyor", "iris.runtime.studio.stage.cleanup": "Temizleniyor", "iris.runtime.world_create.teleport_failed": "§eDünya yaratıldı, ancak otomatik telgraf başarısız oldu. Deneyin /iris teleport world={world}", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/vi_VI.json b/core/src/main/resources/languages/vi_VI.json index 297ea5b92..ddfad5032 100644 --- a/core/src/main/resources/languages/vi_VI.json +++ b/core/src/main/resources/languages/vi_VI.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§eHãy thử một trong những: ngoài thế giới, vanilla, bằng phẳng, kết thúc", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§aĐã chuẩn bị xong thế giới. Iris đang khởi động lại máy chủ để tạo/tải \"{worldName}\".", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§cNgoại lệ được nuôi lớn trong suốt quá trình sáng tạo. Xem bảng điều khiển để biết thêm chi tiết.", "iris.bukkit.commandiris.successfully_created_your_world": "§aThành công trong việc tạo ra thế giới của anh!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eComment Folia.", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§eĐang chuẩn bị tập tin thế giới và bukkit.yml cho lần khởi chạy tiếp theo...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§cKhông thể tạo tập tin cho chiều không gian{value}\".", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§aĐã chuẩn bị thế giới Iris \"{name}\" với trình tạo Iris:{value} và seed {seed}.", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§cLỗi cập nhật bukkit.yml: {value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§cNgười chơi đã ghi rõ không tồn tại.", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aIris v.{value} by Volmit Phần mềm", "iris.bukkit.commandiris.to": "§a{value} phải {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} không phải thế giới của Iris.", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§cKhông thể xác định Iris Kích thước cho {logicalWorldName}.", "iris.bukkit.commandiris.loading_world": "§aTải về {logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia không thể tải những thế giới mới vào thời điểm chạy trốn. Khởi động lại máy phục vụ để tải \"{logicalWorldName}\".", "iris.bukkit.commandiris.loaded_successfully": "§a{logicalWorldName} đã nạp thành công.", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§cĐây không phải là Iris Thế giới. Iris Thế giới: {value}", "iris.bukkit.commandiris.evacuating_world": "§aThế giới đang sơ tán{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "Tên dự án không hợp lệ{nameRaw}' (được phép: a-z, 0-9, và -)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "Gói '{name}'Đã có tại {value}", "iris.modded.moddedstudiocommands.creating_project_from_template": "Đang tạo dự án{name}Từ mẫu{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "Mẫu{template}Chưa cài đặt; tải về IrisDimensions/{template2}...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "Mẫu{template}Không thể tải về; cài đặt một gói với dimensions/{template2}.json Đầu tiên.", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "Gói bắt buộc '{pack}' chưa được cài đặt. Hãy cài bằng /iris download, rồi khởi động lại máy chủ.", "iris.modded.moddedstudiocommands.created_project_at": "Tạo dự án{name}Ở {value}", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "Sửa dimensions/{name}.json và những người còn lại. A VSCode Vùng làm việc với JSON schema tự động hoàn tất đã được tạo ra; mở nó hoặc chạy lại /iris studio vscode.", "iris.modded.moddedstudiocommands.project_creation_failed": "Lỗi tạo dự án: {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "Phân phối khu vực {value}x{value2} Những chunk xung quanh bạn...", "iris.modded.moddedstudiocommands.rarity": "{key}: hiếm ={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "Việc lấy mẫu vùng bị lỗi: {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "Gói '{pack}Chưa cài đặt; tải về IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "Gói '{pack}Không thể tải về; hãy kiểm tra tên hay cài đặt nó bằng /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "Gói bắt buộc '{pack}' chưa được cài đặt. Hãy cài bằng /iris download, rồi khởi động lại máy chủ.", "iris.modded.moddedworldcommands.failed_inject_iris_world": "Lỗi khi tiêm Iris Thế giới{dimensionId}': {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "Tạo Iris thế giới {dimensionId} từ gói '{pack}Không gian{packDimension}' (dòng dõi {seed}).", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "Nó được truyền hình trực tiếp và phát lại vào mỗi lần khởi động. Name /iris world status hoặc cổng; không cần khởi động lại.", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "Thay vào đó, {dimensionId} Bây giờ là thế giới chính được cấu hình: những người chơi trong thế giới bên kia vanilla được định tuyến ở đó, và nó tái phát vào mỗi lần bắt đầu.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Iris quyền kiểm soát thế giới đã được xóa. Thế giới bên ngoài giữ trình tạo; chỉnh sửa server.properties Kiểu cấp và khởi động lại để thay đổi nó.", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "Hạt giống không hợp lệ{seedRaw}'. Sử dụng một số hoặc \"vô hạn\".", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "Gói '{pack}Chưa cài đặt; tải về IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "Gói '{pack}Không thể tải về; hãy kiểm tra tên hay cài đặt nó bằng /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "Gói '{pack}' chưa sẵn sàng (chưa tải hay xác nhận). Thử lại lệnh lần nữa đi.", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "Lỗi ghi server.properties; kiểm tra quyền hạn tập tin và đặt kiểu cấp thủ công.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Iris thế giới chính được đặt thành '{pack}(đã định sẵn {preset}, hạt giống {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "Name Iris Chiều rộng", "iris.modded.help.entry.command.evacuate": "Dịch chuyển mọi người chơi khỏi chiều không gian Iris đến điểm spawn của thế giới chính", "iris.modded.help.entry.command.seed": "Hiển thị thông tin seed của thế giới và engine", - "iris.modded.help.entry.command.debug": "Bật/ tắt Iris @ info/ rich settings.json", - "iris.modded.help.entry.command.reload": "Nạp lại settings.json (đã nạp quá nóng tự động mỗi 3)", + "iris.modded.help.entry.command.debug": "Bật/ tắt Iris @ info/ rich iris.json", + "iris.modded.help.entry.command.reload": "Nạp lại iris.json (đã nạp quá nóng tự động mỗi 3)", "iris.modded.help.entry.command.download": "Tải về dự án gói", "iris.modded.help.entry.command.metrics": "Số đo cỡ ảnh hiện thời Iris Chiều rộng", "iris.modded.help.entry.command.regen": "Xóa và tái tạo các mẫu gần đó", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "Xem thử hoặc cách ly ứng dụng nguồn không dùng", "iris.modded.help.entry.command.restore": "Xem thử hoặc phục hồi cách ly mới nhất", "iris.modded.help.entry.command.status_validation": "Hiện trạng thái hợp lệ tạm", - "iris.modded.help.entry.command.enable": "Tạo ra và tiêm một sự bền bỉ Iris Không gian thời gian chạy; tải xuống các gói nếu thiếu, trích dẫn gói:dimensionKey để chọn một chiều cụ thể", + "iris.modded.help.entry.command.enable": "Tạo và chèn một chiều Iris cố định trong khi chạy; gói phải được cài đặt và pack:dimensionKey đặt trong dấu ngoặc kép sẽ chọn một chiều cụ thể.", "iris.modded.help.entry.command.replace_overworld": "Tiêm a Iris Thế giới chính và các cầu thủ đường xá ở đó thay vì hiện nay", "iris.modded.help.entry.command.disable": "Di tản và dỡ hàng. Iris Kích thước; dữ liệu thế giới trên đĩa được lưu giữ để phục hồi sức khỏe", "iris.modded.help.entry.command.delete": "Tắt Iris Kích thước và xoá dữ liệu trong đĩa", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§cLỗi chuyển đổi: {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7Chuyển đổi: {get} trong {value}", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§cMột số sơ đồ không thể chuyển đổi. Kiểm tra bảng điều khiển xem có chi tiết gì không.", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "Đang tìm gói: {type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "Đang tạo thế giới", "iris.runtime.studio.stage.apply_world_rules": "Đang áp dụng quy tắc thế giới", "iris.runtime.studio.stage.prepare_generator": "Đang chuẩn bị trình tạo", - "iris.runtime.studio.stage.request_entry_chunk": "Đang tải chunk khởi đầu", - "iris.runtime.studio.stage.resolve_safe_entry": "Đang tìm điểm spawn an toàn", "iris.runtime.studio.stage.teleport_player": "Đang dịch chuyển", "iris.runtime.studio.stage.finalize_open": "Đang hoàn tất", "iris.runtime.studio.stage.cleanup": "Đang dọn dẹp", "iris.runtime.world_create.teleport_failed": "§eThế giới đã được tạo ra, nhưng dịch chuyển tự động thất bại. Thử /iris teleport world={world}", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/zh_CN.json b/core/src/main/resources/languages/zh_CN.json index 4a50ab152..4f5dd9a06 100644 --- a/core/src/main/resources/languages/zh_CN.json +++ b/core/src/main/resources/languages/zh_CN.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§e可尝试:overworld、vanilla、flat 或 the_end", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§a世界暂存完成。Iris 正在重启服务器以生成并加载 \"{worldName}\"。", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§c创建期间发生异常。详情请查看控制台。", "iris.bukkit.commandiris.successfully_created_your_world": "§a世界创建成功!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eFolia 上已禁用运行时世界创建。", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§e正在为下次启动准备世界文件和 bukkit.yml...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§c无法为维度 \"{value}\" 暂存世界文件。", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§a已暂存 Iris 世界 \"{name}\",生成器为 Iris:{value},种子为 {seed}。", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§c无法更新 bukkit.yml:{value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§c指定的玩家不存在。", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aIris v.{value},由 Volmit Software 开发", "iris.bukkit.commandiris.to": "§a{value} 改为: {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} 不是 Iris 世界。", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§c无法确定 {logicalWorldName} 的 Iris 维度。", "iris.bukkit.commandiris.loading_world": "§a正在加载世界:{logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia 无法在运行时加载新世界。请重启服务器以加载 \"{logicalWorldName}\"。", "iris.bukkit.commandiris.loaded_successfully": "§a已成功加载 {logicalWorldName}。", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§c这不是 Iris 世界。Iris 世界数量:{value}", "iris.bukkit.commandiris.evacuating_world": "§a正在疏散世界:{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "无效的工程名称 '{nameRaw}'(获批准:a-z, 0-9时, 和 -)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "包{name}' 已经存在于 {value}", "iris.modded.moddedstudiocommands.creating_project_from_template": "创建工程 '{name}'从模板'{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "模板{template}'未安装;下载 IrisDimensions/{template2}...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "模板{template}' 无法下载;安装一个包 dimensions/{template2}.json 先说", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "所需维度包“{pack}”尚未安装。请使用 /iris download 安装,然后重启服务器。", "iris.modded.moddedstudiocommands.created_project_at": "创建的工程 '{name}时 {value}", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "编辑 dimensions/{name}.json 还有剩下的包 A VSCode 工作空间 JSON 生成了 schema 自动完成; 打开或重运行 /iris studio vscode.", "iris.modded.moddedstudiocommands.project_creation_failed": "工程创建失败 : {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "抽样地区分布 {value}x{value2} 环绕着你...", "iris.modded.moddedstudiocommands.rarity": "{key}: 罕见={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "区域采样失败 : {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "包{pack}'未安装;下载 IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "包{pack}' 无法下载; 检查名称或安装 /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "所需维度包“{pack}”尚未安装。请使用 /iris download 安装,然后重启服务器。", "iris.modded.moddedworldcommands.failed_inject_iris_world": "注入失败 Iris 世界{dimensionId}': {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "创建 Iris 世界 {dimensionId} 从包\"{pack}\" 维度 \"{packDimension}'(种子 {seed}).", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "它现在正在直播,并在每次启动时重新注入。 传送到 /iris world status 或门户网站;不需要重新启动。", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "相反, {dimensionId} 现在是配置的初级世界:在原版覆盖世界的玩家在加入时会在那里经过路由,并且在每次启动时都会重新注入.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Iris 关闭主世界。 覆盖世界保留其当前生成器; 编辑 server.properties 级别类型并重新启动以将其修改回。", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "无效的种子 '{seedRaw}'. 使用数字或“ 随机” 。", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "包{pack}'未安装;下载 IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "包{pack}' 无法下载; 检查名称或安装 /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "包{pack}'尚未准备好(仍在加载或验证中)。 一会儿再试试命令", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "写入失败 server.properties; 检查文件权限并手动设定级别类型.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Iris 主要世界设定为`{pack}'(预设 {preset}种子 {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "将自己或指定玩家传送到已加载的 Iris 维度", "iris.modded.help.entry.command.evacuate": "将 Iris 维度中的所有玩家传送到主世界出生点", "iris.modded.help.entry.command.seed": "显示世界和引擎的种子信息", - "iris.modded.help.entry.command.debug": "切换 Iris 调试日志并保存 settings.json", - "iris.modded.help.entry.command.reload": "重新加载 settings.json(也会每 3 秒自动热加载)", + "iris.modded.help.entry.command.debug": "切换 Iris 调试日志并保存 iris.json", + "iris.modded.help.entry.command.reload": "重新加载 iris.json(也会每 3 秒自动热加载)", "iris.modded.help.entry.command.download": "下载包项目", "iris.modded.help.entry.command.metrics": "显示当前 Iris 维度的生成指标", "iris.modded.help.entry.command.regen": "原地删除并重新生成附近区块", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "预览或隔离未使用的资源候选项", "iris.modded.help.entry.command.restore": "预览或恢复最近的隔离内容", "iris.modded.help.entry.command.status_validation": "显示缓存的验证状态", - "iris.modded.help.entry.command.enable": "在运行时创建并注入持久 Iris 维度;缺少包时会自动下载,用引号包住 pack:dimensionKey 可选择特定包维度", + "iris.modded.help.entry.command.enable": "在运行时创建并注入持久 Iris 维度;必须先安装维度包,用引号括起 pack:dimensionKey 可选择包中的特定维度。", "iris.modded.help.entry.command.replace_overworld": "注入 Iris 主世界,并将玩家路由到该世界而非原版主世界", "iris.modded.help.entry.command.disable": "疏散并卸载 Iris 维度;保留磁盘上的世界数据以便重新启用", "iris.modded.help.entry.command.delete": "禁用 Iris 维度并从磁盘清除其区块和 Mantle 数据", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§c转换失败 : {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7已转换 : {get} 输入 {value}", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§c一些示意图未能转换. 检查控制台的细节。", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "正在查找内容包:{type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "创造世界", "iris.runtime.studio.stage.apply_world_rules": "应用世界规则", "iris.runtime.studio.stage.prepare_generator": "正在准备生成器", - "iris.runtime.studio.stage.request_entry_chunk": "正在加载条目块", - "iris.runtime.studio.stage.resolve_safe_entry": "寻找安全的产卵", "iris.runtime.studio.stage.teleport_player": "远程传送", "iris.runtime.studio.stage.finalize_open": "最后确定", "iris.runtime.studio.stage.cleanup": "清理", "iris.runtime.world_create.teleport_failed": "§e世界是创建的,但自动电传传送失败. 尝试 /iris teleport world={world}", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/main/resources/languages/zh_TW.json b/core/src/main/resources/languages/zh_TW.json index e1ad88a14..b8d00257c 100644 --- a/core/src/main/resources/languages/zh_TW.json +++ b/core/src/main/resources/languages/zh_TW.json @@ -152,14 +152,8 @@ "iris.bukkit.commandiris.try_one_overworld_vanilla_flat_theend": "§e可嘗試:overworld、vanilla、flat 或 the_end", "iris.bukkit.commandiris.dimension_not_found": "§cCould not find dimension §f{dimension}§c.", "iris.bukkit.commandiris.install_pack_and_restart": "§eInstall it with §b{command}§e and restart the server.", - "iris.bukkit.commandiris.world_staging_completed_restarting_server_generate_load": "§a世界暫存完成。Iris 正在重新啟動伺服器以生成並載入 \"{worldName}\"。", "iris.bukkit.commandiris.exception_raised_during_creation_see_console_more_details": "§c建立期間發生例外。詳細資訊請查看主控台。", "iris.bukkit.commandiris.successfully_created_your_world": "§a世界建立成功!", - "iris.bukkit.commandiris.runtime_world_creation_is_disabled_on_folia": "§eFolia 上已停用執行階段世界建立。", - "iris.bukkit.commandiris.preparing_world_files_bukkit_yml_next_startup": "§e正在為下次啟動準備世界檔案和 bukkit.yml...", - "iris.bukkit.commandiris.failed_stage_world_files_dimension": "§c無法為維度 \"{value}\" 暫存世界檔案。", - "iris.bukkit.commandiris.staged_iris_world_with_generator_iris_seed": "§a已暫存 Iris 世界 \"{name}\",生成器為 Iris:{value},種子為 {seed}。", - "iris.bukkit.commandiris.failed_update_bukkit_yml": "§c無法更新 bukkit.yml:{value}", "iris.bukkit.commandiris.specified_player_does_not_exist": "§c指定的玩家不存在。", "iris.bukkit.commandiris.iris_v_by_volmit_software": "§aIris v.{value},由 Volmit Software 開發", "iris.bukkit.commandiris.to": "§a{value} 改為: {value2}", @@ -188,7 +182,6 @@ "iris.bukkit.commandiris.is_not_iris_world": "§6{logicalWorldName} 不是 Iris 世界。", "iris.bukkit.commandiris.could_not_determine_iris_dimension": "§c無法判斷 {logicalWorldName} 的 Iris 維度。", "iris.bukkit.commandiris.loading_world": "§a正在載入世界:{logicalWorldName}", - "iris.bukkit.commandiris.folia_cannot_load_new_worlds_at_runtime_restart_server_load": "§eFolia 無法在執行階段載入新世界。請重新啟動伺服器以載入 \"{logicalWorldName}\"。", "iris.bukkit.commandiris.loaded_successfully": "§a已成功載入 {logicalWorldName}。", "iris.bukkit.commandiris.this_is_not_iris_world_iris_worlds_3": "§c這不是 Iris 世界。Iris 世界數量:{value}", "iris.bukkit.commandiris.evacuating_world": "§a正在疏散世界:{value}", @@ -571,8 +564,7 @@ "iris.modded.moddedstudiocommands.invalid_project_name_allowed_z_0_9": "無效的工程名稱 '{nameRaw}'(獲批准:a-z, 0-9時, 和 -)", "iris.modded.moddedstudiocommands.pack_already_exists_at": "包{name}' 已經存在於 {value}", "iris.modded.moddedstudiocommands.creating_project_from_template": "建立工程 '{name}'從模板'{template}'...", - "iris.modded.moddedstudiocommands.template_is_not_installed_downloading_irisdimensions": "模板{template}'未安裝;下載 IrisDimensions/{template2}...", - "iris.modded.moddedstudiocommands.template_could_not_be_downloaded_install_pack_with_dimensions_json": "模板{template}' 無法下載;安裝一個包 dimensions/{template2}.json 先說", + "iris.modded.moddedstudiocommands.required_pack_is_not_installed_install_then_restart": "所需維度包「{pack}」尚未安裝。請使用 /iris download 安裝,然後重新啟動伺服器。", "iris.modded.moddedstudiocommands.created_project_at": "建立的工程 '{name}時 {value}", "iris.modded.moddedstudiocommands.edit_dimensions_json_rest_pack_vscode_workspace_with_json_schema": "編輯 dimensions/{name}.json 還有剩下的包 A VSCode 工作空間 JSON 生成了 schema 自動完成; 開啟或重執行 /iris studio vscode.", "iris.modded.moddedstudiocommands.project_creation_failed": "工程建立失敗 : {value}{errorMessage}", @@ -584,8 +576,7 @@ "iris.modded.moddedstudiocommands.sampling_region_distribution_x_chunks_around_you": "抽樣地區分佈 {value}x{value2} 環繞著你...", "iris.modded.moddedstudiocommands.rarity": "{key}: 罕見={rarity} / {value}%", "iris.modded.moddedstudiocommands.region_sampling_failed": "區域取樣失敗 : {value}", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions": "包{pack}'未安裝;下載 IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with": "包{pack}' 無法下載; 檢查名稱或安裝 /iris download {pack2}.", + "iris.modded.moddedworldcommands.required_pack_is_not_installed_install_then_restart": "所需維度包「{pack}」尚未安裝。請使用 /iris download 安裝,然後重新啟動伺服器。", "iris.modded.moddedworldcommands.failed_inject_iris_world": "注入失敗 Iris 世界{dimensionId}': {value}{errorMessage}", "iris.modded.moddedworldcommands.created_iris_world_from_pack_dimension_seed": "建立 Iris 世界 {dimensionId} 從包\"{pack}\" 維度 \"{packDimension}'(種子 {seed}).", "iris.modded.moddedworldcommands.it_is_live_now_re_injected_on_every_startup_teleport": "它現在正在直播,並在每次啟動時重新注入。 傳送到 /iris world status 或入口網站;不需要重新啟動。", @@ -595,8 +586,6 @@ "iris.modded.moddedworldcommands.instead_is_now_configured_primary_world_players_vanilla_overworld_are": "相反, {dimensionId} 現在是配置的初級世界:在原版覆蓋世界的玩家在加入時會在那裡經過路由,並且在每次啟動時都會重新注入.", "iris.modded.moddedworldcommands.iris_main_world_override_cleared_overworld_keeps_its_current_generator": "Iris 關閉主世界。 覆蓋世界保留其當前生成器; 編輯 server.properties 級別型別並重新啟動以將其修改回。", "iris.modded.moddedworldcommands.invalid_seed_use_number_random": "無效的種子 '{seedRaw}'. 使用數字或“ 隨機” 。", - "iris.modded.moddedworldcommands.pack_is_not_installed_downloading_irisdimensions_2": "包{pack}'未安裝;下載 IrisDimensions/{pack2}...", - "iris.modded.moddedworldcommands.pack_could_not_be_downloaded_check_name_install_it_with_2": "包{pack}' 無法下載; 檢查名稱或安裝 /iris download {pack2}.", "iris.modded.moddedworldcommands.pack_is_not_ready_yet_still_loading_validating_try_command": "包{pack}'尚未準備好(仍在載入或驗證中)。 一會兒再試試命令", "iris.modded.moddedworldcommands.failed_write_server_properties_check_file_permissions_set_level_type": "寫入失敗 server.properties; 檢查檔案許可權並手動設定級別型別.", "iris.modded.moddedworldcommands.iris_main_world_set_preset_seed": "Iris 主要世界設定為`{pack}'(預設 {preset}種子 {value}).", @@ -845,8 +834,8 @@ "iris.modded.help.entry.command.tp": "將自己或指定玩家傳送到已載入的 Iris 維度", "iris.modded.help.entry.command.evacuate": "將 Iris 維度中的所有玩家傳送到主要世界出生點", "iris.modded.help.entry.command.seed": "顯示世界和引擎的種子資訊", - "iris.modded.help.entry.command.debug": "切換 Iris 偵錯記錄並儲存 settings.json", - "iris.modded.help.entry.command.reload": "重新載入 settings.json(也會每 3 秒自動熱載入)", + "iris.modded.help.entry.command.debug": "切換 Iris 偵錯記錄並儲存 iris.json", + "iris.modded.help.entry.command.reload": "重新載入 iris.json(也會每 3 秒自動熱載入)", "iris.modded.help.entry.command.download": "下載包專案", "iris.modded.help.entry.command.metrics": "顯示目前 Iris 維度的生成指標", "iris.modded.help.entry.command.regen": "原地刪除並重新生成附近區塊", @@ -906,7 +895,7 @@ "iris.modded.help.entry.command.cleanup": "預覽或隔離未使用的資源候選項目", "iris.modded.help.entry.command.restore": "預覽或還原最近的隔離內容", "iris.modded.help.entry.command.status_validation": "顯示快取的驗證狀態", - "iris.modded.help.entry.command.enable": "在執行階段建立並注入持久 Iris 維度;缺少包時會自動下載,用引號包住 pack:dimensionKey 可選擇特定包維度", + "iris.modded.help.entry.command.enable": "在執行階段建立並注入持久 Iris 維度;必須先安裝維度包,用引號括住 pack:dimensionKey 可選擇包中的特定維度。", "iris.modded.help.entry.command.replace_overworld": "注入 Iris 主要世界,並將玩家導向該世界而非原版主世界", "iris.modded.help.entry.command.disable": "疏散並卸載 Iris 維度;保留磁碟上的世界資料以便重新啟用", "iris.modded.help.entry.command.delete": "停用 Iris 維度並從磁碟清除其區塊和 Mantle 資料", @@ -1135,7 +1124,6 @@ "iris.bukkit.runtime.irisconverter.failed_convert": "§c轉換失敗 : {name}", "iris.bukkit.runtime.irisconverter.converted_3": "§7已轉換 : {get} 輸入 {value}", "iris.bukkit.runtime.irisconverter.some_schematics_failed_convert_check_console_details": "§c一些示意圖未能轉換. 檢查控制檯的細節。", - "iris.bukkit.runtime.studiosvc.installing_package": "§6World pack §b{name}:{loadKey}§7 | §fPublishing snapshot", "iris.bukkit.runtime.studiosvc.pack_copy_requires_async_thread": "§cIris refused to copy the world pack on the Bukkit primary thread.", "iris.bukkit.runtime.studiosvc.pack_install_failed": "§cFailed to install world pack §f{dimension}§c: {error}", "iris.bukkit.runtime.studiosvc.looking_package": "正在尋找內容包:{type}", @@ -1202,16 +1190,10 @@ "iris.runtime.studio.stage.create_world": "創造世界", "iris.runtime.studio.stage.apply_world_rules": "應用世界規則", "iris.runtime.studio.stage.prepare_generator": "正在準備生成器", - "iris.runtime.studio.stage.request_entry_chunk": "正在載入條目塊", - "iris.runtime.studio.stage.resolve_safe_entry": "尋找安全的產卵", "iris.runtime.studio.stage.teleport_player": "遠端傳送", "iris.runtime.studio.stage.finalize_open": "最後確定", "iris.runtime.studio.stage.cleanup": "清理", "iris.runtime.world_create.teleport_failed": "§e世界是建立的,但自動電傳傳送失敗. 嘗試 /iris teleport world={world}", - "iris.runtime.world_create.bossbar.working": "§6World §b{world}§7 | §fStarting", - "iris.runtime.world_create.bossbar.progress": "§6World §b{world}§7 | §e{percent}% §f{stage}", - "iris.runtime.world_create.bossbar.failed": "§6World §b{world}§7 | §cFAILED §8{percent}%", - "iris.runtime.world_create.bossbar.ready": "§6World §b{world}§7 | §aREADY 100%", "iris.runtime.world_create.lifecycle.action": "{bar}§7 §e{percent}%§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.failed": "{bar}§7 §cFAILED§7 | §f{stage}{detail}§8 {elapsed}", "iris.runtime.world_create.lifecycle.action.ready": "{bar}§7 §a100%§7 | §aWorld ready§8 {elapsed}", @@ -1226,7 +1208,7 @@ "iris.runtime.world_create.stage.prepare_generator": "Preparing generator", "iris.runtime.world_create.stage.create_world": "Generating spawn", "iris.runtime.world_create.stage.register_world": "Registering world", - "iris.runtime.world_create.stage.teleport_player": "Finding safe entry", + "iris.runtime.world_create.stage.teleport_player": "Entering world", "iris.runtime.world_create.stage.pregenerate": "Pregenerating", "iris.runtime.world_create.stage.finalize": "Finalizing", "iris.runtime.world_create.stage.complete": "World ready", diff --git a/core/src/test/java/art/arcane/iris/core/DiagnosticSeverityPolicyTest.java b/core/src/test/java/art/arcane/iris/core/DiagnosticSeverityPolicyTest.java index 520073d19..be8bb65e6 100644 --- a/core/src/test/java/art/arcane/iris/core/DiagnosticSeverityPolicyTest.java +++ b/core/src/test/java/art/arcane/iris/core/DiagnosticSeverityPolicyTest.java @@ -5,6 +5,8 @@ import org.junit.Test; import java.io.IOException; import java.nio.file.Files; 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.assertFalse; @@ -42,15 +44,46 @@ public class DiagnosticSeverityPolicyTest { assertLoggedWith("core/pregenerator/PregenMantleBackpressure.java", "Pregen mantle backpressure: ", "info"); assertLoggedWith("core/pregenerator/PregenMantleBackpressure.java", "Pregen heap pressure: pausing generation", "info"); assertLoggedWith("core/pregenerator/MantleHeapPressure.java", "Iris heap remained at", "info"); - assertLoggedWith("core/pregenerator/methods/AsyncPregenMethod.java", "is still pending after", "info"); + assertLoggedWith("core/pregenerator/methods/AsyncPregenMethod.java", "is still pending after", "debug"); } @Test - public void probesOnOptionalDependenciesAreNotWarnings() { + public void generationTracingStaysAtDebugLevel() { + assertLoggedWith("engine/mantle/components/MantleObjectComponent.java", "Regen object layer start:", "debug"); + assertLoggedWith("engine/mantle/components/MantleObjectComponent.java", "Goldendebug object attempt:", "debug"); + assertLoggedWith("engine/mantle/components/GoldenDebugObjectPlacer.java", "Goldendebug query:", "debug"); + assertLoggedWith("engine/mantle/components/IrisStructureComponent.java", "[StructTrace] ORIGIN", "debug"); + } + + @Test + public void coreProductionDoesNotWriteDirectlyToProcessStreams() throws IOException { + try (Stream files = Files.walk(Path.of("src/main/java"))) { + List bypasses = files + .filter(path -> path.toString().endsWith(".java")) + .filter(path -> { + try { + String source = Files.readString(path); + return source.contains("System.out") + || source.contains("System.err") + || source.contains(".printStackTrace();"); + } catch (IOException unreadable) { + throw new IllegalStateException(unreadable); + } + }) + .toList(); + assertTrue(bypasses.toString(), bypasses.isEmpty()); + } + } + + @Test + public void probesOnOptionalDependenciesAreNotWarnings() throws IOException { assertLoggedWith("core/link/MultiverseCoreLink.java", "is not reachable; Iris world settings", "info"); assertLoggedWith("core/link/MultiverseCoreLink.java", "Multiverse will record the live name", "info"); assertLoggedWith("core/gui/GuiHost.java", "Unable to install the Iris desktop quit guard", "info"); - assertLoggedWith("util/common/misc/SlimJar.java", "Failed to inject the library loader", "info"); + String slimJar = read("util/common/misc/SlimJar.java"); + assertTrue(slimJar.contains("debug(plugin, \"Failed to inject the library loader")); + assertTrue(slimJar.contains("if (DEBUG)")); + assertTrue(slimJar.contains("plugin.getLogger().info(\"[DEBUG] \" + message)")); } /** diff --git a/core/src/test/java/art/arcane/iris/core/IrisSettingsDefaultsTest.java b/core/src/test/java/art/arcane/iris/core/IrisSettingsDefaultsTest.java index 0bbe35536..4e5e1ee86 100644 --- a/core/src/test/java/art/arcane/iris/core/IrisSettingsDefaultsTest.java +++ b/core/src/test/java/art/arcane/iris/core/IrisSettingsDefaultsTest.java @@ -13,21 +13,23 @@ import static org.junit.Assert.assertThrows; public class IrisSettingsDefaultsTest { @Test - public void concurrencyIsNotSerializedIntoSettingsJson() { + public void concurrencyIsNotSerializedIntoIrisJson() { String json = new Gson().toJson(new IrisSettings()); - assertFalse("settings.json must not advertise an unconfigurable concurrency block", + assertFalse("iris.json must not advertise an unconfigurable concurrency block", json.contains("\"concurrency\"")); } @Test - public void legacyConcurrencyBlockStillLoadsAndKeepsDerivedValues() { - IrisSettings loaded = new Gson().fromJson( - "{\"concurrency\":{\"parallelism\":8,\"ioParallelism\":4}}", IrisSettings.class); + public void languageAndMetricsAreTheFirstGeneralControls() { + String json = new Gson().toJson(new IrisSettings()); - assertNotNull(loaded.getConcurrency()); - assertEquals(Math.max(2, Runtime.getRuntime().availableProcessors()), - loaded.getConcurrency().getParallelism()); + int language = json.indexOf("\"language\""); + int metrics = json.indexOf("\"metrics\""); + int commandSounds = json.indexOf("\"commandSounds\""); + assertTrue(language >= 0); + assertTrue(language < metrics); + assertTrue(metrics < commandSounds); } @Test diff --git a/core/src/test/java/art/arcane/iris/core/ProgressBossBarToggleTest.java b/core/src/test/java/art/arcane/iris/core/ProgressBossBarToggleTest.java index b487c5f71..15eed1c4f 100644 --- a/core/src/test/java/art/arcane/iris/core/ProgressBossBarToggleTest.java +++ b/core/src/test/java/art/arcane/iris/core/ProgressBossBarToggleTest.java @@ -34,8 +34,12 @@ public class ProgressBossBarToggleTest { } @Test - public void worldCreationBossBarIsGatedBySettings() throws Exception { - assertGated("core/tools/WorldCreationProgressReporter.java"); + public void worldCreationAlwaysUsesTheActionBarInsteadOfABossBar() throws Exception { + String source = source("core/tools/WorldCreationProgressReporter.java"); + + assertFalse(source.contains("isProgressBossBar()")); + assertFalse(source.contains("Bukkit.createBossBar(")); + assertTrue(source.contains("WORLD_CREATE_LIFECYCLE_ACTION")); } @Test diff --git a/core/src/test/java/art/arcane/iris/core/ServerConfiguratorStartupRestartTest.java b/core/src/test/java/art/arcane/iris/core/ServerConfiguratorStartupRestartTest.java index dd02373fb..fc4773c40 100644 --- a/core/src/test/java/art/arcane/iris/core/ServerConfiguratorStartupRestartTest.java +++ b/core/src/test/java/art/arcane/iris/core/ServerConfiguratorStartupRestartTest.java @@ -4,9 +4,14 @@ import art.arcane.iris.spi.IrisLogging; import org.bukkit.Bukkit; import org.junit.Test; import org.mockito.MockedStatic; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; import java.nio.file.Files; import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -45,6 +50,48 @@ public class ServerConfiguratorStartupRestartTest { } } + @Test + public void immediateRestartCapabilityIsOptional() throws ReflectiveOperationException { + RestartCapableApi.restarted = false; + + assertTrue(ServerConfigurator.invokeImmediateRestartIfSupported(RestartCapableApi.class)); + assertTrue(RestartCapableApi.restarted); + assertFalse(ServerConfigurator.invokeImmediateRestartIfSupported(ShutdownOnlyApi.class)); + } + + @Test + public void productionBytecodeDoesNotLinkBukkitRestartDirectly() throws Exception { + AtomicBoolean directRestartInvocation = new AtomicBoolean(false); + ClassReader reader = new ClassReader(ServerConfigurator.class.getName()); + reader.accept(new ClassVisitor(Opcodes.ASM9) { + @Override + public MethodVisitor visitMethod( + int access, + String name, + String descriptor, + String signature, + String[] exceptions + ) { + return new MethodVisitor(Opcodes.ASM9) { + @Override + public void visitMethodInsn( + int opcode, + String owner, + String methodName, + String methodDescriptor, + boolean isInterface + ) { + if ("org/bukkit/Bukkit".equals(owner) && "restart".equals(methodName)) { + directRestartInvocation.set(true); + } + } + }; + } + }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + + assertFalse(directRestartInvocation.get()); + } + @Test public void startupRestartPathsPromoteTheValidationStateAndBypassTickQueues() throws Exception { String source = Files.readString(Path.of( @@ -62,12 +109,24 @@ public class ServerConfiguratorStartupRestartTest { int validationRestart = configure.indexOf("requireDatapackRestart();", restartResult); assertTrue(restartResult >= 0); assertTrue(validationRestart > restartResult); - assertTrue(startupRestart.indexOf("Bukkit.restart();") + assertTrue(startupRestart.indexOf("invokeImmediateRestartIfSupported(Bukkit.class)") < startupRestart.indexOf("Bukkit.shutdown();")); + assertFalse(startupRestart.contains("Bukkit.restart")); assertFalse(startupRestart.contains("J.s(")); assertFalse(startupRestart.contains("dispatchCommand")); } + public static final class RestartCapableApi { + private static boolean restarted; + + public static void restart() { + restarted = true; + } + } + + public static final class ShutdownOnlyApi { + } + private static String section(String source, String startMarker, String endMarker) { int start = source.indexOf(startMarker); int end = source.indexOf(endMarker, start); diff --git a/core/src/test/java/art/arcane/iris/core/SettingsHotloadWatchTest.java b/core/src/test/java/art/arcane/iris/core/SettingsHotloadWatchTest.java index 0543dde5b..ced8c4ea0 100644 --- a/core/src/test/java/art/arcane/iris/core/SettingsHotloadWatchTest.java +++ b/core/src/test/java/art/arcane/iris/core/SettingsHotloadWatchTest.java @@ -43,7 +43,7 @@ public class SettingsHotloadWatchTest { public void setUp() throws Exception { previousSettings = IrisSettings.settings; dataFolder = temporaryFolder.newFolder("iris-hotload"); - settingsFile = new File(dataFolder, "settings.json"); + settingsFile = new File(dataFolder, "iris.json"); overrideFolder = new File(dataFolder, "languages/overrides"); Files.createDirectories(overrideFolder.toPath()); String settings = settings("en_US"); diff --git a/core/src/test/java/art/arcane/iris/core/gui/VisionRenderTypeTest.java b/core/src/test/java/art/arcane/iris/core/gui/VisionRenderTypeTest.java index d497de073..3c6022c8c 100644 --- a/core/src/test/java/art/arcane/iris/core/gui/VisionRenderTypeTest.java +++ b/core/src/test/java/art/arcane/iris/core/gui/VisionRenderTypeTest.java @@ -25,6 +25,13 @@ public class VisionRenderTypeTest { assertEquals("iris.desktop.vision.mode.river", DesktopUiMessages.VISION_MODE_RIVER.id()); } + @Test + public void teleportCoordinatesFloorAcrossTheNegativeOrigin() { + assertEquals(-1, VisionGUI.floorWorldCoordinate(-0.01D)); + assertEquals(0, VisionGUI.floorWorldCoordinate(0.99D)); + assertEquals(1, VisionGUI.floorWorldCoordinate(1D)); + } + private static void assertTrueCatalogMember(MessageKey key) { assertEquals(key.id(), 1, DesktopUiMessages.keys().stream() .filter(candidate -> candidate.id().equals(key.id())) diff --git a/core/src/test/java/art/arcane/iris/core/lifecycle/WorldLifecycleSelectionTest.java b/core/src/test/java/art/arcane/iris/core/lifecycle/WorldLifecycleSelectionTest.java index 2d4005147..f20fe5f91 100644 --- a/core/src/test/java/art/arcane/iris/core/lifecycle/WorldLifecycleSelectionTest.java +++ b/core/src/test/java/art/arcane/iris/core/lifecycle/WorldLifecycleSelectionTest.java @@ -84,11 +84,11 @@ public class WorldLifecycleSelectionTest { } @Test - public void persistentCreateFallsBackToBukkitBackendOnFoliaWithoutRuntime() { - WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.FOLIA, true, false, false)); + public void persistentCreateFailsClosedOnFoliaWithoutRuntime() { + WorldLifecycleService service = new WorldLifecycleService(CapabilitySnapshot.forTesting(ServerFamily.FOLIA, false, false, false)); WorldLifecycleRequest request = new WorldLifecycleRequest("persistent", NamespacedKey.minecraft("persistent"), World.Environment.NORMAL, null, null, null, true, false, 1337L, false, false, WorldLifecycleCaller.CREATE); - assertEquals("bukkit_public", service.selectCreateBackend(request).backendName()); + assertThrows(IllegalStateException.class, () -> service.selectCreateBackend(request)); } @Test diff --git a/core/src/test/java/art/arcane/iris/core/lifecycle/WorldLifecycleUnloadAsyncTest.java b/core/src/test/java/art/arcane/iris/core/lifecycle/WorldLifecycleUnloadAsyncTest.java index 5783297fe..f3af9776a 100644 --- a/core/src/test/java/art/arcane/iris/core/lifecycle/WorldLifecycleUnloadAsyncTest.java +++ b/core/src/test/java/art/arcane/iris/core/lifecycle/WorldLifecycleUnloadAsyncTest.java @@ -10,6 +10,8 @@ import org.junit.Test; import org.mockito.MockedStatic; import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; @@ -57,6 +59,40 @@ public class WorldLifecycleUnloadAsyncTest { } } + @Test + public void paperLikeManualUnloadDetachesBeforeOffThreadChunkDrain() throws Exception { + String source = Files.readString(Path.of( + "src/main/java/art/arcane/iris/core/lifecycle/WorldLifecycleSupport.java")) + .replace("\r\n", "\n"); + int unloadStart = source.indexOf("private static CompletableFuture unloadWorldWithoutAsyncApi("); + int unloadEnd = source.indexOf("static boolean announceManualWorldUnload", unloadStart); + String unloadMethod = source.substring(unloadStart, unloadEnd); + int capabilityFallback = unloadMethod.indexOf( + "capabilities.minecraftServer() == null || capabilities.removeLevelMethod() == null"); + int bukkitUnload = unloadMethod.indexOf("Bukkit.unloadWorld(world, save)", capabilityFallback); + int detach = unloadMethod.indexOf("detachServerLevelAsync(capabilities, serverLevel, world)"); + int drain = unloadMethod.indexOf("drainChunkTasksAsync(world, serverLevel)"); + int close = unloadMethod.indexOf("closeServerLevelAsync(world, serverLevel)"); + int closeStart = source.indexOf("private static CompletableFuture closeServerLevelAsync("); + int closeEnd = source.indexOf("private static CompletableFuture drainChunkTasksAsync(", closeStart); + String closeMethod = source.substring(closeStart, closeEnd); + int drainStart = closeEnd; + int drainEnd = source.indexOf("private static void removeWorldFromCraftServerMap", drainStart); + String drainMethod = source.substring(drainStart, drainEnd); + + assertTrue(capabilityFallback >= 0); + assertTrue(bukkitUnload > capabilityFallback); + assertTrue(detach > bukkitUnload); + assertTrue(drain > detach); + assertTrue(close > drain); + assertTrue(drainMethod.contains("return J.afut(() ->")); + assertTrue(drainMethod.contains("\"moonrise$getChunkTaskScheduler\"")); + assertTrue(drainMethod.contains("\"halt\"")); + assertTrue(closeMethod.contains("return runGlobalAsync(closeTask)")); + assertFalse(closeMethod.contains("J.afut(")); + assertFalse(closeMethod.contains("J.runRegion(")); + } + @Test public void reflectedAsyncUnloadWaitsForTrueCallback() throws Exception { CallbackServer server = new CallbackServer(); @@ -101,6 +137,49 @@ public class WorldLifecycleUnloadAsyncTest { assertFalse(result.join()); } + @Test + public void reflectedAsyncUnloadAcceptsCanvasResultCallback() throws Exception { + CanvasCallbackServer server = new CanvasCallbackServer(); + Method unloadMethod = CanvasCallbackServer.class.getMethod( + "unloadWorldAsync", + World.class, + boolean.class, + Consumer.class + ); + + CompletableFuture result = WorldLifecycleSupport.invokeAsyncUnload( + server, + unloadMethod, + mock(World.class), + true + ); + + assertFalse(result.isDone()); + server.complete(CanvasUnloadResult.SUCCESS); + assertTrue(result.join()); + } + + @Test + public void reflectedAsyncUnloadMapsCanvasFailureResultToFalse() throws Exception { + CanvasCallbackServer server = new CanvasCallbackServer(); + Method unloadMethod = CanvasCallbackServer.class.getMethod( + "unloadWorldAsync", + World.class, + boolean.class, + Consumer.class + ); + + CompletableFuture result = WorldLifecycleSupport.invokeAsyncUnload( + server, + unloadMethod, + mock(World.class), + false + ); + + server.complete(CanvasUnloadResult.FAILURE); + assertFalse(result.join()); + } + @Test public void reflectedAsyncUnloadPropagatesInvocationFailure() throws Exception { FailingServer server = new FailingServer(); @@ -232,6 +311,27 @@ public class WorldLifecycleUnloadAsyncTest { } } + public static final class CanvasCallbackServer { + private Consumer callback; + + public void unloadWorldAsync(World world, boolean save, Consumer callback) { + this.callback = callback; + } + + private void complete(CanvasUnloadResult result) { + callback.accept(result); + } + } + + public enum CanvasUnloadResult { + SUCCESS, + FAILURE; + + public boolean isSuccess() { + return this == SUCCESS; + } + } + private static final class ControlledBackend implements WorldLifecycleBackend { private final String name; private final CompletableFuture unloadResult = new CompletableFuture<>(); diff --git a/core/src/test/java/art/arcane/iris/core/pack/PackRiverValidatorTest.java b/core/src/test/java/art/arcane/iris/core/pack/PackRiverValidatorTest.java index cfccdb1af..90b18d094 100644 --- a/core/src/test/java/art/arcane/iris/core/pack/PackRiverValidatorTest.java +++ b/core/src/test/java/art/arcane/iris/core/pack/PackRiverValidatorTest.java @@ -79,12 +79,27 @@ public class PackRiverValidatorTest { "minimumSourcesPerTile": 2, "sinkSearchReaches": 8, "routingBasinCells": 7, + "routingDeviationScaleCells": 7, + "routingDeviationStrengthCells": 33, "routingPlateauHeight": 0, + "flowAlignmentWeight": 1025, + "confluenceWeight": -1, + "branchSoftCap": 9, + "branchChildShrinkFactor": 2, "routingStyle": {"zoom": 0} }, "terrain": { "channelWidth": {"min": 40, "max": 12}, - "depth": {} + "depth": {}, + "tunnelWidthMultiplier": {"min": 0.5, "max": 9}, + "tunnelMouthBlend": 17, + "tunnelFloorVariation": 9, + "tunnelRoofVariation": 17, + "tunnelFloorStyle": {"zoom": 0}, + "tunnelRoofStyle": {"zoom": 0} + }, + "caves": { + "parentBiomeInheritance": 2 } } } @@ -96,10 +111,24 @@ public class PackRiverValidatorTest { assertContains(result.errors(), "rivers.topology.minimumSourcesPerTile must not exceed tileCells squared."); assertContains(result.errors(), "rivers.topology.sinkSearchReaches must be at most 7"); assertContains(result.errors(), "rivers.topology.routingBasinCells must be at least 8"); + assertContains(result.errors(), "rivers.topology.routingDeviationScaleCells must be at least 8"); + assertContains(result.errors(), "rivers.topology.routingDeviationStrengthCells must be at most 32"); assertContains(result.errors(), "rivers.topology.routingPlateauHeight must be at least 1"); + assertContains(result.errors(), "rivers.topology.flowAlignmentWeight must be at most 1024"); + assertContains(result.errors(), "rivers.topology.confluenceWeight must be at least 0"); + assertContains(result.errors(), "rivers.topology.branchSoftCap must be at most 8"); + assertContains(result.errors(), "rivers.topology.branchChildShrinkFactor must be at most 1"); assertContains(result.errors(), "rivers.topology.routingStyle.zoom must be at least"); assertContains(result.errors(), "rivers.terrain.channelWidth.min must not exceed"); assertContains(result.errors(), "rivers.terrain.depth must set min and max explicitly."); + assertContains(result.errors(), "rivers.terrain.tunnelWidthMultiplier.min must be at least 1"); + assertContains(result.errors(), "rivers.terrain.tunnelWidthMultiplier.max must be at most 8"); + assertContains(result.errors(), "rivers.terrain.tunnelMouthBlend must be at most 16"); + assertContains(result.errors(), "rivers.terrain.tunnelFloorVariation must be at most 8"); + assertContains(result.errors(), "rivers.terrain.tunnelRoofVariation must be at most 16"); + assertContains(result.errors(), "rivers.terrain.tunnelFloorStyle.zoom must be at least"); + assertContains(result.errors(), "rivers.terrain.tunnelRoofStyle.zoom must be at least"); + assertContains(result.errors(), "rivers.caves.parentBiomeInheritance must be at most 1"); } @Test @@ -177,6 +206,27 @@ public class PackRiverValidatorTest { assertContains(result.errors(), "rivers.terrain.maxDepth"); } + @Test + public void rejectsTunnelFootprintsAboveDerivedHydrologyBudget() throws Exception { + File pack = pack(""" + { + "regions": ["region"], + "rivers": { + "enabled": true, + "terrain": { + "maxChannelWidth": 2048, + "tunnelMouthBlend": 16 + } + } + } + """); + + PackRiverValidator.Validation result = validate(pack); + + assertContains(result.errors(), "exceeds the safe derived hydrology budget"); + assertContains(result.errors(), "columns per generated chunk"); + } + @Test public void rejectsMissingBiomeReferencesAndWarnsAboutInferredRoles() throws Exception { File pack = pack(""" @@ -199,6 +249,44 @@ public class PackRiverValidatorTest { assertContains(result.warnings(), "inferred river role SHORE"); } + @Test + public void netherRiverBiomesKeepNetherDerivativesWithoutOverworldRoleWarnings() throws Exception { + File pack = pack(""" + { + "environment": "NETHER", + "regions": ["region"], + "rivers": { + "enabled": true, + "biomes": { + "channel": ["biome"], + "bank": ["biome"] + } + } + } + """); + write(pack, "biomes/biome.json", """ + { + "name": "Nether River", + "derivative": "minecraft:basalt_deltas", + "vanillaDerivative": "minecraft:basalt_deltas" + } + """); + write(pack, "regions/region.json", """ + { + "landBiomes": ["biome"], + "riverOverride": { + "channelBiomes": ["biome"], + "bankBiomes": ["biome"] + } + } + """); + + PackRiverValidator.Validation result = validate(pack); + + assertFalse(result.errors().toString(), contains(result.errors(), "biome")); + assertFalse(result.warnings().toString(), contains(result.warnings(), "inferred river role")); + } + @Test public void validatesRegionAndBiomeOverridesWhenRiversAreEnabled() throws Exception { File pack = pack(""" diff --git a/core/src/test/java/art/arcane/iris/core/protocol/IrisCursorRequestServiceTest.java b/core/src/test/java/art/arcane/iris/core/protocol/IrisCursorRequestServiceTest.java new file mode 100644 index 000000000..390f7c5bf --- /dev/null +++ b/core/src/test/java/art/arcane/iris/core/protocol/IrisCursorRequestServiceTest.java @@ -0,0 +1,140 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2026 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package art.arcane.iris.core.protocol; + +import art.arcane.iris.engine.framework.Engine; +import art.arcane.iris.engine.object.IrisDimension; +import art.arcane.iris.spi.protocol.IrisProtocol; +import org.junit.Test; + +import java.util.ArrayDeque; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class IrisCursorRequestServiceTest { + private static final Executor DIRECT = Runnable::run; + + @Test + public void burstCoalescesToLatestRequestPerSessionAndOneDrain() { + IrisSessionRegistry registry = new IrisSessionRegistry(); + registerReady(registry, "s1"); + QueuedExecutor executor = new QueuedExecutor(); + IrisCursorRequestService service = new IrisCursorRequestService( + sessionId -> { + throw new AssertionError("queued work must not run early"); + }, + registry, + executor, + 8); + + for (int index = 0; index < 10; index++) { + service.handle("s1", index, -index); + } + + assertEquals(1, service.pendingSize()); + assertEquals(9L, service.coalescedCount()); + assertEquals(1, executor.size()); + } + + @Test + public void saturationDropsOldestSessionsButKeepsQueueBounded() { + IrisSessionRegistry registry = new IrisSessionRegistry(); + QueuedExecutor executor = new QueuedExecutor(); + IrisCursorRequestService service = new IrisCursorRequestService(sessionId -> null, registry, executor, 2); + + service.handle("s1", 1, 1); + service.handle("s2", 2, 2); + service.handle("s3", 3, 3); + + assertEquals(2, service.pendingSize()); + assertEquals(1L, service.droppedSaturatedCount()); + assertEquals(1, executor.size()); + } + + @Test + public void directDrainResolvesAndSendsCursorResponse() { + IrisSessionRegistry registry = new IrisSessionRegistry(); + CountingTransport transport = registerReady(registry, "s1"); + Engine engine = mock(Engine.class); + IrisDimension dimension = mock(IrisDimension.class); + when(engine.isClosed()).thenReturn(false); + when(engine.getSurfaceBiome(4, 8)).thenReturn(null); + when(engine.getRegion(4, 8)).thenReturn(null); + when(engine.getCaveBiome(4, 8)).thenReturn(null); + when(engine.getMinHeight()).thenReturn(-64); + when(engine.getHeight(4, 8)).thenReturn(80); + when(engine.getDimension()).thenReturn(dimension); + when(dimension.getLoadKey()).thenReturn("overworld"); + IrisCursorRequestService service = new IrisCursorRequestService(sessionId -> engine, registry, DIRECT, 8); + + service.handle("s1", 4, 8); + + assertEquals(1L, service.resolvedCount()); + assertEquals(1, transport.sent.get()); + assertEquals(0, service.pendingSize()); + } + + @Test + public void blankSessionIsRejectedWithoutSchedulingWork() { + IrisSessionRegistry registry = new IrisSessionRegistry(); + QueuedExecutor executor = new QueuedExecutor(); + IrisCursorRequestService service = new IrisCursorRequestService(sessionId -> null, registry, executor, 8); + + service.handle(null, 0, 0); + service.handle("", 0, 0); + + assertEquals(2L, service.droppedNoSessionCount()); + assertEquals(0, service.pendingSize()); + assertEquals(0, executor.size()); + } + + private static CountingTransport registerReady(IrisSessionRegistry registry, String sessionId) { + CountingTransport transport = new CountingTransport(); + IrisSession session = new IrisSession(sessionId, transport); + session.markReady(IrisProtocol.PROTOCOL_VERSION, IrisProtocol.CAPABILITY_CURSOR); + registry.register(session); + return transport; + } + + private static final class CountingTransport implements IrisServerTransport { + private final AtomicInteger sent = new AtomicInteger(0); + + @Override + public void sendToClient(String sessionId, byte[] frame) { + sent.incrementAndGet(); + } + } + + private static final class QueuedExecutor implements Executor { + private final ArrayDeque tasks = new ArrayDeque<>(); + + @Override + public void execute(Runnable command) { + tasks.addLast(command); + } + + private int size() { + return tasks.size(); + } + } +} diff --git a/core/src/test/java/art/arcane/iris/core/protocol/IrisProtocolServerTest.java b/core/src/test/java/art/arcane/iris/core/protocol/IrisProtocolServerTest.java index 1245e6020..3fe2ce7c1 100644 --- a/core/src/test/java/art/arcane/iris/core/protocol/IrisProtocolServerTest.java +++ b/core/src/test/java/art/arcane/iris/core/protocol/IrisProtocolServerTest.java @@ -350,6 +350,28 @@ public class IrisProtocolServerTest { assertEquals(1L, server.visionTileForwardedCount()); } + @Test + public void visionTileOutsideWorldBoundsIsRejectedBeforeRendering() { + RecordingTransport transport = new RecordingTransport(); + IrisSessionRegistry registry = new IrisSessionRegistry(); + IrisProtocolServer server = new IrisProtocolServer(registry, SERVER_CAPABILITIES, BRAND, true); + IrisSession session = new IrisSession("s1", transport); + registry.register(session); + server.onClientFrame("s1", IrisMessageCodec.encode(new IrisMessage.ClientHello( + IrisProtocol.PROTOCOL_VERSION, + IrisProtocol.CAPABILITY_VISION))); + RecordingVisionHandler handler = new RecordingVisionHandler(); + server.setVisionTileHandler(handler); + + server.onClientFrame("s1", IrisMessageCodec.encode(new IrisMessage.VisionTileRequest(Integer.MAX_VALUE, 0, 0))); + server.onClientFrame("s1", IrisMessageCodec.encode(new IrisMessage.VisionTileRequest(0, Integer.MIN_VALUE, 8))); + server.onClientFrame("s1", IrisMessageCodec.encode(new IrisMessage.VisionTileRequest(0, 0, Integer.MAX_VALUE))); + + assertEquals(1, handler.count); + assertEquals(2L, server.visionOutOfBoundsCount()); + assertEquals(1L, server.visionTileForwardedCount()); + } + @Test public void visionTileRequestBudgetDropsExcessWithinWindow() { RecordingTransport transport = new RecordingTransport(); diff --git a/core/src/test/java/art/arcane/iris/core/protocol/IrisVisionRequestServiceTest.java b/core/src/test/java/art/arcane/iris/core/protocol/IrisVisionRequestServiceTest.java index d9c5f261c..a15216352 100644 --- a/core/src/test/java/art/arcane/iris/core/protocol/IrisVisionRequestServiceTest.java +++ b/core/src/test/java/art/arcane/iris/core/protocol/IrisVisionRequestServiceTest.java @@ -21,6 +21,9 @@ package art.arcane.iris.core.protocol; import art.arcane.iris.spi.protocol.IrisProtocol; import org.junit.Test; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; @@ -50,6 +53,94 @@ public class IrisVisionRequestServiceTest { assertEquals(maxPending, service.pendingSize()); } + @Test + public void burstUsesAtMostTwoDrainsAndDrainsEveryRetainedRequest() { + IrisSessionRegistry registry = new IrisSessionRegistry(); + registerReady(registry, "s1", IrisProtocol.CAPABILITY_VISION); + EngineResolver resolver = sessionId -> null; + QueuedExecutor executor = new QueuedExecutor(); + int maxPending = 4; + IrisVisionRequestService service = new IrisVisionRequestService(resolver, registry, executor, maxPending); + + for (int index = 0; index < 10; index++) { + service.handle("s1", index, 0, 0); + } + + assertEquals(2, executor.size()); + assertEquals(maxPending, service.pendingSize()); + executor.runAll(); + assertEquals(0, service.pendingSize()); + assertEquals(maxPending, service.droppedNoEngineCount()); + } + + @Test + public void duplicateTileRequestsCoalesceWithoutConsumingCapacity() { + IrisSessionRegistry registry = new IrisSessionRegistry(); + registerReady(registry, "s1", IrisProtocol.CAPABILITY_VISION); + IrisVisionRequestService service = new IrisVisionRequestService(sessionId -> null, registry, DISABLED, 8); + + service.handle("s1", 4, -7, 2); + service.handle("s1", 4, -7, 2); + service.handle("s1", 4, -7, 2); + + assertEquals(1, service.pendingSize()); + assertEquals(0L, service.droppedSaturatedCount()); + } + + @Test + public void drainRotatesAcrossSessionsInsteadOfServingOneBurstFirst() { + IrisSessionRegistry registry = new IrisSessionRegistry(); + registerReady(registry, "s1", IrisProtocol.CAPABILITY_VISION); + registerReady(registry, "s2", IrisProtocol.CAPABILITY_VISION); + ArrayList resolutionOrder = new ArrayList<>(); + EngineResolver resolver = sessionId -> { + resolutionOrder.add(sessionId); + return null; + }; + QueuedExecutor executor = new QueuedExecutor(); + IrisVisionRequestService service = new IrisVisionRequestService(resolver, registry, executor, 8); + + service.handle("s1", 0, 0, 0); + service.handle("s1", 1, 0, 0); + service.handle("s2", 0, 0, 0); + service.handle("s2", 1, 0, 0); + executor.runAll(); + + assertEquals(List.of("s1", "s2", "s1", "s2"), resolutionOrder); + } + + @Test + public void saturatedSessionCannotExcludeAnotherSession() { + IrisSessionRegistry registry = new IrisSessionRegistry(); + registerReady(registry, "noisy", IrisProtocol.CAPABILITY_VISION); + registerReady(registry, "other", IrisProtocol.CAPABILITY_VISION); + IrisVisionRequestService service = new IrisVisionRequestService(sessionId -> null, registry, DISABLED, 4); + + for (int tile = 0; tile < 20; tile++) { + service.handle("noisy", tile, 0, 0); + } + service.handle("other", 0, 0, 0); + + assertEquals(4, service.pendingSize()); + service.clearSession("noisy"); + assertEquals(1, service.pendingSize()); + } + + @Test + public void blankSessionIsRejectedWithoutSchedulingWork() { + IrisSessionRegistry registry = new IrisSessionRegistry(); + QueuedExecutor executor = new QueuedExecutor(); + IrisVisionRequestService service = new IrisVisionRequestService(sessionId -> null, registry, executor, 8); + + service.handle(null, 0, 0, 0); + service.handle("", 0, 0, 0); + service.handle(" ", 0, 0, 0); + + assertEquals(3L, service.droppedNoSessionCount()); + assertEquals(0, service.pendingSize()); + assertEquals(0, executor.size()); + } + @Test public void nullEngineDropsAndCounts() { IrisSessionRegistry registry = new IrisSessionRegistry(); @@ -157,4 +248,27 @@ public class IrisVisionRequestServiceTest { sent.incrementAndGet(); } } + + private static final class QueuedExecutor implements Executor { + private final ArrayDeque tasks = new ArrayDeque<>(); + + @Override + public void execute(Runnable command) { + tasks.addLast(command); + } + + private int size() { + return tasks.size(); + } + + private void runNext() { + tasks.removeFirst().run(); + } + + private void runAll() { + while (!tasks.isEmpty()) { + runNext(); + } + } + } } diff --git a/core/src/test/java/art/arcane/iris/core/runtime/StudioEntitySpawningTest.java b/core/src/test/java/art/arcane/iris/core/runtime/StudioEntitySpawningTest.java index 5c26b11cc..f1001852b 100644 --- a/core/src/test/java/art/arcane/iris/core/runtime/StudioEntitySpawningTest.java +++ b/core/src/test/java/art/arcane/iris/core/runtime/StudioEntitySpawningTest.java @@ -36,12 +36,12 @@ public class StudioEntitySpawningTest { } @Test - public void generalStudioUsesAPlayerModeEligibleForNaturalSpawning() throws IOException { + public void standardStudioDefaultsToSpectatorMode() throws IOException { String source = Files.readString(Path.of( - "src/main/java/art/arcane/iris/core/project/IrisProject.java")).replace("\r\n", "\n"); + "src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java")).replace("\r\n", "\n"); - assertFalse(source.contains("GameMode.SPECTATOR")); - assertTrue(source.contains("GameMode.CREATIVE")); + assertTrue(source.contains("request.openKind().teleportThroughStandardEntry()")); + assertTrue(source.contains("GameMode.SPECTATOR")); } @Test diff --git a/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorCloseSequenceTest.java b/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorCloseSequenceTest.java index be252c212..14d3fa2b8 100644 --- a/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorCloseSequenceTest.java +++ b/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorCloseSequenceTest.java @@ -1,19 +1,19 @@ package art.arcane.iris.core.runtime; +import art.arcane.iris.core.ServerConfigurator; import org.junit.Test; +import org.mockito.MockedStatic; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.Mockito.mockStatic; public class StudioOpenCoordinatorCloseSequenceTest { @Test @@ -104,126 +104,22 @@ public class StudioOpenCoordinatorCloseSequenceTest { } @Test - public void pendingEntryLoadDefersCleanupUntilSettlement() { - CompletableFuture entryLoad = new CompletableFuture<>(); - AtomicInteger cleanups = new AtomicInteger(0); + public void closeFailureAfterUnloadBeginsRequestsTerminalRestart() { + try (MockedStatic configurator = mockStatic(ServerConfigurator.class)) { + StudioOpenCoordinator.requestRestartAfterPartialClose("studio-world", true); - CompletableFuture deferred = StudioOpenCoordinator.deferCleanupUntilEntrySettlement( - entryLoad, - () -> { - cleanups.incrementAndGet(); - return CompletableFuture.completedFuture(null); - }); - - assertEquals(0, cleanups.get()); - assertFalse(deferred.isDone()); - entryLoad.complete(null); - deferred.join(); - assertEquals(1, cleanups.get()); - entryLoad.complete(null); - assertEquals(1, cleanups.get()); - } - - @Test - public void onlyAnUnsettledEntryLoadRequiresDeferredCleanup() { - CompletableFuture pending = new CompletableFuture<>(); - CompletableFuture failed = CompletableFuture.failedFuture( - new IllegalStateException("entry failed")); - - assertTrue(StudioOpenCoordinator.requiresDeferredEntryCleanup(pending)); - assertFalse(StudioOpenCoordinator.requiresDeferredEntryCleanup( - CompletableFuture.completedFuture(null))); - assertFalse(StudioOpenCoordinator.requiresDeferredEntryCleanup(failed)); - assertFalse(StudioOpenCoordinator.requiresDeferredEntryCleanup(null)); - } - - @Test - public void retryRemainsFencedUntilDeferredCleanupCompletes() { - StudioOpenCoordinator.EntryLoadRegistry registry = - new StudioOpenCoordinator.EntryLoadRegistry(); - CompletableFuture entryLoad = new CompletableFuture<>(); - CompletableFuture cleanup = new CompletableFuture<>(); - registry.register("iris-entry-timeout", entryLoad); - CompletableFuture deferred = StudioOpenCoordinator.deferCleanupUntilEntrySettlement( - entryLoad, - () -> cleanup); - registry.releaseAfterSuccessfulCompletion( - "iris-entry-timeout", - entryLoad, - deferred); - - entryLoad.complete(null); - assertFalse(deferred.isDone()); - assertTrue(registry.isFenced("iris-entry-timeout")); - try { - registry.rejectNewOpen(); - fail("Expected the retry to be rejected"); - } catch (IllegalStateException exception) { - assertTrue(exception.getMessage().contains("iris-entry-timeout")); + configurator.verify(() -> ServerConfigurator.restart( + "Studio close failed after world unload began for \"studio-world\".")); } - - cleanup.complete(null); - deferred.join(); - assertFalse(registry.isFenced("iris-entry-timeout")); - registry.rejectNewOpen(); } @Test - public void exceptionalEntrySettlementStillTriggersCleanupOnce() { - CompletableFuture entryLoad = new CompletableFuture<>(); - AtomicInteger cleanups = new AtomicInteger(0); + public void closeFailureBeforeUnloadDoesNotRequestRestart() { + try (MockedStatic configurator = mockStatic(ServerConfigurator.class)) { + StudioOpenCoordinator.requestRestartAfterPartialClose("studio-world", false); - CompletableFuture deferred = StudioOpenCoordinator.deferCleanupUntilEntrySettlement( - entryLoad, - () -> { - cleanups.incrementAndGet(); - return CompletableFuture.completedFuture(null); - }); - - entryLoad.completeExceptionally(new IllegalStateException("entry failed")); - deferred.join(); - assertEquals(1, cleanups.get()); - } - - @Test - public void nonSettlingEntryLoadNeverStartsCleanup() { - CompletableFuture entryLoad = new CompletableFuture<>(); - AtomicInteger cleanups = new AtomicInteger(0); - - CompletableFuture deferred = StudioOpenCoordinator.deferCleanupUntilEntrySettlement( - entryLoad, - () -> { - cleanups.incrementAndGet(); - return CompletableFuture.completedFuture(null); - }); - - assertFalse(deferred.isDone()); - assertEquals(0, cleanups.get()); - } - - @Test - public void deferredCleanupFailureIsPreserved() { - StudioOpenCoordinator.EntryLoadRegistry registry = - new StudioOpenCoordinator.EntryLoadRegistry(); - CompletableFuture entryLoad = new CompletableFuture<>(); - IllegalStateException cleanupFailure = new IllegalStateException("cleanup failed"); - registry.register("iris-cleanup-failure", entryLoad); - CompletableFuture deferred = StudioOpenCoordinator.deferCleanupUntilEntrySettlement( - entryLoad, - () -> CompletableFuture.failedFuture(cleanupFailure)); - registry.releaseAfterSuccessfulCompletion( - "iris-cleanup-failure", - entryLoad, - deferred); - - entryLoad.complete(null); - try { - deferred.join(); - fail("Expected deferred cleanup failure"); - } catch (CompletionException exception) { - assertSame(cleanupFailure, exception.getCause()); + configurator.verifyNoInteractions(); } - assertTrue(registry.isFenced("iris-cleanup-failure")); } private static CompletableFuture phase(List phases, String phase) { diff --git a/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorOpenKindTest.java b/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorOpenKindTest.java index 284bcbc0f..399e885dc 100644 --- a/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorOpenKindTest.java +++ b/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorOpenKindTest.java @@ -27,7 +27,7 @@ public class StudioOpenCoordinatorOpenKindTest { } @Test - public void bothStudioKindsReuseOnlyAnUnchangedLoadedRuntime() { + public void allStudioKindsReuseOnlyAnUnchangedLoadedRuntime() { for (StudioOpenCoordinator.StudioOpenKind kind : StudioOpenCoordinator.StudioOpenKind.values()) { assertEquals( IrisCreator.DatapackPreparation.REUSE_LOADED_RUNTIME_IF_READY, @@ -35,6 +35,15 @@ public class StudioOpenCoordinatorOpenKindTest { } } + @Test + public void objectStudioOwnsWorkspaceWithoutUsingTheStandardEntry() { + StudioOpenCoordinator.StudioOpenKind kind = StudioOpenCoordinator.StudioOpenKind.OBJECT; + + assertTrue(kind.openWorkspace()); + assertFalse(kind.teleportThroughStandardEntry()); + assertTrue(kind.prepareGeneratorState()); + } + @Test public void jigsawStudioLeavesWorkspaceClosedAndUsesItsDestinationTeleport() { StudioOpenCoordinator.StudioOpenKind kind = StudioOpenCoordinator.StudioOpenKind.JIGSAW; @@ -62,22 +71,6 @@ public class StudioOpenCoordinatorOpenKindTest { assertEquals(StudioOpenCoordinator.StudioOpenKind.JIGSAW, request.openKind()); } - @Test - public void onlyAStandardPlayerOpenLoadsTheEntry() { - assertTrue(StudioOpenCoordinator.requiresLoadedEntry(request( - StudioOpenCoordinator.StudioOpenKind.STANDARD, - "Magic_Psycho"))); - assertFalse(StudioOpenCoordinator.requiresLoadedEntry(request( - StudioOpenCoordinator.StudioOpenKind.STANDARD, - null))); - assertFalse(StudioOpenCoordinator.requiresLoadedEntry(request( - StudioOpenCoordinator.StudioOpenKind.STANDARD, - " "))); - assertFalse(StudioOpenCoordinator.requiresLoadedEntry(request( - StudioOpenCoordinator.StudioOpenKind.JIGSAW, - "Magic_Psycho"))); - } - @Test public void activeStudioTeleportIsSerializedAndBoundedBeforeNativeDelegation() throws Exception { String coordinator = Files.readString(Path.of( @@ -103,42 +96,57 @@ public class StudioOpenCoordinatorOpenKindTest { int nativeClaim = coordinatorMethod.indexOf( "activeAdmission.compareAndSet(true, false)"); int nativeDelegation = coordinatorMethod.indexOf( - "WorldRuntimeControlService.get().teleport(player, entry)"); + "WorldRuntimeControlService.get().teleportInMode(player, entry, GameMode.SPECTATOR)"); assertTrue(transitionAdmission >= 0); assertTrue(projectCapture > transitionAdmission); assertTrue(publicDeadline > projectCapture); assertTrue(admissionClose > publicDeadline); assertTrue(serviceMethod.contains("deadlineNanos")); - assertTrue(coordinatorMethod.contains("beforeStudioTeleportDeadline(")); - assertTrue(coordinatorMethod.contains("CompletableFuture bounded = new CompletableFuture<>()")); assertTrue(coordinatorMethod.contains( - "CompletableFuture.delayedExecutor(remainingNanos, TimeUnit.NANOSECONDS)")); - assertFalse(coordinatorMethod.contains("stage.orTimeout(")); - assertTrue(coordinatorMethod.contains("EntryChunkResolution entryResolution")); - assertFalse(coordinatorMethod.contains("addPluginChunkTicket(")); - assertFalse(coordinatorMethod.contains("removePluginChunkTicket(")); + "WorldRuntimeControlService.get().resolveEntryAnchor(world, provider)")); + assertTrue(coordinatorMethod.contains( + "project.getActiveOpenKind() == StudioOpenKind.STANDARD")); + assertFalse(coordinatorMethod.contains("requestChunkAsync(")); + assertFalse(coordinatorMethod.contains("getHighestBlockYAt(")); assertTrue(nativeClaim >= 0); assertTrue(nativeDelegation > nativeClaim); } @Test - public void entryLoadUsesOnlyTheUrgentExactChunkRequest() throws Exception { + public void standardEntryDelegatesWithoutAnExplicitChunkOrSurfaceLookup() throws Exception { String source = Files.readString(Path.of( "src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java")) .replace("\r\n", "\n"); - int loadStart = source.indexOf("private EntryChunkResolution loadEntryChunk("); - int loadEnd = source.indexOf("private void settleEntryUseAfterOperation(", loadStart); - String load = source.substring(loadStart, loadEnd); - assertTrue(load.contains("requestChunkAsync(")); - assertTrue(load.contains("true,\n true")); - assertTrue(load.contains("J.isOwnedByCurrentRegion(world, chunkX, chunkZ)")); - assertTrue(load.contains("J.runRegion(world, chunkX, chunkZ")); - assertTrue(load.contains("WorldRuntimeControlService.findTopSafeStudioLocation(world, entryAnchor)")); + assertTrue(source.contains("Location entryLocation = entryAnchor;")); + assertFalse(source.contains("prepareStudioEntryChunks(")); + assertFalse(source.contains("findStudioEntryLocation")); + assertFalse(source.contains("EntryChunkResolution")); assertFalse(source.contains("resolveSafeEntry(world, entryAnchor)")); - assertFalse(source.contains("addPluginChunkTicket(")); - assertFalse(source.contains("removePluginChunkTicket(")); + assertFalse(source.contains("getHighestBlockYAt(")); + } + + @Test + public void standardEntryBindsSpectatorToTheNativeTeleport() throws Exception { + String source = Files.readString(Path.of( + "src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java")) + .replace("\r\n", "\n"); + String runtime = Files.readString(Path.of( + "src/main/java/art/arcane/iris/core/runtime/WorldRuntimeControlService.java")) + .replace("\r\n", "\n"); + int mode = runtime.indexOf("modeRestore.apply(gameMode)"); + int teleport = runtime.indexOf("teleporter.teleport(player, location)", mode); + + assertTrue(source.contains("WorldRuntimeControlService.get().teleportInMode(")); + int openKind = source.indexOf("request.project().setActiveOpenKind(request.openKind())"); + int provider = source.indexOf("request.project().setActiveProvider(provider)", openKind); + assertTrue(openKind >= 0); + assertTrue(provider > openKind); + assertTrue(source.contains("project.setActiveOpenKind(null)")); + assertTrue(mode >= 0); + assertTrue(teleport > mode); + assertTrue(runtime.contains("modeRestore.restore()")); } @Test @@ -153,9 +161,6 @@ public class StudioOpenCoordinatorOpenKindTest { "prepare_generator", "resolve_entry_anchor", "prepare_structure_rings", - "prepare_entry_chunks", - "load_entry_chunk", - "resolve_safe_entry", "teleport_standard_entry", "finalize_open")) { int current = source.indexOf("\"" + phase + "\"", previous + 1); @@ -166,18 +171,16 @@ public class StudioOpenCoordinatorOpenKindTest { } @Test - public void structureStateActivatesBeforeTheRealEntryChunkLoads() throws Exception { + public void structureStateCompletesBeforeImmediateNativeTeleport() throws Exception { String source = Files.readString(Path.of( "src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java")).replace("\r\n", "\n"); int completionCall = source.indexOf("endStudioEntryBootstrap(world, provider)"); - int entryReady = source.indexOf( - "entryResolution.chunk().get(STUDIO_ENTRY_LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)", completionCall); int teleport = source.indexOf( - "WorldRuntimeControlService.get().teleport(player, safeEntry)", entryReady); + "WorldRuntimeControlService.get().teleportInMode(", completionCall); int finalizeOpen = source.indexOf( "updateStage(request, \"finalize_open\", 1.00D)", teleport); int futureComplete = source.indexOf( - "future.complete(new StudioOpenResult(world, safeEntry))", finalizeOpen); + "future.complete(new StudioOpenResult(world, entryLocation))", finalizeOpen); int methodStart = source.indexOf( "private void endStudioEntryBootstrap(World world, PlatformChunkGenerator provider)"); int methodEnd = source.indexOf("private void abandonStudioEntryBootstrap", methodStart); @@ -188,8 +191,8 @@ public class StudioOpenCoordinatorOpenKindTest { int gateRelease = method.indexOf("bukkitGenerator::endStudioEntryBootstrap"); assertTrue(completionCall >= 0); - assertTrue(entryReady > completionCall); - assertTrue(teleport > entryReady); + assertTrue(teleport > completionCall); + assertFalse(source.contains("preparedEntryChunks")); assertTrue(finalizeOpen > teleport); assertTrue(futureComplete > finalizeOpen); assertTrue(scheduled >= 0); @@ -208,7 +211,7 @@ public class StudioOpenCoordinatorOpenKindTest { "src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java")).replace("\r\n", "\n"); int methodStart = source.indexOf( "private void abandonStudioEntryBootstrap(World world, Throwable failure)"); - int methodEnd = source.indexOf("private void deferFailedOpenCleanup", methodStart); + int methodEnd = source.indexOf("private CompletableFuture cleanupFailedOpen", methodStart); String method = source.substring(methodStart, methodEnd); assertTrue(method.contains("if (J.isPrimaryThread())")); @@ -247,7 +250,7 @@ public class StudioOpenCoordinatorOpenKindTest { "src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java")).replace("\r\n", "\n"); int finalizerCall = source.indexOf("runOpenFinalizer(request.onDone(), world);"); int futureCompletion = source.indexOf( - "future.complete(new StudioOpenResult(world, safeEntry))", finalizerCall); + "future.complete(new StudioOpenResult(world, entryLocation))", finalizerCall); int methodStart = source.indexOf( "private void runOpenFinalizer(Consumer finalizer, World world)"); int methodEnd = source.indexOf("private long elapsedMillis", methodStart); @@ -260,21 +263,4 @@ public class StudioOpenCoordinatorOpenKindTest { assertTrue(method.contains( "completion.get(STUDIO_STRUCTURE_ACTIVATION_TIMEOUT_SECONDS, TimeUnit.SECONDS)")); } - - private static StudioOpenCoordinator.StudioOpenRequest request( - StudioOpenCoordinator.StudioOpenKind openKind, - String playerName - ) { - return new StudioOpenCoordinator.StudioOpenRequest( - "overworld", - null, - null, - 1337L, - "iris-test", - playerName, - openKind, - false, - null, - null); - } } diff --git a/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorSpawnStuckRegressionTest.java b/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorSpawnStuckRegressionTest.java index 530abb273..7eab779f5 100644 --- a/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorSpawnStuckRegressionTest.java +++ b/core/src/test/java/art/arcane/iris/core/runtime/StudioOpenCoordinatorSpawnStuckRegressionTest.java @@ -11,10 +11,10 @@ import static org.junit.Assert.assertTrue; public class StudioOpenCoordinatorSpawnStuckRegressionTest { @Test - public void waitForSafeEntryRetryLoopIsRemoved() { + public void legacySafeEntryRetryLoopIsRemoved() { boolean found = Arrays.stream(StudioOpenCoordinator.class.getDeclaredMethods()) .anyMatch(m -> m.getName().equals("waitForSafeEntry")); - assertFalse("waitForSafeEntry retry loop must be removed — it burns up to 120s on ocean columns", found); + assertFalse("waitForSafeEntry retry loop must remain removed", found); } @Test @@ -32,31 +32,23 @@ public class StudioOpenCoordinatorSpawnStuckRegressionTest { } @Test - public void entryChunkLoadUsesTheUrgentAsyncRequestWithoutRetentionTickets() throws Exception { + public void entryTeleportDoesNotIssueASecondChunkRequest() throws Exception { String source = Files.readString(Path.of( "src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java")).replace("\r\n", "\n"); - int asyncLoad = source.indexOf("requested = WorldRuntimeControlService.get().requestChunkAsync("); - int urgentFlag = source.indexOf("true);", asyncLoad); - assertTrue(asyncLoad >= 0); - assertTrue(urgentFlag > asyncLoad); - assertFalse(source.contains("addPluginChunkTicket")); - assertFalse(source.contains("removePluginChunkTicket")); + assertTrue(source.contains("WorldRuntimeControlService.get().teleportInMode(")); + assertFalse(source.contains("prepareStudioEntryChunks(")); + assertFalse(source.contains("requestChunkAsync(")); } @Test - public void foliaUsesTheSameNonBlockingAsyncEntryPath() throws Exception { + public void foliaEntryPathNeverReadsTerrainOnARegionThread() throws Exception { String source = Files.readString(Path.of( "src/main/java/art/arcane/iris/core/runtime/StudioOpenCoordinator.java")).replace("\r\n", "\n"); - int loadStart = source.indexOf("private EntryChunkResolution loadEntryChunk("); - int loadEnd = source.indexOf("private void settleEntryUseAfterOperation(", loadStart); - String load = source.substring(loadStart, loadEnd); - assertTrue(load.contains("requestChunkAsync(")); - assertTrue(load.contains("J.isOwnedByCurrentRegion(world, chunkX, chunkZ)")); - assertTrue(load.contains("J.runRegion(world, chunkX, chunkZ")); - assertTrue(load.contains("findTopSafeStudioLocation(world, entryAnchor)")); + assertTrue(source.contains("resolveEntryAnchor(world, provider)")); + assertFalse(source.contains("findStudioEntryLocation")); + assertFalse(source.contains("getHighestBlockYAt(")); assertFalse(source.contains("resolveSafeEntry(world, entryAnchor)")); - assertFalse(load.contains("J.isFolia()")); } } diff --git a/core/src/test/java/art/arcane/iris/core/runtime/WorldRuntimeControlServiceSafeEntryTest.java b/core/src/test/java/art/arcane/iris/core/runtime/WorldRuntimeControlServiceSafeEntryTest.java index 9e4999b50..b0875fdcf 100644 --- a/core/src/test/java/art/arcane/iris/core/runtime/WorldRuntimeControlServiceSafeEntryTest.java +++ b/core/src/test/java/art/arcane/iris/core/runtime/WorldRuntimeControlServiceSafeEntryTest.java @@ -7,7 +7,6 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; -import org.bukkit.block.data.Levelled; import org.bukkit.block.data.Waterlogged; import org.bukkit.util.BoundingBox; import org.bukkit.util.VoxelShape; @@ -78,35 +77,6 @@ public class WorldRuntimeControlServiceSafeEntryTest { assertEquals(63, result.getBlockY()); } - @Test - public void studioPrefersNearbyDryGroundWithinTheOwnedChunk() { - World world = loadedWorld(0, 0); - Block water = block(Material.WATER, true, true); - Block stone = block(Material.STONE, false, false, FULL_BLOCK); - Block air = block(Material.AIR, false, true); - doReturn(62).when(world).getHighestBlockYAt(anyInt(), anyInt(), eq(HeightMap.MOTION_BLOCKING_NO_LEAVES)); - doAnswer(invocation -> { - int x = invocation.getArgument(0); - int y = invocation.getArgument(1); - int z = invocation.getArgument(2); - if (x < 0 || x > 15 || z < 0 || z > 15) { - throw new AssertionError("Safe-entry search crossed its Folia-owned source chunk"); - } - if (x == 1 && z == 0) { - return y == 62 ? stone : air; - } - return water; - }).when(world).getBlockAt(anyInt(), anyInt(), anyInt()); - - Location source = new Location(world, 0.5D, 63D, 0.5D); - Location result = WorldRuntimeControlService.findTopSafeStudioLocation(world, source); - - assertNotNull(result); - assertEquals(1, result.getBlockX()); - assertEquals(63, result.getBlockY()); - assertEquals(0, result.getBlockZ()); - } - @Test public void rejectsFluidHazardousAndCollisionBlockedCandidates() { World world = loadedWorld(0, 0); @@ -199,98 +169,6 @@ public class WorldRuntimeControlServiceSafeEntryTest { assertEquals(-1, lowestReadY.get()); } - @Test - public void studioFallsBackAboveWaterSurfaceAfterExhaustingDryCandidates() { - World world = loadedWorld(0, 0); - Block water = waterBlock(0); - Block air = block(Material.AIR, false, true); - doReturn(62).when(world).getHighestBlockYAt(anyInt(), anyInt(), eq(HeightMap.MOTION_BLOCKING_NO_LEAVES)); - doAnswer(invocation -> { - int y = invocation.getArgument(1); - return y <= 62 ? water : air; - }).when(world).getBlockAt(anyInt(), anyInt(), anyInt()); - - Location source = new Location(world, 0.5D, 63D, 0.5D); - Location result = WorldRuntimeControlService.findTopSafeStudioLocation(world, source); - - assertNotNull(result); - assertEquals(0, result.getBlockX()); - assertEquals(63, result.getBlockY()); - assertEquals(0, result.getBlockZ()); - } - - @Test - public void studioDoesNotFallBackAboveLavaSurface() { - World world = loadedWorld(0, 0); - Block lava = block(Material.LAVA, true, true); - Block air = block(Material.AIR, false, true); - doReturn(62).when(world).getHighestBlockYAt(anyInt(), anyInt(), eq(HeightMap.MOTION_BLOCKING_NO_LEAVES)); - doAnswer(invocation -> { - int y = invocation.getArgument(1); - return y == 62 ? lava : air; - }).when(world).getBlockAt(anyInt(), anyInt(), anyInt()); - - Location source = new Location(world, 0.5D, 63D, 0.5D); - Location result = WorldRuntimeControlService.findTopSafeStudioLocation(world, source); - - assertNull(result); - } - - @Test - public void studioRejectsWaterSurfaceWithoutClearHeadroom() { - World world = loadedWorld(0, 0); - Block water = waterBlock(0); - Block air = block(Material.AIR, false, true); - Block stone = block(Material.STONE, false, false, FULL_BLOCK); - doReturn(62).when(world).getHighestBlockYAt(anyInt(), anyInt(), eq(HeightMap.MOTION_BLOCKING_NO_LEAVES)); - doAnswer(invocation -> { - int y = invocation.getArgument(1); - if (y <= 62) { - return water; - } - return y == 64 ? stone : air; - }).when(world).getBlockAt(anyInt(), anyInt(), anyInt()); - - Location source = new Location(world, 0.5D, 63D, 0.5D); - Location result = WorldRuntimeControlService.findTopSafeStudioLocation(world, source); - - assertNull(result); - } - - @Test - public void studioRejectsFlowingWaterSurface() { - World world = loadedWorld(0, 0); - Block flowingWater = waterBlock(1); - Block air = block(Material.AIR, false, true); - doReturn(62).when(world).getHighestBlockYAt(anyInt(), anyInt(), eq(HeightMap.MOTION_BLOCKING_NO_LEAVES)); - doAnswer(invocation -> { - int y = invocation.getArgument(1); - return y <= 62 ? flowingWater : air; - }).when(world).getBlockAt(anyInt(), anyInt(), anyInt()); - - Location source = new Location(world, 0.5D, 63D, 0.5D); - Location result = WorldRuntimeControlService.findTopSafeStudioLocation(world, source); - - assertNull(result); - } - - @Test - public void studioRejectsUnsupportedShallowWaterSurface() { - World world = loadedWorld(0, 0); - Block water = waterBlock(0); - Block air = block(Material.AIR, false, true); - doReturn(62).when(world).getHighestBlockYAt(anyInt(), anyInt(), eq(HeightMap.MOTION_BLOCKING_NO_LEAVES)); - doAnswer(invocation -> { - int y = invocation.getArgument(1); - return y == 62 ? water : air; - }).when(world).getBlockAt(anyInt(), anyInt(), anyInt()); - - Location source = new Location(world, 0.5D, 63D, 0.5D); - Location result = WorldRuntimeControlService.findTopSafeStudioLocation(world, source); - - assertNull(result); - } - @Test public void returnsNullWithoutReadingAnUnloadedChunk() { World world = loadedWorld(0, 0); @@ -331,11 +209,4 @@ public class WorldRuntimeControlServiceSafeEntryTest { return block; } - private static Block waterBlock(int level) { - Block block = block(Material.WATER, true, true); - Levelled blockData = mock(Levelled.class); - doReturn(level).when(blockData).getLevel(); - doReturn(blockData).when(block).getBlockData(); - return block; - } } diff --git a/core/src/test/java/art/arcane/iris/core/runtime/WorldRuntimeControlServiceTeleportTest.java b/core/src/test/java/art/arcane/iris/core/runtime/WorldRuntimeControlServiceTeleportTest.java new file mode 100644 index 000000000..9e06c5b3a --- /dev/null +++ b/core/src/test/java/art/arcane/iris/core/runtime/WorldRuntimeControlServiceTeleportTest.java @@ -0,0 +1,100 @@ +package art.arcane.iris.core.runtime; + +import art.arcane.iris.util.common.scheduling.J; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.MockedStatic; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.TimeoutException; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +public class WorldRuntimeControlServiceTeleportTest { + @Test + public void failedModeTeleportRestoresThePreviousGameMode() { + Player player = mock(Player.class); + Location destination = mock(Location.class); + CompletableFuture nativeTeleport = new CompletableFuture<>(); + when(player.getGameMode()).thenReturn(GameMode.SURVIVAL); + + try (MockedStatic scheduling = immediateEntityScheduling()) { + CompletableFuture result = WorldRuntimeControlService.scheduleTeleport( + player, + destination, + GameMode.SPECTATOR, + (target, location) -> nativeTeleport); + nativeTeleport.complete(false); + + assertFalse(result.join()); + InOrder gameModes = inOrder(player); + gameModes.verify(player).setGameMode(GameMode.SPECTATOR); + gameModes.verify(player).setGameMode(GameMode.SURVIVAL); + } + } + + @Test + public void exceptionalModeTeleportRestoresThePreviousGameMode() { + Player player = mock(Player.class); + Location destination = mock(Location.class); + CompletableFuture nativeTeleport = new CompletableFuture<>(); + when(player.getGameMode()).thenReturn(GameMode.ADVENTURE); + + try (MockedStatic scheduling = immediateEntityScheduling()) { + CompletableFuture result = WorldRuntimeControlService.scheduleTeleport( + player, + destination, + GameMode.SPECTATOR, + (target, location) -> nativeTeleport); + nativeTeleport.completeExceptionally(new IllegalStateException("teleport failed")); + + assertThrows(CompletionException.class, result::join); + InOrder gameModes = inOrder(player); + gameModes.verify(player).setGameMode(GameMode.SPECTATOR); + gameModes.verify(player).setGameMode(GameMode.ADVENTURE); + } + } + + @Test + public void timedOutModeTeleportCancelsNativeWorkAndRestoresThePreviousGameMode() { + Player player = mock(Player.class); + Location destination = mock(Location.class); + CompletableFuture nativeTeleport = new CompletableFuture<>(); + when(player.getGameMode()).thenReturn(GameMode.CREATIVE); + + try (MockedStatic scheduling = immediateEntityScheduling()) { + CompletableFuture result = WorldRuntimeControlService.scheduleTeleport( + player, + destination, + GameMode.SPECTATOR, + (target, location) -> nativeTeleport); + assertTrue(result.completeExceptionally(new TimeoutException("timed out"))); + + assertThrows(CompletionException.class, result::join); + assertTrue(nativeTeleport.isCancelled()); + InOrder gameModes = inOrder(player); + gameModes.verify(player).setGameMode(GameMode.SPECTATOR); + gameModes.verify(player).setGameMode(GameMode.CREATIVE); + } + } + + private static MockedStatic immediateEntityScheduling() { + MockedStatic scheduling = mockStatic(J.class); + scheduling.when(() -> J.runEntity(any(Player.class), any(Runnable.class))).thenAnswer(invocation -> { + invocation.getArgument(1, Runnable.class).run(); + return true; + }); + return scheduling; + } +} diff --git a/core/src/test/java/art/arcane/iris/core/service/StudioSVCWorldPackFeedbackContractTest.java b/core/src/test/java/art/arcane/iris/core/service/StudioSVCWorldPackFeedbackContractTest.java index 9a769b894..b48ee9520 100644 --- a/core/src/test/java/art/arcane/iris/core/service/StudioSVCWorldPackFeedbackContractTest.java +++ b/core/src/test/java/art/arcane/iris/core/service/StudioSVCWorldPackFeedbackContractTest.java @@ -10,13 +10,15 @@ import static org.junit.Assert.assertFalse; public class StudioSVCWorldPackFeedbackContractTest { @Test - public void persistentPackCopyUsesLocalizedStyledOperatorFeedback() throws Exception { + public void persistentPackCopyUsesLifecycleProgressWithoutLegacySnapshotFeedback() throws Exception { String source = Files.readString(Path.of( "src/main/java/art/arcane/iris/core/service/StudioSVC.java" )).replace("\r\n", "\n"); assertEquals(1, occurrences(source, "STUDIO_S_V_C_PACK_COPY_REQUIRES_ASYNC_THREAD")); assertEquals(2, occurrences(source, "STUDIO_S_V_C_PACK_INSTALL_FAILED")); + assertFalse(source.contains("STUDIO_S_V_C_INSTALLING_PACKAGE")); + assertFalse(source.contains("Publishing snapshot")); assertFalse(source.contains("sender.sendMessage(\"Iris refused to copy a pack")); assertFalse(source.contains("sender.sendMessage(\"Failed to install studio pack")); } diff --git a/core/src/test/java/art/arcane/iris/core/tools/IrisCreatorTeleportTest.java b/core/src/test/java/art/arcane/iris/core/tools/IrisCreatorTeleportTest.java index bb4e983d9..34b409460 100644 --- a/core/src/test/java/art/arcane/iris/core/tools/IrisCreatorTeleportTest.java +++ b/core/src/test/java/art/arcane/iris/core/tools/IrisCreatorTeleportTest.java @@ -26,6 +26,8 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; public class IrisCreatorTeleportTest { @Test @@ -50,7 +52,7 @@ public class IrisCreatorTeleportTest { Chunk chunk = mock(Chunk.class); WorldRuntimeControlService runtimeControl = mock(WorldRuntimeControlService.class); Location anchor = new Location(world, 32.5D, 80D, -15.5D); - Location safeEntry = new Location(world, 32.5D, 94D, -15.5D); + Location safeEntry = new Location(world, 33.5D, 91D, -14.5D); doReturn(anchor).when(runtimeControl).resolveEntryAnchor(world); doReturn(CompletableFuture.completedFuture(chunk)) .when(runtimeControl).requestChunkAsync(world, 2, -1, true); @@ -67,6 +69,7 @@ public class IrisCreatorTeleportTest { order.verify(runtimeControl).requestChunkAsync(world, 2, -1, true); order.verify(runtimeControl).resolveSafeEntry(world, anchor); order.verify(runtimeControl).teleport(player, safeEntry); + verifyNoMoreInteractions(runtimeControl); } @Test @@ -89,7 +92,22 @@ public class IrisCreatorTeleportTest { } @Test - public void teleportSenderToCreatedWorld_failsWhenSafeEntryCannotResolve() { + public void teleportSenderToCreatedWorld_failsWhenEntryAnchorCannotResolve() { + Player player = mock(Player.class); + World world = mock(World.class); + WorldRuntimeControlService runtimeControl = mock(WorldRuntimeControlService.class); + doReturn("irisworld").when(world).getName(); + doReturn(null).when(runtimeControl).resolveEntryAnchor(world); + + CompletableFuture result = IrisCreator.teleportSenderToCreatedWorld(player, world, runtimeControl); + + assertThrows(CompletionException.class, result::join); + verify(runtimeControl).resolveEntryAnchor(world); + verifyNoMoreInteractions(runtimeControl); + } + + @Test + public void teleportSenderToCreatedWorld_failsWhenNoSafeEntryExists() { Player player = mock(Player.class); World world = mock(World.class); WorldRuntimeControlService runtimeControl = mock(WorldRuntimeControlService.class); @@ -104,6 +122,11 @@ public class IrisCreatorTeleportTest { CompletableFuture result = IrisCreator.teleportSenderToCreatedWorld(player, world, runtimeControl); assertThrows(CompletionException.class, result::join); + InOrder order = inOrder(runtimeControl); + order.verify(runtimeControl).resolveEntryAnchor(world); + order.verify(runtimeControl).requestChunkAsync(world, 0, 0, true); + order.verify(runtimeControl).resolveSafeEntry(world, anchor); + verifyNoMoreInteractions(runtimeControl); } @Test diff --git a/core/src/test/java/art/arcane/iris/core/tools/WorldCreationProgressReporterTest.java b/core/src/test/java/art/arcane/iris/core/tools/WorldCreationProgressReporterTest.java index f674c48af..cd0016599 100644 --- a/core/src/test/java/art/arcane/iris/core/tools/WorldCreationProgressReporterTest.java +++ b/core/src/test/java/art/arcane/iris/core/tools/WorldCreationProgressReporterTest.java @@ -59,18 +59,38 @@ public class WorldCreationProgressReporterTest { } @Test - public void bukkitHudMutationIsMarshalledOffTheAsyncReporterThread() throws Exception { + public void terminalPlayerMessagesHonorStrictPlaceholderContracts() { + String ready = C.stripColor(WorldCreationProgressReporter.terminalPlayerMessage( + false, + 1.0D, + "World ready", + "", + 1_000L + )); + String failed = C.stripColor(WorldCreationProgressReporter.terminalPlayerMessage( + true, + 0.98D, + "Entering world", + "", + 1_000L + )); + + assertTrue(ready.contains("100% | World ready")); + assertTrue(failed.contains("FAILED | Entering world")); + } + + @Test + public void playerProgressUsesTheEntityScheduledActionBarWithoutABossBar() throws Exception { String source = Files.readString(Path.of( "src/main/java/art/arcane/iris/core/tools/WorldCreationProgressReporter.java" )).replace("\r\n", "\n"); - assertEquals(1, occurrences(source, "Bukkit.createBossBar(")); - assertTrue(source.contains("J.sfut(reporter::initializePlayerHud)")); + assertFalse(source.contains("Bukkit.createBossBar(")); + assertFalse(source.contains("WORLD_CREATE_BOSSBAR_")); + assertFalse(source.contains("isProgressBossBar()")); + assertTrue(source.contains("RuntimeProgressMessages.WORLD_CREATE_LIFECYCLE_ACTION")); assertTrue(source.contains("J.runEntity(sender.player(), guardedRender)")); assertTrue(source.contains("J.runEntity(sender.player(), render)")); - assertTrue(source.contains("J.runEntity(sender.player(), cleanup, 60, cleanup)")); - assertFalse(source.contains("HudSlotRequest")); - assertFalse(source.contains("hudLanes()")); } private static int occurrences(String value, String match) { diff --git a/core/src/test/java/art/arcane/iris/engine/IrisWorldManagerMarkerTest.java b/core/src/test/java/art/arcane/iris/engine/IrisWorldManagerMarkerTest.java index 3f453b72f..1172c14db 100644 --- a/core/src/test/java/art/arcane/iris/engine/IrisWorldManagerMarkerTest.java +++ b/core/src/test/java/art/arcane/iris/engine/IrisWorldManagerMarkerTest.java @@ -1,18 +1,51 @@ package art.arcane.iris.engine; import org.junit.Test; +import org.bukkit.World; +import org.bukkit.entity.LivingEntity; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class IrisWorldManagerMarkerTest { + @Test + public void cancelledMantleCleanupLeavesNoDelayedExecutorEntry() { + ScheduledThreadPoolExecutor executor = IrisWorldManager.createCleanupExecutor("test"); + try { + ScheduledFuture future = executor.schedule(() -> { + }, 1, TimeUnit.DAYS); + + assertEquals(1, executor.getQueue().size()); + assertTrue(future.cancel(false)); + assertEquals(0, executor.getQueue().size()); + } finally { + executor.shutdownNow(); + } + } + + @Test + public void mantleCleanupDoesNotExecuteDelayedTasksAfterShutdown() throws InterruptedException { + ScheduledThreadPoolExecutor executor = IrisWorldManager.createCleanupExecutor("test"); + AtomicBoolean executed = new AtomicBoolean(); + executor.schedule(() -> executed.set(true), 1, TimeUnit.DAYS); + + executor.shutdown(); + + assertTrue(executor.awaitTermination(1, TimeUnit.SECONDS)); + assertFalse(executed.get()); + assertEquals(0, executor.getQueue().size()); + } + @Test public void worldBlockHeightIsTranslatedToMantleHeight() { assertEquals(64, WorldBlockDropRouter.toMantleY(0, -64)); @@ -28,28 +61,13 @@ public class IrisWorldManagerMarkerTest { } @Test - public void completedEntityTasksAreAccepted() { - assertTrue(WorldEntitySpawner.awaitEntityTasks(new CountDownLatch(0), 0, TimeUnit.MILLISECONDS)); - } + public void entitySaturationUsesTheCompleteLivingEntitySnapshot() { + World world = mock(World.class); + LivingEntity first = mock(LivingEntity.class); + LivingEntity second = mock(LivingEntity.class); + when(world.getLivingEntities()).thenReturn(List.of(first, second)); - @Test - public void incompleteEntityTasksAreRejected() { - assertFalse(WorldEntitySpawner.awaitEntityTasks(new CountDownLatch(1), 0, TimeUnit.MILLISECONDS)); - } - - @Test - public void interruptedEntityWaitPreservesInterruptStatus() throws Exception { - AtomicBoolean preserved = new AtomicBoolean(); - Thread thread = new Thread(() -> { - Thread.currentThread().interrupt(); - boolean completed = WorldEntitySpawner.awaitEntityTasks(new CountDownLatch(1), 1, TimeUnit.SECONDS); - preserved.set(!completed && Thread.currentThread().isInterrupted()); - }); - - thread.start(); - thread.join(); - - assertTrue(preserved.get()); + assertEquals(2, WorldEntitySpawner.livingEntityCount(world)); } @Test diff --git a/core/src/test/java/art/arcane/iris/engine/mantle/components/MantleRiverCaveVoxelViewTest.java b/core/src/test/java/art/arcane/iris/engine/mantle/components/MantleRiverCaveVoxelViewTest.java index 6817d5c9e..8ba8cbea2 100644 --- a/core/src/test/java/art/arcane/iris/engine/mantle/components/MantleRiverCaveVoxelViewTest.java +++ b/core/src/test/java/art/arcane/iris/engine/mantle/components/MantleRiverCaveVoxelViewTest.java @@ -2,16 +2,21 @@ package art.arcane.iris.engine.mantle.components; import art.arcane.iris.engine.river.cave.CavePosition; import art.arcane.iris.engine.river.cave.CaveVoxel; +import art.arcane.iris.engine.river.cave.RiverCaveAction; +import art.arcane.iris.engine.river.cave.RiverCaveHydrology; import art.arcane.volmlib.util.collection.KMap; import art.arcane.volmlib.util.mantle.runtime.Mantle; +import art.arcane.volmlib.util.mantle.runtime.MantleChunk; import art.arcane.volmlib.util.mantle.runtime.TectonicPlate; import art.arcane.volmlib.util.matter.Matter; +import art.arcane.volmlib.util.matter.MatterSlice; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; public class MantleRiverCaveVoxelViewTest { @@ -34,4 +39,33 @@ public class MantleRiverCaveVoxelViewTest { assertEquals(CaveVoxel.SOLID, view.voxelAt(terrain)); assertFalse(view.isOpenToSurface(terrain)); } + + @Test + @SuppressWarnings("unchecked") + public void publishedRiverActionsDoNotReplaceTheUnderlyingPlanningBaseline() { + Mantle mantle = mock(Mantle.class); + TectonicPlate plate = mock(TectonicPlate.class); + MantleChunk chunk = mock(MantleChunk.class); + Matter matter = mock(Matter.class); + MatterSlice hydrologySlice = mock(MatterSlice.class); + KMap> regions = new KMap<>(); + regions.put(Mantle.key(0, 0), plate); + CavePosition position = new CavePosition(0, 20, 0); + when(mantle.getLoadedRegions()).thenReturn(regions); + when(plate.get(0, 0)).thenReturn(chunk); + when(chunk.exists(1)).thenReturn(true); + when(chunk.get(1)).thenReturn(matter); + when(matter.hasSlice(RiverCaveHydrology.class)).thenReturn(true); + doReturn(hydrologySlice).when(matter).getSlice(RiverCaveHydrology.class); + when(hydrologySlice.get(0, 4, 0)).thenReturn(RiverCaveHydrology.of(RiverCaveAction.WET_SOURCE)); + MantleRiverCaveVoxelView view = new MantleRiverCaveVoxelView( + mantle, + 128, + (x, z) -> 60, + (x, z) -> null + ); + + assertEquals(CaveVoxel.SOLID, view.voxelAt(position)); + assertEquals(RiverCaveAction.WET_SOURCE, view.riverActionAt(position)); + } } diff --git a/core/src/test/java/art/arcane/iris/engine/mantle/components/MantleRiverHydrologyComponentTest.java b/core/src/test/java/art/arcane/iris/engine/mantle/components/MantleRiverHydrologyComponentTest.java index 97ac8b1ad..de1ed2740 100644 --- a/core/src/test/java/art/arcane/iris/engine/mantle/components/MantleRiverHydrologyComponentTest.java +++ b/core/src/test/java/art/arcane/iris/engine/mantle/components/MantleRiverHydrologyComponentTest.java @@ -99,6 +99,7 @@ public class MantleRiverHydrologyComponentTest { 0, 0, 1, + 2, (minimumX, minimumZ, maximumX, maximumZ) -> riverSample( RiverRouteState.WET, RiverSection.CHANNEL @@ -116,9 +117,10 @@ public class MantleRiverHydrologyComponentTest { } @Test - public void buriedChannelCapsBeforeAnUnrelatedCave() { + public void buriedChannelCutsIntoCaveWhileGuardingItsWaterline() { TestVoxelView view = new TestVoxelView(); view.set(new CavePosition(1, 12, 0), CaveVoxel.CAVE_AIR); + view.set(new CavePosition(1, 14, 0), CaveVoxel.CAVE_AIR); IrisRiverTunnelSample tunnel = new IrisRiverTunnelSample( riverSample(RiverRouteState.WET, RiverSection.CHANNEL), 10, @@ -131,6 +133,72 @@ public class MantleRiverHydrologyComponentTest { 0, 0, 1, + 2, + (minimumX, minimumZ, maximumX, maximumZ) -> riverSample( + RiverRouteState.WET, + RiverSection.CHANNEL + ), + (x, z) -> x == 0 && z == 0 ? tunnel : null, + (x, z) -> IrisRiverSurfaceSample.none(70D, 63D) + ); + + assertEquals(RiverCaveAction.WET_SOURCE, plan.actions().get(new CavePosition(0, 12, 0))); + assertEquals(RiverCaveAction.SEAL_GUARD, plan.actions().get(new CavePosition(1, 12, 0))); + assertFalse(plan.actions().containsKey(new CavePosition(1, 14, 0))); + assertEquals( + new CaveVoxelPrecondition(CaveVoxel.CAVE_AIR, false), + plan.preconditions().get(new CavePosition(1, 14, 0)) + ); + assertTrue(MantleRiverHydrologyComponent.preconditionsHold(view, plan.preconditions())); + } + + @Test + public void buriedChannelRejectsSurfaceOpenCaveAir() { + TestVoxelView view = new TestVoxelView(); + CavePosition contact = new CavePosition(1, 12, 0); + view.set(contact, CaveVoxel.CAVE_AIR); + view.open(contact); + IrisRiverTunnelSample tunnel = new IrisRiverTunnelSample( + riverSample(RiverRouteState.WET, RiverSection.CHANNEL), + 10, + 12, + 14 + ); + + MantleRiverHydrologyComponent.TunnelPlan plan = MantleRiverHydrologyComponent.planTunnels( + view, + 0, + 0, + 1, + 2, + (minimumX, minimumZ, maximumX, maximumZ) -> riverSample( + RiverRouteState.WET, + RiverSection.CHANNEL + ), + (x, z) -> x == 0 && z == 0 ? tunnel : null, + (x, z) -> IrisRiverSurfaceSample.none(70D, 63D) + ); + + assertTrue(plan.actions().isEmpty()); + } + + @Test + public void buriedChannelStillRejectsAnUncontainedFluidCave() { + TestVoxelView view = new TestVoxelView(); + view.set(new CavePosition(1, 12, 0), CaveVoxel.COMPATIBLE_FLUID); + IrisRiverTunnelSample tunnel = new IrisRiverTunnelSample( + riverSample(RiverRouteState.WET, RiverSection.CHANNEL), + 10, + 12, + 14 + ); + + MantleRiverHydrologyComponent.TunnelPlan plan = MantleRiverHydrologyComponent.planTunnels( + view, + 0, + 0, + 1, + 2, (minimumX, minimumZ, maximumX, maximumZ) -> riverSample( RiverRouteState.WET, RiverSection.CHANNEL @@ -148,6 +216,8 @@ public class MantleRiverHydrologyComponentTest { TestVoxelView view = new TestVoxelView(); view.set(new CavePosition(1, 11, 0), CaveVoxel.COMPATIBLE_FLUID); view.set(new CavePosition(1, 12, 0), CaveVoxel.COMPATIBLE_FLUID); + view.set(new CavePosition(1, 13, 0), CaveVoxel.CAVE_AIR); + view.set(new CavePosition(1, 14, 0), CaveVoxel.CAVE_AIR); IrisRiverTunnelSample tunnel = new IrisRiverTunnelSample( riverSample(RiverRouteState.WET, RiverSection.CHANNEL), 10, @@ -168,6 +238,7 @@ public class MantleRiverHydrologyComponentTest { 0, 0, 1, + 2, (minimumX, minimumZ, maximumX, maximumZ) -> riverSample( RiverRouteState.WET, RiverSection.CHANNEL @@ -177,6 +248,7 @@ public class MantleRiverHydrologyComponentTest { ); assertEquals(RiverCaveAction.WET_SOURCE, plan.actions().get(new CavePosition(0, 12, 0))); + assertEquals(RiverCaveAction.DRY_AIR, plan.actions().get(new CavePosition(0, 14, 0))); assertFalse(plan.preconditions().containsKey(new CavePosition(1, 12, 0))); } @@ -199,6 +271,7 @@ public class MantleRiverHydrologyComponentTest { 0, 0, 1, + 2, (minimumX, minimumZ, maximumX, maximumZ) -> riverSample( RiverRouteState.WET, RiverSection.CHANNEL @@ -343,6 +416,29 @@ public class MantleRiverHydrologyComponentTest { assertFalse(plan.actions().containsKey(connectedPit)); } + @Test + public void generatedTargetKeepsItsGrottoShellBelowTheRiverBed() { + IrisRiverCaves caves = caves() + .setDryHeadroom(4) + .setGrottoVerticalRadius(6) + .setMaxBoreDepth(64); + CavePosition entry = new CavePosition(0, 54, 0); + int waterHeadY = 48; + + CavePosition target = MantleRiverHydrologyComponent.findGeneratedTarget( + new TestVoxelView(), + caves, + entry, + waterHeadY, + 0, + 0 + ); + + assertEquals(new CavePosition(0, 46, 0), target); + assertTrue(target.y() + caves.getGrottoVerticalRadius() < entry.y()); + assertTrue(target.y() + caves.getGrottoVerticalRadius() >= waterHeadY + caves.getDryHeadroom()); + } + @Test public void fluidPolicyMappingPreservesReplaceSemantics() { assertEquals( @@ -436,6 +532,7 @@ public class MantleRiverHydrologyComponentTest { 3, -4, 6, + 2, (minimumX, minimumZ, maximumX, maximumZ) -> { footprintSamples.incrementAndGet(); assertEquals(42D, minimumX, 0D); diff --git a/core/src/test/java/art/arcane/iris/engine/modifier/IrisCarveModifierBoundarySupportTest.java b/core/src/test/java/art/arcane/iris/engine/modifier/IrisCarveModifierBoundarySupportTest.java index 01279a14a..9c58fa3a5 100644 --- a/core/src/test/java/art/arcane/iris/engine/modifier/IrisCarveModifierBoundarySupportTest.java +++ b/core/src/test/java/art/arcane/iris/engine/modifier/IrisCarveModifierBoundarySupportTest.java @@ -5,6 +5,8 @@ import art.arcane.iris.core.loader.ResourceLoader; import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.object.IrisBiome; import art.arcane.iris.engine.object.IrisDimensionCarvingResolver; +import art.arcane.iris.engine.river.cave.RiverCaveAction; +import art.arcane.iris.engine.river.cave.RiverCaveHydrology; import art.arcane.iris.spi.PlatformBlockState; import art.arcane.iris.util.project.hunk.Hunk; import art.arcane.volmlib.util.mantle.runtime.MantleChunk; @@ -107,10 +109,52 @@ public class IrisCarveModifierBoundarySupportTest { assertTrue(IrisCarveModifier.hasStableCaveFloorSupport(output, 0, 6, 0)); } + @Test + public void riverGuardOnlyAcceptsStableSolidBoundaryLayers() { + RiverCaveHydrology guard = RiverCaveHydrology.of(RiverCaveAction.SEAL_GUARD); + PlatformBlockState stone = state("minecraft:stone", true); + PlatformBlockState sand = state("minecraft:sand", true); + PlatformBlockState water = state("minecraft:water", false, true); + + assertTrue(IrisCarveModifier.canReplaceRiverGuard(guard, stone, false)); + assertTrue(IrisCarveModifier.canReplaceRiverGuard(guard, stone, true)); + assertTrue(IrisCarveModifier.canReplaceRiverGuard(guard, sand, false)); + assertFalse(IrisCarveModifier.canReplaceRiverGuard(guard, sand, true)); + assertFalse(IrisCarveModifier.canReplaceRiverGuard(guard, water, false)); + } + + @Test + public void riverBiomeInheritanceIsColumnCoherentAndHonorsLimits() { + long seed = 7845123L; + boolean cell = IrisCarveModifier.selectsParentRiverBiome(seed, 8, 12, 0.5D); + for (int x = 8; x < 12; x++) { + for (int z = 12; z < 16; z++) { + assertTrue(cell == IrisCarveModifier.selectsParentRiverBiome(seed, x, z, 0.5D)); + } + } + assertFalse(IrisCarveModifier.selectsParentRiverBiome(seed, 8, 12, 0D)); + assertTrue(IrisCarveModifier.selectsParentRiverBiome(seed, 8, 12, 1D)); + + boolean inherited = false; + boolean overridden = false; + for (int x = -128; x <= 128; x += 4) { + boolean selected = IrisCarveModifier.selectsParentRiverBiome(seed, x, 0, 0.5D); + inherited |= selected; + overridden |= !selected; + } + assertTrue(inherited); + assertTrue(overridden); + } + private PlatformBlockState state(String key, boolean solid) { + return state(key, solid, false); + } + + private PlatformBlockState state(String key, boolean solid, boolean fluid) { PlatformBlockState state = mock(PlatformBlockState.class); doReturn(key).when(state).key(); doReturn(solid).when(state).isSolid(); + doReturn(fluid).when(state).isFluid(); return state; } } diff --git a/core/src/test/java/art/arcane/iris/engine/modifier/IrisCarveModifierInferenceIsolationTest.java b/core/src/test/java/art/arcane/iris/engine/modifier/IrisCarveModifierInferenceIsolationTest.java index 1096c4fbb..06ea0083c 100644 --- a/core/src/test/java/art/arcane/iris/engine/modifier/IrisCarveModifierInferenceIsolationTest.java +++ b/core/src/test/java/art/arcane/iris/engine/modifier/IrisCarveModifierInferenceIsolationTest.java @@ -70,7 +70,7 @@ public class IrisCarveModifierInferenceIsolationTest { Map customBiomes = new HashMap<>(); customBiomes.put("shared", biome); CarveWallBuffer walls = new CarveWallBuffer(1); - walls.put(0, 1, 0, new MatterCavern(true, "shared", (byte) 0)); + walls.put(0, 1, 0, new MatterCavern(true, "shared", (byte) 0), false); Method paintBoundaryZone = IrisCarveModifier.class.getDeclaredMethod( "paintBoundaryZone", Hunk.class, diff --git a/core/src/test/java/art/arcane/iris/engine/modifier/IrisCarveScratchTest.java b/core/src/test/java/art/arcane/iris/engine/modifier/IrisCarveScratchTest.java index d03e4cea1..3e38380c7 100644 --- a/core/src/test/java/art/arcane/iris/engine/modifier/IrisCarveScratchTest.java +++ b/core/src/test/java/art/arcane/iris/engine/modifier/IrisCarveScratchTest.java @@ -22,19 +22,21 @@ public class IrisCarveScratchTest { int y = 20 + index; int z = (index * 3) & 15; MatterCavern cavern = new MatterCavern(true, "cave-" + index, (byte) 0); - buffer.put(x, y, z, cavern); + buffer.put(x, y, z, cavern, (index & 1) == 0); expected.put(key(x, y, z), cavern); } MatterCavern replacement = new MatterCavern(true, "replacement", (byte) 0); - buffer.put(5, 25, 15, replacement); + buffer.put(5, 25, 15, replacement, true); expected.put(key(5, 25, 15), replacement); assertSame(replacement, buffer.get(5, 25, 15)); + assertTrue(buffer.isRiverBoundary(5, 25, 15)); assertNull(buffer.get(5, 26, 15)); + assertFalse(buffer.isRiverBoundary(5, 26, 15)); Map actual = new HashMap<>(); - buffer.forEach((x, y, z, cavern) -> actual.put(key(x, y, z), cavern)); + buffer.forEach((x, y, z, cavern, riverBoundary) -> actual.put(key(x, y, z), cavern)); assertEquals(expected.keySet(), actual.keySet()); for (Map.Entry entry : expected.entrySet()) { assertSame(entry.getValue(), actual.get(entry.getKey())); @@ -47,7 +49,7 @@ public class IrisCarveScratchTest { MatterCavern cavern = new MatterCavern(true, "cave", (byte) 0); scratch.columnMasks[0].add(12); scratch.boundaryMasks[0].add(13); - scratch.walls.put(1, 12, 2, cavern); + scratch.walls.put(1, 12, 2, cavern, true); scratch.customBiomeCache.put("cave", null); scratch.customCaveBiomePresent = true; @@ -58,7 +60,7 @@ public class IrisCarveScratchTest { assertTrue(scratch.customBiomeCache.isEmpty()); assertFalse(scratch.customCaveBiomePresent); int[] wallCount = new int[1]; - scratch.walls.forEach((x, y, z, value) -> wallCount[0]++); + scratch.walls.forEach((x, y, z, value, riverBoundary) -> wallCount[0]++); assertEquals(0, wallCount[0]); } diff --git a/core/src/test/java/art/arcane/iris/engine/object/IrisDimensionRuntimeContractTest.java b/core/src/test/java/art/arcane/iris/engine/object/IrisDimensionRuntimeContractTest.java index b3bb46e86..003359779 100644 --- a/core/src/test/java/art/arcane/iris/engine/object/IrisDimensionRuntimeContractTest.java +++ b/core/src/test/java/art/arcane/iris/engine/object/IrisDimensionRuntimeContractTest.java @@ -2,6 +2,7 @@ package art.arcane.iris.engine.object; import org.junit.Test; +import static art.arcane.iris.engine.object.IrisDimensionTypeOptions.TriState.TRUE; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -79,6 +80,60 @@ public class IrisDimensionRuntimeContractTest { } } + @Test + public void hotloadRejectsEnvironmentChanges() { + IrisDimension active = hotloadDimension(); + IrisDimension replacement = hotloadDimension(); + replacement.setEnvironment(IrisEnvironment.NETHER); + + assertHotloadRejected(active, replacement, "active environment is NORMAL"); + } + + @Test + public void hotloadRejectsCustomEnvironmentEvenWhenItsBaseTypeMatchesNormal() { + IrisDimension active = hotloadDimension(); + IrisDimension replacement = hotloadDimension(); + replacement.setEnvironment(IrisEnvironment.CUSTOM); + + assertHotloadRejected(active, replacement, "replacement environment is CUSTOM"); + } + + @Test + public void hotloadRejectsEffectiveDimensionOptionChanges() { + IrisDimension active = hotloadDimension(); + IrisDimension replacement = hotloadDimension(); + replacement.setDimensionOptions(new IrisDimensionTypeOptions().coordinateScale(2D)); + + assertHotloadRejected(active, replacement, "dimensionOptions"); + } + + @Test + public void hotloadRejectsFullbrightChangesThatAlterTheGeneratedType() { + IrisDimension active = hotloadDimension(); + IrisDimension replacement = hotloadDimension(); + replacement.setFullbright(true); + + assertHotloadRejected(active, replacement, "fullbright"); + } + + @Test + public void hotloadAllowsExplicitOptionsEquivalentToTheBaseTemplate() { + IrisDimension active = hotloadDimension(); + IrisDimension replacement = hotloadDimension(); + replacement.setDimensionOptions(new IrisDimensionTypeOptions().skylight(TRUE)); + + IrisDimensionRuntimeContract.requireHotloadCompatible("Studio world", active, replacement, "iris"); + } + + @Test + public void hotloadAllowsTerrainOnlyDimensionChanges() { + IrisDimension active = hotloadDimension(); + IrisDimension replacement = hotloadDimension(); + replacement.setFluidHeight(80); + + IrisDimensionRuntimeContract.requireHotloadCompatible("Studio world", active, replacement, "iris"); + } + private IrisDimensionRuntimeContract expectedContract() { IrisDimension dimension = new IrisDimension(); dimension.setLoadKey("overworld"); @@ -86,4 +141,22 @@ public class IrisDimensionRuntimeContractTest { dimension.setLogicalHeight(512); return IrisDimensionRuntimeContract.expected(dimension, "iris"); } + + private IrisDimension hotloadDimension() { + IrisDimension dimension = new IrisDimension(); + dimension.setLoadKey("overworld"); + dimension.setEnvironment(IrisEnvironment.NORMAL); + dimension.setDimensionHeight(new IrisRange(-64, 320)); + dimension.setLogicalHeight(384); + return dimension; + } + + private void assertHotloadRejected(IrisDimension active, IrisDimension replacement, String messageFragment) { + try { + IrisDimensionRuntimeContract.requireHotloadCompatible("Studio world", active, replacement, "iris"); + fail("A hotload cannot replace the generated dimension type contract"); + } catch (IrisDimensionContractException e) { + assertTrue(e.getMessage().contains(messageFragment)); + } + } } diff --git a/core/src/test/java/art/arcane/iris/engine/river/RiverNetworkTest.java b/core/src/test/java/art/arcane/iris/engine/river/RiverNetworkTest.java index 68512354a..5e1c2e0e4 100644 --- a/core/src/test/java/art/arcane/iris/engine/river/RiverNetworkTest.java +++ b/core/src/test/java/art/arcane/iris/engine/river/RiverNetworkTest.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; @@ -20,6 +21,8 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; public class RiverNetworkTest { + private static final double TWO_PI_FOR_TESTS = StrictMath.PI * 2D; + @Test public void mapsNegativeWorldCoordinatesWithFloorDivision() { RiverNetwork network = new RiverNetwork(options(1L).build()); @@ -32,6 +35,28 @@ public class RiverNetworkTest { assertEquals(0, network.tileXForBlock(0)); } + @Test + public void expandedSamplingAddsOnlyTheRequestedLateralRadius() { + RiverNode from = node(0L, 0L, 0D, 0D); + RiverNode to = node(1L, 0L, 100D, 0D); + RiverTile tile = new RiverTile( + 0, + 0, + 0, + -64, + 128, + 64, + List.of(reach(from, to, 4D, 1D)) + ); + + assertFalse(tile.sample(50D, 5D).present()); + RiverSample expanded = tile.sampleExpanded(50D, 5D, 2D); + assertTrue(expanded.present()); + assertEquals(RiverSection.BANK, expanded.section()); + assertEquals(5D, expanded.distance(), 0D); + assertThrows(IllegalArgumentException.class, () -> tile.sampleExpanded(50D, 5D, -1D)); + } + @Test public void graphHasReciprocalCardinalsAndOneStableDiagonalPerSquare() { RiverNetwork network = new RiverNetwork(options(2L).build()); @@ -138,6 +163,30 @@ public class RiverNetworkTest { assertEquals(digest(tile), digest(network.buildTile(0, 0, terrain))); } + @Test + public void recursiveBranchesApplyTheChildSoftCapAtEveryNode() { + RiverNetwork network = new RiverNetwork(options(720L) + .requireOcean(false) + .routingBasinCells(16) + .branchSoftCap(4) + .branchChildShrinkFactor(0D) + .build()); + RiverTerrainSampler terrain = flatTerrain(false); + HashMap incoming = new HashMap<>(); + for (long cellX = -24L; cellX <= 24L; cellX++) { + for (long cellZ = -24L; cellZ <= 24L; cellZ++) { + RiverNode downstream = network.downstream(new RiverNodeId(cellX, cellZ), terrain); + if (downstream != null) { + incoming.merge(downstream.id(), 1, Integer::sum); + } + } + } + + assertFalse(incoming.isEmpty()); + assertTrue(incoming.values().stream().allMatch(children -> children <= 4)); + assertTrue(incoming.values().stream().anyMatch(children -> children >= 3)); + } + @Test public void candidateRankingAndTracingDoNotMaterializeMeanderGeometry() { RiverNetwork network = new RiverNetwork(options(75L) @@ -167,8 +216,8 @@ public class RiverNetworkTest { assertFalse(tile.reaches().isEmpty()); assertTrue(meanderSamples[0] > 0); - assertEquals(0, meanderSamples[0] % 33); - assertTrue(meanderSamples[0] / 33 <= 256); + assertEquals(0, meanderSamples[0] % 12); + assertTrue(meanderSamples[0] / 12 <= 256); } @Test @@ -330,27 +379,33 @@ public class RiverNetworkTest { assertFalse(wet.edges().isEmpty()); RiverNetwork dryNetwork = new RiverNetwork(options(4L).maxRouteReaches(6).build()); - RiverRoute dry = dryNetwork.trace(source, slopedTerrain(false)); + RiverTerrainSampler physicalTerminal = new TestTerrain(false) { + @Override + public boolean allowsReach(RiverRoutingContext context) { + return context.from().id().equals(source); + } + }; + RiverRoute dry = dryNetwork.trace(source, physicalTerminal); assertEquals(RiverRouteState.DRY, dry.state()); assertFalse(dry.edges().isEmpty()); RiverNetwork suppressedNetwork = new RiverNetwork( options(4L).maxRouteReaches(6).dryChannelChance(0.0).build() ); - RiverRoute suppressed = suppressedNetwork.trace(source, slopedTerrain(false)); + RiverRoute suppressed = suppressedNetwork.trace(source, physicalTerminal); assertEquals(RiverRouteState.SUPPRESSED, suppressed.state()); assertTrue(suppressed.edges().isEmpty()); } @Test - public void oceanOptionalRoutesRemainWetAndExposeTerminalState() { + public void oceanOptionalRoutesDoNotTurnTheTraceHorizonIntoATerminal() { RiverNetwork network = new RiverNetwork( options(41L).maxRouteReaches(3).requireOcean(false).build() ); RiverRoute route = network.trace(new RiverNodeId(0L, 0L), slopedTerrain(false)); assertEquals(RiverRouteState.WET, route.state()); assertFalse(route.oceanConnected()); - assertTrue(route.terminal()); + assertFalse(route.terminal()); assertFalse(route.edges().isEmpty()); } @@ -359,12 +414,22 @@ public class RiverNetworkTest { RiverNodeId source = new RiverNodeId(0L, 0L); RiverNetwork requiredOcean = new RiverNetwork(options(42L).maxRouteReaches(3).requireOcean(true).build()); RiverTerrainSampler wetTerminal = new TestTerrain(false) { + @Override + public boolean allowsReach(RiverRoutingContext context) { + return context.from().id().equals(source); + } + @Override public RiverTerminalPolicy terminalPolicy(int blockX, int blockZ) { return RiverTerminalPolicy.WET; } }; RiverTerrainSampler suppressedTerminal = new TestTerrain(false) { + @Override + public boolean allowsReach(RiverRoutingContext context) { + return context.from().id().equals(source); + } + @Override public RiverTerminalPolicy terminalPolicy(int blockX, int blockZ) { return RiverTerminalPolicy.SUPPRESS; @@ -376,6 +441,11 @@ public class RiverNetworkTest { RiverNetwork optionalOcean = new RiverNetwork(options(43L).maxRouteReaches(3).requireOcean(false).build()); RiverTerrainSampler dryTerminal = new TestTerrain(false) { + @Override + public boolean allowsReach(RiverRoutingContext context) { + return context.from().id().equals(source); + } + @Override public RiverTerminalPolicy terminalPolicy(int blockX, int blockZ) { return RiverTerminalPolicy.DRY; @@ -546,10 +616,10 @@ public class RiverNetworkTest { ); assertEquals(List.of( - -6381557853649488119L, - 8976828799821705624L, - 6774369135184562919L, - -6208289191927305511L + 3050685048971830506L, + -3861364772874819248L, + -8770785170128501517L, + 6698607697170340377L ), actual); } @@ -589,6 +659,7 @@ public class RiverNetworkTest { .channelWidth(512D) .maxChannelWidth(512D) .bankWidth(0D) + .branchChildShrinkFactor(1D) .meanderStrength(0D) .meanderSubdivisions(1) .build()); @@ -716,6 +787,7 @@ public class RiverNetworkTest { 1, 1, 10.0, + RiverWidthProfile.constant(10.0), 5.0, 3.0, false, @@ -795,6 +867,7 @@ public class RiverNetworkTest { .maxChannelWidth(10D) .maxBankWidth(3D) .maxDepth(6D) + .meanderStrength(0D) .build()); RiverTerrainSampler styled = new TestTerrain(false) { @Override @@ -832,6 +905,78 @@ public class RiverNetworkTest { } } + @Test + public void channelWidthVariesInsideOneReachAndRemainsSpatiallyQueryable() { + RiverNode from = node(0L, 0L, 0D, 0D); + RiverNode to = node(1L, 0L, 100D, 0D); + RiverWidthProfile profile = new RiverWidthProfile( + new double[]{0D, 0.5D, 1D}, + new double[]{1D, 7D, 1D} + ); + RiverReach reach = new RiverReach( + RiverEdgeId.of(from.id(), to.id()), + from, + to, + RiverRouteState.WET, + 1, + 1, + 7D, + profile, + 0D, + 3D, + false, + false, + new RiverPolyline(new double[]{0D, 100D}, new double[]{0D, 0D}) + ); + RiverTile tile = new RiverTile(0, 0, 0, -16, 128, 32, List.of(reach)); + + RiverSample narrow = tile.sample(10D, 2D); + RiverSample wide = tile.sample(50D, 2D); + + assertFalse(narrow.present()); + assertTrue(wide.present()); + assertEquals(7D, wide.width(), 0D); + assertEquals(RiverSection.CHANNEL, wide.section()); + } + + @Test + public void foldedReachSamplingFindsFartherCoveringWidthEnvelope() { + RiverNode from = node(0L, 0L, 0D, 0D); + RiverNode to = node(1L, 0L, 0D, 10D); + RiverWidthProfile profile = new RiverWidthProfile( + new double[]{0D, 0.48D, 0.53D, 1D}, + new double[]{1D, 1D, 18D, 18D} + ); + RiverReach reach = new RiverReach( + RiverEdgeId.of(from.id(), to.id()), + from, + to, + RiverRouteState.WET, + 1, + 1, + 18D, + profile, + 0D, + 3D, + false, + false, + new RiverPolyline( + new double[]{0D, 100D, 100D, 0D}, + new double[]{0D, 0D, 10D, 10D} + ) + ); + RiverTile tile = new RiverTile(0, 0, 0, -16, 128, 32, List.of(reach)); + + RiverSample point = tile.sample(50D, 2D); + RiverSample footprint = tile.sampleFootprint(49D, 1.5D, 51D, 2.5D); + + assertTrue(point.present()); + assertTrue(point.alongReach() > 0.53D); + assertEquals(18D, point.width(), 0.000001D); + assertTrue(footprint.present()); + assertTrue(footprint.alongReach() > 0.53D); + } + @Test public void reachFeasibilityReceivesTheFinalPinnedMeanderPolyline() { RiverNetwork network = new RiverNetwork(options(62L).meanderStrength(20D).build()); @@ -865,6 +1010,182 @@ public class RiverNetworkTest { assertEquals(null, downstream); } + @Test + public void irisNoiseChangesTheSelectedPersonalityWithoutBreakingItsEnvelope() { + double meanderStrength = 20D; + RiverNetwork network = new RiverNetwork(options(63L) + .siteJitter(0D) + .requireOcean(false) + .meanderStrength(meanderStrength) + .meanderSubdivisions(32) + .build()); + RiverTerrainSampler quietTerrain = new TestTerrain(false) { + @Override + public double meanderNoise(RiverMeanderContext context) { + return 0D; + } + }; + RiverTerrainSampler noisyTerrain = new TestTerrain(false) { + @Override + public double meanderNoise(RiverMeanderContext context) { + return StrictMath.sin(context.normalizedPosition() * StrictMath.PI * 7D); + } + }; + + RiverReach quietReach = network.buildTile(0, 0, quietTerrain).reaches().getFirst(); + RiverReach noisyReach = network.buildTile(0, 0, noisyTerrain).reaches().getFirst(); + assertEquals(quietReach.id(), noisyReach.id()); + RiverPolyline quietPolyline = quietReach.polyline(); + RiverPolyline noisyPolyline = noisyReach.polyline(); + double deltaX = noisyReach.to().x() - noisyReach.from().x(); + double deltaZ = noisyReach.to().z() - noisyReach.from().z(); + double chordLength = StrictMath.hypot(deltaX, deltaZ); + boolean changed = false; + for (int point = 1; point < noisyPolyline.size() - 1; point++) { + double pointX = noisyPolyline.x(point) - noisyReach.from().x(); + double pointZ = noisyPolyline.z(point) - noisyReach.from().z(); + double signedOffset = (pointX * deltaZ - pointZ * deltaX) / chordLength; + if (Double.doubleToLongBits(noisyPolyline.x(point)) != Double.doubleToLongBits(quietPolyline.x(point)) + || Double.doubleToLongBits(noisyPolyline.z(point)) + != Double.doubleToLongBits(quietPolyline.z(point))) { + changed = true; + } + assertTrue(StrictMath.abs(signedOffset) <= meanderStrength + 0.0000001D); + } + + assertTrue(changed); + assertEquals(noisyReach.from().x(), noisyPolyline.x(0), 0D); + assertEquals(noisyReach.from().z(), noisyPolyline.z(0), 0D); + int last = noisyPolyline.size() - 1; + assertEquals(noisyReach.to().x(), noisyPolyline.x(last), 0D); + assertEquals(noisyReach.to().z(), noisyPolyline.z(last), 0D); + } + + @Test + public void reachesSpanBroadAsymmetricCompoundAndRestlessShapeFamilies() { + double meanderStrength = 20D; + RiverNetwork network = new RiverNetwork(options(64L) + .siteJitter(0D) + .requireOcean(false) + .meanderStrength(meanderStrength) + .meanderSubdivisions(48) + .build()); + RiverTerrainSampler terrain = new TestTerrain(false) { + @Override + public double meanderNoise(RiverMeanderContext context) { + return 0D; + } + }; + + HashMap uniqueReaches = new HashMap<>(); + for (int tileX = -2; tileX <= 2; tileX++) { + for (int tileZ = -2; tileZ <= 2; tileZ++) { + for (RiverReach reach : network.buildTile(tileX, tileZ, terrain).reaches()) { + uniqueReaches.putIfAbsent(reach.id(), reach); + } + } + } + ArrayList shapes = new ArrayList<>(uniqueReaches.size()); + for (RiverReach reach : uniqueReaches.values()) { + shapes.add(shapeOf(reach, meanderStrength)); + } + double minimumAmplitude = shapes.stream() + .mapToDouble(ReachShape::amplitudeRatio) + .min() + .orElseThrow(); + double maximumAmplitude = shapes.stream() + .mapToDouble(ReachShape::amplitudeRatio) + .max() + .orElseThrow(); + HashSet dominantBands = new HashSet<>(); + HashSet signatures = new HashSet<>(); + for (ReachShape shape : shapes) { + dominantBands.add(shape.dominantBand()); + signatures.add( + StrictMath.min(6, shape.crossings()) + ":" + + (int) StrictMath.floor(shape.centroid() * 5D) + ":" + + (int) StrictMath.floor(shape.amplitudeRatio() * 5D) + ":" + + shape.dominantBand() + ); + } + String diagnostics = shapes.toString(); + assertTrue(diagnostics, shapes.size() >= 32); + assertTrue(diagnostics, shapes.stream().anyMatch(shape -> shape.crossings() <= 1)); + assertTrue(diagnostics, shapes.stream().anyMatch(shape -> shape.crossings() >= 2 && shape.crossings() <= 4)); + assertTrue(diagnostics, shapes.stream().anyMatch(shape -> shape.crossings() >= 5)); + assertTrue(diagnostics, shapes.stream().anyMatch(shape -> shape.centroid() < 0.43D)); + assertTrue(diagnostics, shapes.stream().anyMatch(shape -> shape.centroid() > 0.57D)); + assertTrue(diagnostics, maximumAmplitude - minimumAmplitude >= 0.25D); + assertTrue(diagnostics, dominantBands.size() >= 3); + assertTrue(diagnostics, signatures.size() >= 8); + } + + @Test + public void drainageDomainWarpChangesRoutesDeterministically() { + RiverTerrainSampler terrain = flatTerrain(false); + RiverNetwork straight = new RiverNetwork(options(65L) + .requireOcean(false) + .routingDeviationStrengthCells(0D) + .build()); + RiverNetwork deviated = new RiverNetwork(options(65L) + .requireOcean(false) + .routingDeviationScaleCells(8) + .routingDeviationStrengthCells(4D) + .build()); + boolean changed = false; + for (long cellX = -8L; cellX <= 8L; cellX++) { + for (long cellZ = -8L; cellZ <= 8L; cellZ++) { + RiverNode straightDownstream = straight.downstream(new RiverNodeId(cellX, cellZ), terrain); + RiverNode deviatedDownstream = deviated.downstream(new RiverNodeId(cellX, cellZ), terrain); + if (!Objects.equals( + straightDownstream == null ? null : straightDownstream.id(), + deviatedDownstream == null ? null : deviatedDownstream.id() + )) { + changed = true; + } + RiverNode repeated = deviated.downstream(new RiverNodeId(cellX, cellZ), terrain); + assertEquals( + deviatedDownstream == null ? null : deviatedDownstream.id(), + repeated == null ? null : repeated.id() + ); + } + } + assertTrue(changed); + } + + @Test + public void confluenceAttractionChangesDownstreamChoicesDeterministically() { + RiverTerrainSampler terrain = flatTerrain(false); + RiverNetwork dispersed = new RiverNetwork(options(67L) + .requireOcean(false) + .confluenceWeight(0D) + .build()); + RiverNetwork branching = new RiverNetwork(options(67L) + .requireOcean(false) + .confluenceWeight(512D) + .build()); + boolean changed = false; + for (long cellX = -8L; cellX <= 8L; cellX++) { + for (long cellZ = -8L; cellZ <= 8L; cellZ++) { + RiverNodeId id = new RiverNodeId(cellX, cellZ); + RiverNode dispersedDownstream = dispersed.downstream(id, terrain); + RiverNode branchingDownstream = branching.downstream(id, terrain); + if (!Objects.equals( + dispersedDownstream == null ? null : dispersedDownstream.id(), + branchingDownstream == null ? null : branchingDownstream.id() + )) { + changed = true; + } + RiverNode repeated = branching.downstream(id, terrain); + assertEquals( + branchingDownstream == null ? null : branchingDownstream.id(), + repeated == null ? null : repeated.id() + ); + } + } + assertTrue(changed); + } + @Test public void optionsRejectNonFiniteOrUnsafeValues() { assertThrows(IllegalArgumentException.class, () -> options(10L).sourceChance(Double.NaN).build()); @@ -872,6 +1193,9 @@ public class RiverNetworkTest { assertThrows(IllegalArgumentException.class, () -> options(10L).routingBasinCells(7).build()); assertThrows(IllegalArgumentException.class, () -> options(10L).routingPlateauHeight(0D).build()); assertThrows(IllegalArgumentException.class, () -> options(10L).hydraulicBaseHeight(Double.NaN).build()); + assertThrows(IllegalArgumentException.class, () -> options(10L).confluenceWeight(-1D).build()); + assertThrows(IllegalArgumentException.class, () -> options(10L).branchSoftCap(0).build()); + assertThrows(IllegalArgumentException.class, () -> options(10L).branchChildShrinkFactor(1.1D).build()); assertThrows(IllegalArgumentException.class, () -> options(10L).channelWidth(-1.0).build()); assertThrows(IllegalArgumentException.class, () -> options(10L) .tileCells(1) @@ -981,6 +1305,7 @@ public class RiverNetworkTest { 1, 1, width, + RiverWidthProfile.constant(width), bankWidth, 3D, false, @@ -1022,6 +1347,57 @@ public class RiverNetworkTest { return hydraulicComparison != 0 ? hydraulicComparison : first.id().compareTo(second.id()); } + private static ReachShape shapeOf(RiverReach reach, double meanderStrength) { + RiverPolyline polyline = reach.polyline(); + double deltaX = reach.to().x() - reach.from().x(); + double deltaZ = reach.to().z() - reach.from().z(); + double chordLength = StrictMath.hypot(deltaX, deltaZ); + double[] offsets = new double[polyline.size()]; + double maximumOffset = 0D; + double weightedPosition = 0D; + double totalWeight = 0D; + int crossings = 0; + int previousSign = 0; + for (int point = 0; point < polyline.size(); point++) { + double pointX = polyline.x(point) - reach.from().x(); + double pointZ = polyline.z(point) - reach.from().z(); + double offset = (pointX * deltaZ - pointZ * deltaX) / chordLength; + offsets[point] = offset; + double absoluteOffset = StrictMath.abs(offset); + maximumOffset = StrictMath.max(maximumOffset, absoluteOffset); + double t = (double) point / (polyline.size() - 1); + weightedPosition += t * absoluteOffset; + totalWeight += absoluteOffset; + int sign = absoluteOffset < meanderStrength * 0.05D ? 0 : offset < 0D ? -1 : 1; + if (sign != 0 && previousSign != 0 && sign != previousSign) { + crossings++; + } + if (sign != 0) { + previousSign = sign; + } + assertTrue(absoluteOffset <= meanderStrength + 0.0000001D); + } + int dominantBand = 1; + double dominantEnergy = -1D; + int samples = offsets.length - 1; + for (int frequency = 1; frequency <= 8; frequency++) { + double cosine = 0D; + double sine = 0D; + for (int point = 0; point < samples; point++) { + double phase = TWO_PI_FOR_TESTS * frequency * point / samples; + cosine += offsets[point] * StrictMath.cos(phase); + sine += offsets[point] * StrictMath.sin(phase); + } + double energy = cosine * cosine + sine * sine; + if (energy > dominantEnergy) { + dominantEnergy = energy; + dominantBand = frequency; + } + } + double centroid = totalWeight <= 0.0000001D ? 0.5D : weightedPosition / totalWeight; + return new ReachShape(crossings, maximumOffset / meanderStrength, centroid, dominantBand); + } + private static long digest(RiverTile tile) { long hash = 0xCBF29CE484222325L; for (RiverReach reach : tile.reaches()) { @@ -1032,6 +1408,10 @@ public class RiverNetworkTest { hash = RiverNetwork.mix(hash ^ reach.order()); hash = RiverNetwork.mix(hash ^ reach.state().ordinal()); hash = RiverNetwork.mix(hash ^ Double.doubleToLongBits(reach.width())); + for (int index = 0; index < reach.widthProfile().size(); index++) { + hash = RiverNetwork.mix(hash ^ Double.doubleToLongBits(reach.widthProfile().position(index))); + hash = RiverNetwork.mix(hash ^ Double.doubleToLongBits(reach.widthProfile().width(index))); + } hash = RiverNetwork.mix(hash ^ Double.doubleToLongBits(reach.bankWidth())); hash = RiverNetwork.mix(hash ^ Double.doubleToLongBits(reach.depth())); hash = RiverNetwork.mix(hash ^ (reach.mouth() ? 1L : 0L)); @@ -1062,6 +1442,7 @@ public class RiverNetworkTest { assertEquals(first.flow(), second.flow()); assertEquals(first.order(), second.order()); assertEquals(first.width(), second.width(), 0.0); + assertEquals(first.widthProfile(), second.widthProfile()); assertEquals(first.bankWidth(), second.bankWidth(), 0.0); assertEquals(first.depth(), second.depth(), 0.0); assertEquals(first.mouth(), second.mouth()); @@ -1092,4 +1473,7 @@ public class RiverNetworkTest { return ocean && blockX >= 224; } } + + private record ReachShape(int crossings, double amplitudeRatio, double centroid, int dominantBand) { + } } diff --git a/core/src/test/java/art/arcane/iris/engine/river/RiverTileCacheTest.java b/core/src/test/java/art/arcane/iris/engine/river/RiverTileCacheTest.java index 25334414f..7c963ea94 100644 --- a/core/src/test/java/art/arcane/iris/engine/river/RiverTileCacheTest.java +++ b/core/src/test/java/art/arcane/iris/engine/river/RiverTileCacheTest.java @@ -295,6 +295,7 @@ public class RiverTileCacheTest { 1, 1, 8.0, + RiverWidthProfile.constant(8.0), 4.0, 3.0, false, diff --git a/core/src/test/java/art/arcane/iris/engine/river/cave/RiverCaveContainmentPlannerTest.java b/core/src/test/java/art/arcane/iris/engine/river/cave/RiverCaveContainmentPlannerTest.java index 208f15af4..f40a2d3a0 100644 --- a/core/src/test/java/art/arcane/iris/engine/river/cave/RiverCaveContainmentPlannerTest.java +++ b/core/src/test/java/art/arcane/iris/engine/river/cave/RiverCaveContainmentPlannerTest.java @@ -160,8 +160,8 @@ public class RiverCaveContainmentPlannerTest { RiverCavePlan rejected = planner.plan(view, source, rejectedSettings); assertTrue(accepted.accepted()); - assertEquals(RiverCaveAction.DRY_AIR, accepted.actions().get(position(0, 11, 0))); - assertEquals(RiverCaveAction.DRY_AIR, accepted.actions().get(position(0, 12, 0))); + assertEquals(RiverCaveAction.FALLING_WATER, accepted.actions().get(position(0, 11, 0))); + assertEquals(RiverCaveAction.FALLING_WATER, accepted.actions().get(position(0, 12, 0))); assertEquals(RiverCaveRejection.DRY_HEADROOM_LIMIT, rejected.rejection()); } @@ -341,7 +341,7 @@ public class RiverCaveContainmentPlannerTest { RiverCavePlan plan = planner.plan(view, source, settings()); assertTrue(plan.accepted()); - assertEquals(RiverCaveAction.DRY_AIR, plan.actions().get(position(0, 12, 0))); + assertEquals(RiverCaveAction.FALLING_WATER, plan.actions().get(position(0, 12, 0))); assertEquals(RiverCaveAction.WET_SOURCE, plan.actions().get(position(0, 8, 0))); assertTrue(plan.actions().containsValue(RiverCaveAction.SEAL_GUARD)); assertEquals(plan.actions().keySet(), plan.baselinePreconditions().keySet()); @@ -349,6 +349,44 @@ public class RiverCaveContainmentPlannerTest { assertMutationConnected(plan, source.entry(), source.target()); } + @Test + public void generatedGrottoAcceptsItsCompleteSurfaceMouth() { + TestVoxelView view = new TestVoxelView(); + CavePosition[] wetMouth = { + position(-1, 12, 0), + position(1, 12, 0), + position(0, 12, -1), + position(0, 12, 1) + }; + CavePosition[] dryMouth = { + position(-1, 13, 0), + position(1, 13, 0), + position(0, 13, -1), + position(0, 13, 1) + }; + view.set(CaveVoxel.COMPATIBLE_FLUID, wetMouth); + view.set(CaveVoxel.CAVE_AIR, dryMouth); + for (CavePosition position : wetMouth) { + view.openToSurface(position); + } + for (CavePosition position : dryMouth) { + view.openToSurface(position); + } + RiverCaveSource source = source(108L, 10, RiverCaveMode.GENERATED_GROTTO, position(0, 8, 0)); + + RiverCavePlan plan = planner.plan(view, source, detailedSettings( + 2, + 2, + RiverCaveGrottoShape.ELLIPSOID, + RiverCaveFluidPolicy.ALLOW_COMPATIBLE + )); + + assertTrue(plan.rejection().toString(), plan.accepted()); + for (CavePosition position : wetMouth) { + assertEquals(RiverCaveAction.FALLING_WATER, plan.actions().get(position)); + } + } + @Test public void generatedGrottoRejectsAnOpenShell() { TestVoxelView view = new TestVoxelView(); diff --git a/core/src/test/java/art/arcane/iris/engine/river/runtime/IrisRiverRuntimeTest.java b/core/src/test/java/art/arcane/iris/engine/river/runtime/IrisRiverRuntimeTest.java index bda60f5cb..f62fc0e92 100644 --- a/core/src/test/java/art/arcane/iris/engine/river/runtime/IrisRiverRuntimeTest.java +++ b/core/src/test/java/art/arcane/iris/engine/river/runtime/IrisRiverRuntimeTest.java @@ -22,7 +22,9 @@ import art.arcane.iris.engine.river.RiverRouteState; import art.arcane.iris.engine.river.RiverAnchor; import art.arcane.iris.engine.river.RiverRoutingContext; import art.arcane.iris.engine.river.RiverTerrainNodeSample; +import art.arcane.iris.engine.river.RiverTopologyComplexity; import art.arcane.iris.engine.river.RiverTerrainSourceSample; +import art.arcane.iris.engine.river.RiverWidthProfile; import art.arcane.iris.util.project.interpolation.NoiseBounds; import art.arcane.iris.util.project.stream.ProceduralStream; import art.arcane.iris.util.project.stream.interpolation.Interpolated; @@ -40,6 +42,82 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; public class IrisRiverRuntimeTest { + @Test + public void tunnelCrossSectionRoundsAndNoiseModulatesBothSurfaces() { + assertEquals(62, IrisRiverRuntime.shapedTunnelBedY(70, 64D, 1D, 2D)); + assertEquals(69, IrisRiverRuntime.shapedTunnelBedY(70, 64D, 0D, 2D)); + assertEquals(65, IrisRiverRuntime.shapedTunnelBedY(70, 64D, 1D, -1D)); + assertEquals(77, IrisRiverRuntime.shapedTunnelCeilingY(70, 4D, 1D, 3D)); + assertEquals(70, IrisRiverRuntime.shapedTunnelCeilingY(70, 4D, 0D, 3D)); + assertEquals(71, IrisRiverRuntime.shapedTunnelCeilingY(70, 4D, 0.5D, -2D)); + } + + @Test + public void widenedTunnelHaloAccountsForMaximumConfiguredMultiplier() { + assertEquals(17, RiverTopologyComplexity.tunnelHalo(7D, 4D, 2D)); + assertEquals(2500L, RiverTopologyComplexity.tunnelSampleColumns(7D, 4D, 2D)); + } + + @Test + public void tunnelWidthMultiplierWidensOnlySubterraneanFootprint() { + IrisRiverNetwork narrowConfiguration = configuration(false); + narrowConfiguration.getTerrain() + .setMaxChannelWidth(4D) + .setMaxBankWidth(0D) + .setBankWidth(range(0D)) + .setMaxIncision(10) + .setTunnelMouthBlend(0D) + .setTunnelWidthMultiplier(range(1D)); + IrisRiverNetwork wideConfiguration = configuration(false); + wideConfiguration.getTerrain() + .setMaxChannelWidth(4D) + .setMaxBankWidth(0D) + .setBankWidth(range(0D)) + .setMaxIncision(10) + .setTunnelMouthBlend(0D) + .setTunnelWidthMultiplier(range(4D)); + + try (IrisRiverRuntime narrow = runtime( + narrowConfiguration, + constantHeight(100D), + constantLandBiome(), + new IrisRegion(), + true, + false + ); IrisRiverRuntime wide = runtime( + wideConfiguration, + constantHeight(100D), + constantLandBiome(), + new IrisRegion(), + true, + false + )) { + int narrowTunnelColumns = 0; + int wideTunnelColumns = 0; + int narrowSurfaceColumns = 0; + int wideSurfaceColumns = 0; + for (int x = -128; x < 128; x += 2) { + for (int z = -128; z < 128; z += 2) { + if (narrow.sampleTunnel(x, z) != null) { + narrowTunnelColumns++; + } + if (wide.sampleTunnel(x, z) != null) { + wideTunnelColumns++; + } + if (narrow.sample(x, z).river().present()) { + narrowSurfaceColumns++; + } + if (wide.sample(x, z).river().present()) { + wideSurfaceColumns++; + } + } + } + + assertTrue(wideTunnelColumns > narrowTunnelColumns * 2); + assertEquals(narrowSurfaceColumns, wideSurfaceColumns); + } + } + @Test public void terminalTaperUsesMeasuredReachLength() { assertEquals(1D, IrisRiverRuntime.terminalWeight(40, 200D, 0.8D), 0D); @@ -185,6 +263,50 @@ public class IrisRiverRuntimeTest { } } + @Test + public void oceanIntentOnlyTerminatesRiversWhereTheNaturalSurfaceIsSubmerged() { + IrisRiverNetwork configuration = configuration(false); + configuration.getTopology().setTerrainHeightWeight(0D); + IrisBiome oceanBiome = new IrisBiome().setInferredType(InferredType.SEA); + IrisRegion region = new IrisRegion(); + ProceduralStream height = ProceduralStream.ofDouble((x, z) -> x < 0D ? 50D : 200D); + ProceduralStream oceanIntent = ProceduralStream.of( + (x, z) -> true, + Interpolated.BOOLEAN + ); + ProceduralStream biomes = ProceduralStream.of( + (x, z) -> oceanBiome, + Interpolated.of(value -> 0D, value -> oceanBiome) + ); + ProceduralStream regions = ProceduralStream.of( + (x, z) -> region, + Interpolated.of(value -> 0D, value -> region) + ); + + try (IrisRiverRuntime runtime = new IrisRiverRuntime(new IrisRiverRuntimeContext( + 4829759234L, + configuration, + mock(IrisData.class), + 63, + true, + true, + false, + false, + false, + (x, z) -> new NoiseBounds(0D, 512D), + height, + ProceduralStream.ofDouble((x, z) -> 0D), + oceanIntent, + biomes, + regions + ))) { + assertTrue(runtime.sampleNode(-1, 0).ocean()); + assertFalse(runtime.sampleNode(1, 0).ocean()); + assertTrue(runtime.sampleSource(-1, 0).ocean()); + assertFalse(runtime.sampleSource(1, 0).ocean()); + } + } + @Test public void wetTerminalRiverCarvesDownAndPublishesAFluidHead() { IrisRiverNetwork configuration = configuration(false); @@ -568,6 +690,42 @@ public class IrisRiverRuntimeTest { } } + @Test + public void cliffTransitionClassifiesOpenAndSolidColumnsIndependently() { + IrisRiverNetwork configuration = configuration(false); + configuration.getTerrain() + .setMaxIncision(10) + .setTunnelMouthBlend(2D); + ProceduralStream height = ProceduralStream.ofDouble((x, z) -> x < 0D ? 50D : 100D); + + try (IrisRiverRuntime runtime = runtime( + configuration, + height, + constantLandBiome(), + new IrisRegion(), + true, + false + )) { + boolean transitionFound = false; + for (int z = -2048; z <= 2048 && !transitionFound; z++) { + IrisRiverSurfaceSample open = runtime.sample(-1, z); + IrisRiverSurfaceSample solid = runtime.sample(0, z); + IrisRiverTunnelSample tunnel = runtime.sampleTunnel(0, z); + if (open.river().present() + && solid.river().present() + && open.river().reachId().equals(solid.river().reachId()) + && open.surfaceFluid() + && tunnel != null) { + assertFalse(open.subterranean()); + assertTrue(solid.subterranean()); + transitionFound = true; + } + } + + assertTrue(transitionFound); + } + } + @Test public void terracedWaterUsesFixedInteriorPoolsAndPreservesNodeHeads() { IrisRiverNetwork configuration = configuration(false); @@ -635,6 +793,7 @@ public class IrisRiverRuntimeTest { 1, 1, 4D, + RiverWidthProfile.constant(4D), 2D, 4D, true, diff --git a/core/src/test/java/art/arcane/iris/spi/IrisLoggingTest.java b/core/src/test/java/art/arcane/iris/spi/IrisLoggingTest.java index 8810cded5..2e0bc1e0f 100644 --- a/core/src/test/java/art/arcane/iris/spi/IrisLoggingTest.java +++ b/core/src/test/java/art/arcane/iris/spi/IrisLoggingTest.java @@ -13,8 +13,10 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Severity is stated by the caller and honoured by every adapter, so the levels {@link IrisLogging} offers @@ -24,6 +26,7 @@ import static org.mockito.Mockito.mock; public class IrisLoggingTest { private final List levels = new ArrayList<>(); private final List messages = new ArrayList<>(); + private IrisPlatform capturingPlatform; private IrisPlatform previousPlatform; @Before @@ -55,6 +58,34 @@ public class IrisLoggingTest { assertEquals("Engine init: world", messages.getFirst()); } + @Test + public void debugFormatsArgumentsAtDebugLevel() { + IrisLogging.debug("chunk=%d,%d", 3, 7); + + assertEquals(List.of(LogLevel.DEBUG), levels); + assertEquals("chunk=3,7", messages.getFirst()); + } + + @Test + public void contextualReportUsesThePlatformReporterWithoutADuplicateLogLine() { + RuntimeException failure = new RuntimeException("broken"); + + IrisLogging.reportError("Generation failed.", failure); + + verify(capturingPlatform).reportError("Generation failed.", failure); + assertTrue(levels.isEmpty()); + } + + @Test + public void defaultContextualReporterLogsTheFailureContext() { + IrisPlatform platform = mock(IrisPlatform.class, CALLS_REAL_METHODS); + + platform.reportError("Generation failed.", null); + + verify(platform).log(LogLevel.ERROR, "Generation failed."); + verify(platform).reportError((Throwable) null); + } + @Test public void warnOnceStatesTheConditionOnceAndDropsRepeatsToDebug() { String key = uniqueKey(); @@ -104,6 +135,7 @@ public class IrisLoggingTest { private void bindCapturingPlatform() { IrisPlatform platform = mock(IrisPlatform.class); + capturingPlatform = platform; doAnswer(invocation -> { levels.add(invocation.getArgument(0, LogLevel.class)); messages.add(invocation.getArgument(1, String.class)); diff --git a/core/src/test/java/art/arcane/iris/util/common/scheduling/JRepeatingTaskLifecycleTest.java b/core/src/test/java/art/arcane/iris/util/common/scheduling/JRepeatingTaskLifecycleTest.java new file mode 100644 index 000000000..db1cf2d3f --- /dev/null +++ b/core/src/test/java/art/arcane/iris/util/common/scheduling/JRepeatingTaskLifecycleTest.java @@ -0,0 +1,43 @@ +package art.arcane.iris.util.common.scheduling; + +import org.junit.After; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class JRepeatingTaskLifecycleTest { + @After + public void clearTrackedTasks() { + trackedTasks().clear(); + } + + @Test + public void pluginCancellationDrainsEveryTrackedRepeaterOnce() { + AtomicInteger cancellations = new AtomicInteger(); + Map tracked = trackedTasks(); + tracked.put(-1, cancellations::incrementAndGet); + tracked.put(-2, cancellations::incrementAndGet); + + J.cancelPluginTasks(); + J.cancelPluginTasks(); + + assertEquals(2, cancellations.get()); + assertTrue(tracked.isEmpty()); + } + + @SuppressWarnings("unchecked") + private static Map trackedTasks() { + try { + Field field = J.class.getDeclaredField("REPEATING_CANCELLERS"); + field.setAccessible(true); + return (Map) field.get(null); + } catch (ReflectiveOperationException failure) { + throw new AssertionError("Unable to inspect tracked Iris repeaters", failure); + } + } +} diff --git a/spi/src/main/java/art/arcane/iris/spi/IrisLogging.java b/spi/src/main/java/art/arcane/iris/spi/IrisLogging.java index 25f7fd817..3b35fd757 100644 --- a/spi/src/main/java/art/arcane/iris/spi/IrisLogging.java +++ b/spi/src/main/java/art/arcane/iris/spi/IrisLogging.java @@ -58,6 +58,10 @@ public final class IrisLogging { emit(LogLevel.DEBUG, message); } + public static void debug(String format, Object... args) { + emit(LogLevel.DEBUG, format(format, args)); + } + /** * Logs at {@link LogLevel#NOTICE}, applying {@link #format(String, Object...)} to the arguments. For the * few lifecycle milestones per boot that have to survive into the server's own log file. @@ -121,19 +125,18 @@ public final class IrisLogging { Throwable cause = error == null ? new IllegalStateException("Unknown Iris failure") : error; String message = context == null || context.isBlank() ? "Unhandled Iris failure." : context; - try { - error(message); - } catch (Throwable inner) { - System.err.println("[Iris] " + message); - inner.printStackTrace(System.err); - } - if (IrisPlatforms.isBound()) { - IrisPlatforms.get().reportError(message, cause); + try { + IrisPlatforms.get().reportError(message, cause); + } catch (Throwable inner) { + System.err.println("[Iris/ERROR] " + clean(message)); + inner.printStackTrace(System.err); + cause.printStackTrace(System.err); + } return; } - reportError(cause); + System.err.println("[Iris/ERROR] " + clean(message)); cause.printStackTrace(System.err); } diff --git a/spi/src/main/java/art/arcane/iris/spi/IrisPlatform.java b/spi/src/main/java/art/arcane/iris/spi/IrisPlatform.java index ab9592001..cef86964d 100644 --- a/spi/src/main/java/art/arcane/iris/spi/IrisPlatform.java +++ b/spi/src/main/java/art/arcane/iris/spi/IrisPlatform.java @@ -203,6 +203,7 @@ public interface IrisPlatform { * stack trace on stderr. */ default void reportError(String context, Throwable error) { + log(LogLevel.ERROR, context == null || context.isBlank() ? "Unhandled Iris failure." : context); reportError(error); if (error != null) { error.printStackTrace(System.err);