Refused runtime replace targets

- Refused /iris replace for dimensions that no level stem defines, including mod levels created while the server starts
- Skipped hand-edited dimensionReplacements entries for such dimensions at startup without moving their data or writing a level stem override
- Derived replaceable dimensions from the recorded worldgen catalog and the applied replacement state
- Recorded a replacement as applied only when the loaded level generates from Iris with the configured pack and seed
- Kept matching Iris generation history without recording it as an applied replacement
- Dropped applied records whose loaded level uses another chunk generator
- Stopped Iris engines bound to replaced levels that a mod swapped out during startup
- Listed installed packs and pack:dimensionKey forms when the replace type is unknown
- Added the refusal and installed-pack messages to every locale
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-15 12:33:47 -04:00
1 parent e1049949c6
commit 31ec5a83c2
27 files changed
+542 -55

No files matched your search

@@ -18,6 +18,10 @@
package art.arcane.iris.modded;
import art.arcane.iris.modded.catalog.ModdedWorldgenCatalogs;
import art.arcane.iris.pack.ingest.catalog.CatalogLevelStem;
import art.arcane.iris.pack.ingest.catalog.WorldgenCatalog;
import art.arcane.iris.pack.ingest.catalog.WorldgenCatalogStore;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.volmlib.util.json.JSONObject;
@@ -27,15 +31,20 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
public final class ModdedDimensionReplacements {
private static final String GENERATOR_TYPE = "irisworldgen:iris";
public static final String IRIS_GENERATOR = "irisworldgen:iris";
private static final Set<String> VANILLA_LEVELS = Set.of("minecraft:overworld", "minecraft:the_nether",
"minecraft:the_end");
private static volatile RecordedCatalog recordedCatalog;
private static final String PLACEHOLDER_BIOME_SOURCE = "minecraft:fixed";
private static final String PLACEHOLDER_BIOME = "minecraft:plains";
private static final String DIMENSIONS = "dimensions";
@@ -49,7 +58,7 @@ public final class ModdedDimensionReplacements {
biomeSource.put("type", PLACEHOLDER_BIOME_SOURCE);
biomeSource.put("biome", PLACEHOLDER_BIOME);
JSONObject generator = new JSONObject();
generator.put("type", GENERATOR_TYPE);
generator.put("type", IRIS_GENERATOR);
generator.put("biome_source", biomeSource);
generator.put("dimension", replacement.packRef());
JSONObject stem = new JSONObject();
@@ -72,8 +81,13 @@ public final class ModdedDimensionReplacements {
}
public static void writeLevelStems(Path datapackRoot, Map<String, ModdedDimensionReplacement> configured,
StagedTypes staged, Path worldRoot) throws IOException {
StagedTypes staged, Path worldRoot, ReplaceableLevels levels) throws IOException {
for (ModdedDimensionReplacement replacement : new TreeMap<>(configured).values()) {
if (!levels.replaceable(replacement.levelKey())) {
ModdedIrisLog.error(unreplaceableReason(replacement.levelKey(), levels)
+ " Iris writes no dimension override for it.");
continue;
}
String typeRef = staged.typeRef(replacement, worldRoot);
if (typeRef == null) {
skipUnstaged(replacement, staged, worldRoot);
@@ -89,6 +103,68 @@ public final class ModdedDimensionReplacements {
return canonical(ModdedModConfig.get().dimensionReplacements(), IrisPlatforms.get().packsFolderNoCreate().toPath());
}
public static Map<String, ModdedDimensionReplacement> stageable(Map<String, ModdedDimensionReplacement> configured,
ReplaceableLevels levels) {
Map<String, ModdedDimensionReplacement> stageable = new TreeMap<>();
for (ModdedDimensionReplacement replacement : configured.values()) {
if (levels.replaceable(replacement.levelKey())) {
stageable.put(replacement.levelKey(), replacement);
}
}
return Map.copyOf(stageable);
}
public static ReplaceableLevels replaceableLevels(Path worldRoot, Map<String, ModdedDimensionReplacement> configured)
throws IOException {
if (configured.isEmpty()) {
return ReplaceableLevels.unknown();
}
return replaceableLevels(worldRoot);
}
public static ReplaceableLevels replaceableLevels(Path worldRoot) throws IOException {
Set<String> applied = worldRoot == null
? Set.of()
: ModdedReplacementReconciler.readState(worldRoot).applied().keySet();
Optional<WorldgenCatalog> catalog = ModdedWorldgenCatalogs.latest();
if (catalog.isEmpty()) {
catalog = recordedCatalog();
}
return catalog.isEmpty()
? ReplaceableLevels.unknown()
: ReplaceableLevels.of(catalog.get().levelStems(), applied);
}
public static String unreplaceableReason(String levelKey, ReplaceableLevels levels) {
if (!levels.known()) {
return "Iris refuses to replace " + levelKey + ": it has no record of which dimensions this server loads"
+ " from level stems yet. Start the server once so Iris can read the dimension registry.";
}
return "Iris refuses to replace " + levelKey + ": it is not a dimension the server loads from a level stem;"
+ " a mod creates it while the server runs, so its data stays untouched.";
}
private static Optional<WorldgenCatalog> recordedCatalog() {
Path file = ModdedWorldgenCatalogs.catalogFile();
if (!Files.isRegularFile(file)) {
return Optional.empty();
}
try {
long modified = Files.getLastModifiedTime(file).toMillis();
RecordedCatalog cached = recordedCatalog;
if (cached != null && cached.file().equals(file) && cached.modified() == modified) {
return Optional.of(cached.catalog());
}
WorldgenCatalog catalog = WorldgenCatalogStore.read(file);
recordedCatalog = new RecordedCatalog(file, modified, catalog);
return Optional.of(catalog);
} catch (IOException | RuntimeException unreadable) {
ModdedIrisLog.error("Iris could not read the recorded worldgen catalog at {}; only the vanilla dimensions are replaceable until the next start records it",
file, unreadable);
return Optional.empty();
}
}
public static OptionalLong loadedSeed(String levelKey) {
ModdedModConfig config = ModdedModConfig.loaded();
if (config == null) {
@@ -188,6 +264,39 @@ public final class ModdedDimensionReplacements {
return canonical.toString();
}
private record RecordedCatalog(Path file, long modified, WorldgenCatalog catalog) {
}
public record ReplaceableLevels(boolean known, Map<String, String> stemGenerators, Set<String> applied) {
public ReplaceableLevels {
stemGenerators = Map.copyOf(stemGenerators);
applied = Set.copyOf(applied);
}
public static ReplaceableLevels unknown() {
return new ReplaceableLevels(false, Map.of(), Set.of());
}
public static ReplaceableLevels of(List<CatalogLevelStem> stems, Set<String> applied) {
Map<String, String> generators = new TreeMap<>();
for (CatalogLevelStem stem : stems) {
generators.put(stem.key(), stem.generatorType());
}
return new ReplaceableLevels(true, generators, applied);
}
public boolean replaceable(String levelKey) {
if (VANILLA_LEVELS.contains(levelKey)) {
return true;
}
String generator = stemGenerators.get(levelKey);
if (generator == null) {
return false;
}
return !IRIS_GENERATOR.equals(generator) || applied.contains(levelKey);
}
}
public record StagedTypes(Map<String, String> installed, Map<Path, String> retained) {
public static StagedTypes create() {
return new StagedTypes(new LinkedHashMap<>(), new LinkedHashMap<>());
@@ -178,6 +178,7 @@ public final class ModdedEngineBootstrap {
reconcileSpawn(server, replacedOverworld);
if (!loader().clientEnvironment()) {
ModdedReplacementReconciler.recordApplied(server);
ModdedReplacementReconciler.stopUnpublishedEngines(server);
ModdedReplacementEntry.retireIfComplete();
}
ModdedWorldCheck.serverStarted(server);
@@ -324,7 +324,9 @@ public final class ModdedForcedDatapack {
false
);
digest.update(inputFingerprint.getBytes(StandardCharsets.UTF_8));
digest.update(ModdedDimensionReplacements.fingerprint(ModdedDimensionReplacements.configured())
Map<String, ModdedDimensionReplacement> replacements = ModdedDimensionReplacements.configured();
digest.update(ModdedDimensionReplacements.fingerprint(ModdedDimensionReplacements.stageable(replacements,
ModdedDimensionReplacements.replaceableLevels(worldRootOrNull(), replacements)))
.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(digest.digest());
}
@@ -346,8 +348,9 @@ public final class ModdedForcedDatapack {
}
}
if (!ModdedEngineBootstrap.loader().clientEnvironment()) {
ModdedDimensionReplacements.writeLevelStems(stagingDirectory,
ModdedDimensionReplacements.configured(), stagedDimensionTypes, worldRoot);
Map<String, ModdedDimensionReplacement> replacements = ModdedDimensionReplacements.configured();
ModdedDimensionReplacements.writeLevelStems(stagingDirectory, replacements, stagedDimensionTypes, worldRoot,
ModdedDimensionReplacements.replaceableLevels(worldRoot, replacements));
}
IrisDatapackCompiler.installRetainedRegistrySources(packRoots, List.of(stagingDirectory.toFile()), fixer);
@@ -38,6 +38,7 @@ import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.Set;
@@ -72,9 +73,9 @@ public final class ModdedReplacementReconciler {
Path packsRoot = IrisPlatforms.get().packsFolderNoCreate().toPath();
Result result = reconcile(new Request(worldRoot, configured, config.replacementProblems(),
(ModdedDimensionReplacement replacement) -> ModdedDimensionReplacements.installed(replacement, packsRoot),
recoveryParent));
recoveryParent, ModdedDimensionReplacements.replaceableLevels(worldRoot, configured)));
if (!result.adoptedLevels().isEmpty()) {
ModdedIrisLog.info("Iris dimension replacement recorded for {}: their existing Iris generation history already uses the configured pack and seed",
ModdedIrisLog.info("Iris kept the existing terrain of {}: its Iris generation history already uses the configured pack and seed; the replacement is recorded once the level generates from it",
result.adoptedLevels());
}
if (!result.replacedWorlds().isEmpty()) {
@@ -101,7 +102,7 @@ public final class ModdedReplacementReconciler {
}
try {
recordApplied(worldRoot.get(), ModdedDimensionReplacements.configured(),
(String levelKey, String packRef) -> generatedSeed(server, levelKey, packRef));
(String levelKey) -> liveLevel(server, levelKey));
} catch (IOException | RuntimeException failure) {
ModdedIrisLog.error("Iris is stopping the server because it could not record the applied dimension replacements under {}; an unrecorded replacement would be moved aside again on the next start",
worldRoot.get(), failure);
@@ -109,15 +110,29 @@ public final class ModdedReplacementReconciler {
}
}
static Map<String, ModdedDimensionReplacement> recordApplied(Path worldRoot,
public static void stopUnpublishedEngines(NativeModdedServer server) {
for (String levelKey : ModdedWorldEngines.evictUnpublished(server,
ModdedModConfig.get().dimensionReplacements().keySet())) {
ModdedIrisLog.error("Iris stopped the engine it started for {}: a mod replaced that level with its own chunk generator while the server started",
levelKey);
}
}
static synchronized Map<String, ModdedDimensionReplacement> recordApplied(Path worldRoot,
Map<String, ModdedDimensionReplacement> configured,
GeneratedSeed generating) throws IOException {
LiveLevels levels) throws IOException {
State state = readState(worldRoot);
Map<String, ModdedDimensionReplacement> recorded = new TreeMap<>(state.applied());
for (ModdedDimensionReplacement replacement : new TreeMap<>(configured).values()) {
OptionalLong seed = generating.seed(replacement.levelKey(), replacement.packRef());
if (seed.isPresent() && (replacement.seed().isEmpty() || replacement.seed().equals(seed))) {
recorded.put(replacement.levelKey(), replacement.withSeed(seed.getAsLong()));
Optional<LiveLevel> live = levels.level(replacement.levelKey());
if (live.isPresent() && live.get().generates(replacement)) {
recorded.put(replacement.levelKey(), replacement.withSeed(live.get().seed()));
continue;
}
if (live.isPresent() && !live.get().iris()) {
recorded.remove(replacement.levelKey());
ModdedIrisLog.error("Iris replacement of {} is configured but the loaded level uses another chunk generator; a mod creates that level while the server runs, so Iris cannot replace it. Remove its dimensionReplacements entry from modded.json",
replacement.levelKey());
continue;
}
ModdedIrisLog.warn("Iris replacement of {} is configured but the level does not generate from {}{}; it stays unapplied",
@@ -130,7 +145,7 @@ public final class ModdedReplacementReconciler {
return Map.copyOf(recorded);
}
static Result reconcile(Request request) throws IOException {
static synchronized Result reconcile(Request request) throws IOException {
Path worldRoot = request.worldRoot();
requireReadableConfig(worldRoot, request.configProblems());
State state = readState(worldRoot);
@@ -149,6 +164,7 @@ public final class ModdedReplacementReconciler {
Map<String, ModdedDimensionReplacement> recorded = new TreeMap<>(applied);
Set<String> quarantined = new TreeSet<>();
Set<String> adopted = new TreeSet<>();
Set<String> unreplaceable = new TreeSet<>();
for (String level : changed) {
ModdedDimensionReplacement replacement = request.configured().get(level);
if (replacement == null) {
@@ -156,12 +172,15 @@ public final class ModdedReplacementReconciler {
quarantined.add(level);
continue;
}
if (!request.levels().replaceable(level)) {
unreplaceable.add(level);
ModdedIrisLog.error(ModdedDimensionReplacements.unreplaceableReason(level, request.levels())
+ " Remove its dimensionReplacements entry from modded.json.");
continue;
}
Path storage = replacement.storageFolder(worldRoot);
OptionalLong historySeed = refreshed.contains(level)
? OptionalLong.empty()
: matchingHistorySeed(storage, replacement);
if (historySeed.isPresent()) {
recorded.put(level, replacement.withSeed(historySeed.getAsLong()));
boolean keepHistory = !refreshed.contains(level) && matchingHistorySeed(storage, replacement).isPresent();
if (keepHistory) {
adopted.add(level);
} else if (request.installed().test(replacement)) {
quarantined.add(level);
@@ -205,7 +224,7 @@ public final class ModdedReplacementReconciler {
writeState(worldRoot, reconciled);
}
return new Result(Set.copyOf(quarantined), Set.copyOf(adopted), Set.copyOf(replacedWorlds.keySet()),
moved.isEmpty() ? null : recovery);
Set.copyOf(unreplaceable), moved.isEmpty() ? null : recovery);
}
public static boolean pending(Path worldRoot, String levelKey, Map<String, ModdedDimensionReplacement> configured)
@@ -320,16 +339,16 @@ public final class ModdedReplacementReconciler {
return packs.isEmpty() || packs.contains(replacement.pack());
}
private static OptionalLong generatedSeed(NativeModdedServer server, String levelKey, String packRef) {
private static Optional<LiveLevel> liveLevel(NativeModdedServer server, String levelKey) {
NativeWorld world = server.world(levelKey);
if (world == null) {
return OptionalLong.empty();
return Optional.empty();
}
IrisModdedChunkGenerator generator = NativeWorldGenerators.find(world, IrisModdedChunkGenerator.class);
if (generator == null || !packRef.equals(generator.dimensionKey())) {
return OptionalLong.empty();
if (generator == null) {
return Optional.of(new LiveLevel(false, "", 0L));
}
return OptionalLong.of(generator.visibleBiomeSeed());
return Optional.of(new LiveLevel(true, generator.dimensionKey(), generator.visibleBiomeSeed()));
}
private static void requireReadableConfig(Path worldRoot, List<String> configProblems) throws IOException {
@@ -395,8 +414,19 @@ public final class ModdedReplacementReconciler {
return section;
}
interface GeneratedSeed {
OptionalLong seed(String levelKey, String packRef);
interface LiveLevels {
Optional<LiveLevel> level(String levelKey);
}
record LiveLevel(boolean iris, String packRef, long seed) {
LiveLevel {
Objects.requireNonNull(packRef, "packRef");
}
boolean generates(ModdedDimensionReplacement replacement) {
return iris && packRef.equals(replacement.packRef())
&& (replacement.seed().isEmpty() || replacement.seed().getAsLong() == seed);
}
}
record State(Map<String, ModdedDimensionReplacement> applied, Map<String, ModdedDimensionReplacement> pending) {
@@ -409,9 +439,11 @@ public final class ModdedReplacementReconciler {
}
record Request(Path worldRoot, Map<String, ModdedDimensionReplacement> configured, List<String> configProblems,
Predicate<ModdedDimensionReplacement> installed, Path recoveryParent) {
Predicate<ModdedDimensionReplacement> installed, Path recoveryParent,
ModdedDimensionReplacements.ReplaceableLevels levels) {
}
record Result(Set<String> quarantinedLevels, Set<String> adoptedLevels, Set<String> replacedWorlds, Path recovery) {
record Result(Set<String> quarantinedLevels, Set<String> adoptedLevels, Set<String> replacedWorlds,
Set<String> unreplaceableLevels, Path recovery) {
}
}
@@ -20,6 +20,7 @@ package art.arcane.iris.modded;
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeDimensionRuntime;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldDimensions;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldGenerators;
@@ -49,8 +50,10 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public final class ModdedWorldEngines {
@@ -92,6 +95,19 @@ public final class ModdedWorldEngines {
}
}
static List<String> evictUnpublished(NativeModdedServer server, Set<String> levelKeys) {
List<String> evicted = new ArrayList<>();
for (NativeWorld level : new ArrayList<>(ENGINES.keySet())) {
if (!levelKeys.contains(level.name())
|| NativeDimensionRuntime.sameWorld(server.world(level.name()), level)) {
continue;
}
evict(level);
evicted.add(level.name());
}
return evicted;
}
static void evictOrThrow(NativeWorld level) {
Engine[] removed = new Engine[1];
ENGINES.computeIfPresent(level, (NativeWorld ignored, Engine current) -> {
@@ -30,7 +30,11 @@ import art.arcane.iris.modded.ModdedForcedDatapack;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.modded.ModdedModConfig;
import art.arcane.iris.modded.ModdedReplacementReconciler;
import art.arcane.iris.modded.catalog.ModdedWorldgenCatalogs;
import art.arcane.iris.modded.localization.ModdedCommandMessages;
import art.arcane.iris.pack.PackDirectoryResolver;
import art.arcane.iris.pack.PackDownloader;
import art.arcane.iris.pack.ingest.catalog.WorldgenCatalog;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeCommandRegistration;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeCommandSource;
@@ -46,10 +50,15 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Stream;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
@@ -135,6 +144,7 @@ final class ModdedReplaceCommands {
throw new IllegalArgumentException(MISSING_TARGET);
}
Path worldRoot = server.root();
requireReplaceableLevel(worldRoot, requested.levelKey());
requireNothingPending(worldRoot, requested.levelKey());
long seed = requested.seed().orElse(ModdedReplacementReconciler.applied(worldRoot, requested.levelKey())
.map((ModdedDimensionReplacement applied) -> applied.seed().getAsLong())
@@ -166,6 +176,26 @@ final class ModdedReplaceCommands {
return staged;
}
private static void requireReplaceableLevel(Path worldRoot, String levelKey) throws IOException {
Optional<WorldgenCatalog> catalog = ModdedWorldgenCatalogs.latest();
Set<String> applied = ModdedReplacementReconciler.applied(worldRoot, levelKey).isPresent()
? Set.of(levelKey)
: Set.of();
ModdedDimensionReplacements.ReplaceableLevels levels = catalog.isEmpty()
? ModdedDimensionReplacements.ReplaceableLevels.unknown()
: ModdedDimensionReplacements.ReplaceableLevels.of(catalog.get().levelStems(), applied);
if (levels.replaceable(levelKey)) {
return;
}
if (!levels.known()) {
throw new IllegalArgumentException(IrisLanguage.plain(
ModdedCommandMessages.MODDED_WORLD_COMMANDS_REPLACE_REGISTRY_NOT_READY));
}
throw new IllegalArgumentException(IrisLanguage.plain(
ModdedCommandMessages.MODDED_WORLD_COMMANDS_REPLACE_RUNTIME_LEVEL,
MessageArgument.untrusted("dimensionId", levelKey)));
}
private static void requireNothingPending(Path worldRoot, String levelKey) throws IOException {
Map<String, ModdedDimensionReplacement> configured = ModdedModConfig.get().dimensionReplacements();
if (ModdedReplacementReconciler.pending(worldRoot, levelKey, configured)) {
@@ -230,13 +260,52 @@ final class ModdedReplaceCommands {
IrisModdedCommands.fail(source, IrisLanguage.plain(
BukkitCommandMessagesExtended.COMMAND_IRIS_DIMENSION_NOT_FOUND,
MessageArgument.untrusted("dimension", resolvedType)));
IrisModdedCommands.fail(source, IrisLanguage.plain(
BukkitCommandMessagesExtended.COMMAND_IRIS_TRY_ONE_OVERWORLD_VANILLA_FLAT_THEEND));
List<String> installed = installedTypes(ModdedPackCommands.packsRoot());
if (!installed.isEmpty()) {
IrisModdedCommands.fail(source, IrisLanguage.plain(
ModdedCommandMessages.MODDED_WORLD_COMMANDS_REPLACE_INSTALLED_TYPES,
MessageArgument.untrusted("value", String.join(", ", installed))));
}
IrisModdedCommands.fail(source, IrisLanguage.plain(
BukkitCommandMessagesExtended.COMMAND_IRIS_INSTALL_PACK_AND_RESTART,
MessageArgument.untrusted("command", PackDownloader.downloadCommandFor(packOf(resolvedType)))));
}
static List<String> installedTypes(File packsRoot) {
Set<String> types = new TreeSet<>();
for (File pack : PackDirectoryResolver.listVisiblePackDirectories(packsRoot)) {
List<String> dimensionKeys = dimensionKeys(pack.toPath().resolve("dimensions"));
if (dimensionKeys.isEmpty()) {
continue;
}
types.add(pack.getName());
for (String dimensionKey : dimensionKeys) {
if (!dimensionKey.equals(pack.getName())) {
types.add(pack.getName() + ":" + dimensionKey);
}
}
}
return List.copyOf(types);
}
private static List<String> dimensionKeys(Path dimensions) {
if (!Files.isDirectory(dimensions)) {
return List.of();
}
List<String> keys = new ArrayList<>();
try (Stream<Path> files = Files.walk(dimensions)) {
for (Path file : files.filter(Files::isRegularFile).toList()) {
String relative = dimensions.relativize(file).toString().replace(File.separatorChar, '/');
if (relative.endsWith(".json")) {
keys.add(relative.substring(0, relative.length() - ".json".length()));
}
}
} catch (IOException | RuntimeException unreadable) {
ModdedIrisLog.warn("Iris could not list the dimensions of pack folder {}", dimensions.getParent(), unreadable);
}
return keys;
}
private static String packOf(String type) {
String value = type.trim();
int colon = value.indexOf(':');
@@ -1260,6 +1260,18 @@ public final class ModdedCommandMessages {
"iris.modded.moddedworldcommands.use_iris_world_create_name_pack_inject_one_without_restarting",
"Use /iris world create <name> <pack> to inject one without restarting."
);
public static final TextKey MODDED_WORLD_COMMANDS_REPLACE_RUNTIME_LEVEL = TextKey.of(
"iris.modded.moddedworldcommands.replace_runtime_level",
"{dimensionId}" + " is created by a mod while the server runs and is not defined as a dimension, so Iris cannot replace it."
);
public static final TextKey MODDED_WORLD_COMMANDS_REPLACE_REGISTRY_NOT_READY = TextKey.of(
"iris.modded.moddedworldcommands.replace_registry_not_ready",
"Iris is still reading this server's dimensions. Try again in a moment."
);
public static final TextKey MODDED_WORLD_COMMANDS_REPLACE_INSTALLED_TYPES = TextKey.of(
"iris.modded.moddedworldcommands.replace_installed_types",
"Installed packs: " + "{value}"
);
private static final List<MessageKey> KEYS = List.of(
IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS,
@@ -1574,7 +1586,10 @@ public final class ModdedCommandMessages {
MODDED_WORLD_COMMANDS_NO_IRIS_DIMENSIONS_ARE_CURRENTLY_LOADED_CREATE_ONE_WITH_IRIS,
MODDED_WORLD_COMMANDS_LOADED_IRIS_DIMENSIONS,
MODDED_WORLD_COMMANDS_MESSAGE_2,
MODDED_WORLD_COMMANDS_USE_IRIS_WORLD_CREATE_NAME_PACK_INJECT_ONE_WITHOUT_RESTARTING
MODDED_WORLD_COMMANDS_USE_IRIS_WORLD_CREATE_NAME_PACK_INJECT_ONE_WITHOUT_RESTARTING,
MODDED_WORLD_COMMANDS_REPLACE_RUNTIME_LEVEL,
MODDED_WORLD_COMMANDS_REPLACE_REGISTRY_NOT_READY,
MODDED_WORLD_COMMANDS_REPLACE_INSTALLED_TYPES
);
private ModdedCommandMessages() {