This commit is contained in:
Brian Neumann-Fopiano
2026-07-22 14:56:50 -05:00
parent f6f06c1ab9
commit 9e1e202f15
135 changed files with 34492 additions and 1795 deletions
@@ -152,9 +152,19 @@ public final class ModdedDimensionManager {
}
public static Handle createPersistent(MinecraftServer server, String dimensionId, String pack, String packDimensionKey, long seed) {
Handle handle = create(server, dimensionId, pack, packDimensionKey, seed);
ModdedDimensionRegistryStore.put(server, new ModdedDimensionRegistryStore.PersistentDimension(dimensionId, pack, packDimensionKey, seed));
return handle;
try {
return create(server, dimensionId, pack, packDimensionKey, seed);
} catch (Throwable e) {
ModdedDimensionRegistryStore.remove(server, dimensionId);
if (e instanceof RuntimeException runtimeException) {
throw runtimeException;
}
if (e instanceof Error fatalError) {
throw fatalError;
}
throw new IllegalStateException("Iris runtime dimension injection failed for " + dimensionId, e);
}
}
public static boolean removePersistent(MinecraftServer server, String dimensionId, boolean wipeStorage) {
@@ -265,9 +275,10 @@ public final class ModdedDimensionManager {
IrisDimension dimension = loadPackDimension(pack, packDimensionKey);
String typeRef = ModdedForcedDatapack.dimensionTypeRef(dimension);
ResourceKey<DimensionType> typeKey = ResourceKey.create(Registries.DIMENSION_TYPE, Identifier.parse(typeRef));
Holder.Reference<DimensionType> packType = ModdedForcedDatapack.requireRegisteredDimensionType(
ModdedRuntimeRegistry.ensureCustomBiomes(registryAccess, dimension, pack);
ModdedRuntimeRegistry.ensureDimensionType(registryAccess, registry, typeKey, typeRef, dimension);
return ModdedForcedDatapack.requireRegisteredDimensionType(
typeRef, registry.get(typeKey), pack, packDimensionKey);
return packType;
}
private static IrisDimension loadPackDimension(String pack, String packDimensionKey) {
@@ -19,6 +19,7 @@
package art.arcane.iris.modded;
import art.arcane.iris.core.gui.GuiHost;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.engine.decorator.DecoratorPlatformHooks;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.EngineEffectsProvider;
@@ -260,6 +261,7 @@ public final class ModdedEngineBootstrap {
ModdedIrisLog.info("Iris " + moddedLoader.modVersion() + " bootstrapping on Minecraft " + moddedLoader.minecraftVersion() + " (" + loaderDescription + ")");
selfTest(moddedLoader.getClass().getClassLoader());
bind();
IrisLanguage.initialize();
MainWorldService.reconcileEarly();
chunkGeneratorRegistration.run();
ModdedIrisLog.info("Iris chunk generator registered as irisworldgen:iris");
@@ -19,6 +19,8 @@
package art.arcane.iris.modded;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.iris.core.nms.datapack.DataVersion;
import art.arcane.iris.core.nms.datapack.IDataFixer;
import art.arcane.iris.engine.object.IrisDimension;
@@ -85,7 +87,7 @@ public final class ModdedForcedDatapack {
private static Pack requireReadablePack(Path directory) {
PackLocationInfo location = new PackLocationInfo(
PACK_ID,
Component.literal("Iris World Generation"),
Component.literal(IrisLanguage.plain(RuntimeUiMessages.FORCED_DATAPACK_NAME)),
PackSource.BUILT_IN,
Optional.empty());
PackSelectionConfig selection = new PackSelectionConfig(true, Pack.Position.TOP, true);
@@ -18,7 +18,10 @@
package art.arcane.iris.modded;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.PackDownloadMessages;
import art.arcane.iris.core.pack.PackDownloader;
import art.arcane.volmlib.util.localization.MessageArgument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -38,11 +41,11 @@ public final class ModdedPackInstaller {
public static boolean install(Path configDir, String pack, String branch, Consumer<String> feedback) {
if (pack == null || !PACK_NAME.matcher(pack).matches()) {
feedback.accept("Invalid pack name '" + pack + "' (allowed: a-z, 0-9, _ and -)");
feedback.accept(IrisLanguage.plain(PackDownloadMessages.INVALID_PACK_NAME, MessageArgument.untrusted("pack", String.valueOf(pack))));
return false;
}
if (branch == null || !BRANCH_NAME.matcher(branch).matches()) {
feedback.accept("Invalid branch name '" + branch + "' (allowed: letters, digits, . _ and -)");
feedback.accept(IrisLanguage.plain(PackDownloadMessages.INVALID_BRANCH_NAME, MessageArgument.untrusted("branch", String.valueOf(branch))));
return false;
}
@@ -54,7 +57,11 @@ public final class ModdedPackInstaller {
return PackDownloader.download(packs, "IrisDimensions/" + pack, branch, true, false, feedback) != null;
} catch (IOException error) {
LOGGER.error("Iris pack download failed for IrisDimensions/{} ({})", pack, branch, error);
feedback.accept("Pack download failed: " + error.getClass().getSimpleName() + (error.getMessage() == null ? "" : " - " + error.getMessage()));
feedback.accept(IrisLanguage.plain(
PackDownloadMessages.DOWNLOAD_FAILED,
MessageArgument.untrusted("type", error.getClass().getSimpleName()),
MessageArgument.trusted("errorMessage", IrisLanguage.errorDetail(error))
));
return false;
}
}
@@ -0,0 +1,136 @@
/*
* Iris is a World Generator for Minecraft Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.modded;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.nms.datapack.DataVersion;
import art.arcane.iris.core.nms.datapack.IDataFixer;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisBiomeCustom;
import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.util.common.data.DataProvider;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.mojang.serialization.Codec;
import com.mojang.serialization.JsonOps;
import net.minecraft.core.Holder;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.RegistrationInfo;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.RegistryOps;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.dimension.DimensionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.HashSet;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
public final class ModdedRuntimeRegistry {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Object LOCK = new Object();
private ModdedRuntimeRegistry() {
}
static void ensureDimensionType(RegistryAccess registryAccess, Registry<DimensionType> registry,
ResourceKey<DimensionType> typeKey, String typeRef, IrisDimension dimension) {
if (registry.get(typeKey).isPresent()) {
return;
}
IDataFixer fixer = DataVersion.getLatest().get();
String json = dimension.getDimensionType().toJson(fixer);
DimensionType type = decode(registryAccess, DimensionType.DIRECT_CODEC, json, typeRef);
registerIntoFrozen(registry, typeKey, type, typeRef);
LOGGER.info("Iris registered runtime dimension type '{}'", typeRef);
}
static void ensureCustomBiomes(RegistryAccess registryAccess, IrisDimension dimension, String pack) {
File packFolder = ModdedWorldEngines.packFolder(pack);
if (!packFolder.isDirectory()) {
return;
}
Registry<Biome> registry = registryAccess.lookupOrThrow(Registries.BIOME);
IrisData data = IrisData.get(packFolder);
DataProvider provider = () -> data;
IDataFixer fixer = DataVersion.getLatest().get();
String namespace = dimension.getLoadKey().toLowerCase(Locale.ROOT);
Set<String> seen = new HashSet<>();
int registered = 0;
for (IrisBiome irisBiome : dimension.getAllBiomes(provider)) {
if (!irisBiome.isCustom()) {
continue;
}
for (IrisBiomeCustom customBiome : irisBiome.getCustomDerivitives()) {
String biomeId = customBiome.getId();
if (!seen.add(biomeId)) {
continue;
}
String biomeRef = namespace + ":" + biomeId;
ResourceKey<Biome> biomeKey = ResourceKey.create(Registries.BIOME, Identifier.parse(biomeRef));
if (registry.get(biomeKey).isPresent()) {
continue;
}
String json = customBiome.generateJson(fixer);
Biome biome = decode(registryAccess, Biome.DIRECT_CODEC, json, biomeRef);
registerIntoFrozen(registry, biomeKey, biome, biomeRef);
registered++;
}
}
if (registered > 0) {
LOGGER.info("Iris registered {} runtime biome(s) for pack '{}'", registered, pack);
}
}
private static <T> T decode(RegistryAccess registryAccess, Codec<T> codec, String json, String ref) {
JsonElement element = JsonParser.parseString(json);
RegistryOps<JsonElement> ops = RegistryOps.create(JsonOps.INSTANCE, registryAccess);
return codec.parse(ops, element).getOrThrow((String message) ->
new IllegalStateException("Iris could not decode runtime registry entry '" + ref + "': " + message));
}
private static <T> Holder.Reference<T> registerIntoFrozen(Registry<T> registry, ResourceKey<T> key, T value, String ref) {
if (!(registry instanceof MappedRegistry<T> mapped)) {
throw new IllegalStateException("Iris cannot register '" + ref + "' at runtime: "
+ registry.getClass().getName() + " is not a MappedRegistry");
}
synchronized (LOCK) {
Optional<Holder.Reference<T>> raced = registry.get(key);
if (raced.isPresent()) {
return raced.get();
}
boolean wasFrozen = mapped.frozen;
mapped.frozen = false;
try {
return mapped.register(key, value, RegistrationInfo.BUILT_IN);
} finally {
if (wasFrozen) {
mapped.freeze();
}
}
}
}
}
@@ -19,6 +19,7 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.localization.IrisMessages;
import art.arcane.iris.core.gui.GuiHost;
import art.arcane.iris.core.loader.IrisRegistrant;
import art.arcane.iris.core.pack.PackDownloader;
@@ -106,6 +107,10 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.function.Predicate;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
public final class IrisModdedCommands {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
@@ -386,27 +391,27 @@ public final class IrisModdedCommands {
private static int editBiome(CommandSourceStack source, String key) {
Engine engine = engineFor(source.getLevel());
if (engine == null) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS));
return 0;
}
IrisBiome biome;
if (key == null || key.isBlank()) {
ServerPlayer player = source.getPlayer();
if (player == null) {
fail(source, "Console must name a biome: /iris edit biome <key>");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_CONSOLE_MUST_NAME_BIOME_IRIS_EDIT_BIOME_KEY));
return 0;
}
BlockPos pos = player.blockPosition();
try {
biome = engine.getBiome(pos.getX(), pos.getY() - engine.getMinHeight(), pos.getZ());
} catch (Throwable e) {
fail(source, "Biome lookup failed: " + e.getClass().getSimpleName());
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_BIOME_LOOKUP_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName())));
return 0;
}
} else {
biome = engine.getData().getBiomeLoader().load(key.trim());
if (biome == null) {
fail(source, "Unknown biome: " + key);
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_UNKNOWN_BIOME, MessageArgument.untrusted("key", key)));
return 0;
}
}
@@ -416,27 +421,27 @@ public final class IrisModdedCommands {
private static int editRegion(CommandSourceStack source, String key) {
Engine engine = engineFor(source.getLevel());
if (engine == null) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_2));
return 0;
}
IrisRegion region;
if (key == null || key.isBlank()) {
ServerPlayer player = source.getPlayer();
if (player == null) {
fail(source, "Console must name a region: /iris edit region <key>");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_CONSOLE_MUST_NAME_REGION_IRIS_EDIT_REGION_KEY));
return 0;
}
BlockPos pos = player.blockPosition();
try {
region = engine.getRegion(pos.getX(), pos.getZ());
} catch (Throwable e) {
fail(source, "Region lookup failed: " + e.getClass().getSimpleName());
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_REGION_LOOKUP_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName())));
return 0;
}
} else {
region = engine.getData().getRegionLoader().load(key.trim());
if (region == null) {
fail(source, "Unknown region: " + key);
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_UNKNOWN_REGION, MessageArgument.untrusted("key", key)));
return 0;
}
}
@@ -446,7 +451,7 @@ public final class IrisModdedCommands {
private static int editDimension(CommandSourceStack source) {
Engine engine = engineFor(source.getLevel());
if (engine == null) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_3));
return 0;
}
return openJson(source, engine.getDimension());
@@ -454,11 +459,11 @@ public final class IrisModdedCommands {
private static int openJson(CommandSourceStack source, IrisRegistrant registrant) {
if (!GuiHost.isAvailable() || !Desktop.isDesktopSupported()) {
fail(source, "Cannot open files here: " + ModdedGuiHost.guiUnavailableReason());
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_CANNOT_OPEN_FILES_HERE, MessageArgument.untrusted("value", ModdedGuiHost.guiUnavailableReason())));
return 0;
}
if (registrant == null || registrant.getLoadFile() == null || !registrant.getLoadFile().isFile()) {
fail(source, "Cannot find the file; perhaps it was not loaded directly from a file?");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_CANNOT_FIND_FILE_PERHAPS_IT_WAS_NOT_LOADED_DIRECTLY_FROM));
return 0;
}
File file = registrant.getLoadFile();
@@ -466,29 +471,29 @@ public final class IrisModdedCommands {
Desktop.getDesktop().open(file);
} catch (Throwable e) {
LOGGER.error("Iris edit failed to open {}", file, e);
fail(source, "Could not open " + file.getName() + ": " + e.getClass().getSimpleName());
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_COULD_NOT_OPEN, MessageArgument.untrusted("value", file.getName()), MessageArgument.untrusted("value2", e.getClass().getSimpleName())));
return 0;
}
ok(source, "Opening " + registrant.getTypeName() + " " + file.getName() + " in your editor.");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_OPENING_YOUR_EDITOR, MessageArgument.untrusted("value", registrant.getTypeName()), MessageArgument.untrusted("value2", file.getName())));
return 1;
}
private static int tp(CommandSourceStack source, ServerLevel level, ServerPlayer target) {
ServerPlayer player = target != null ? target : source.getPlayer();
if (player == null) {
fail(source, "Console must name a player: /iris tp <dimension> <player>");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_CONSOLE_MUST_NAME_PLAYER_IRIS_TP_DIMENSION_PLAYER));
return 0;
}
if (!(level.getChunkSource().getGenerator() instanceof IrisModdedChunkGenerator)) {
fail(source, level.dimension().identifier() + " is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_IS_NOT_GENERATED_BY_IRIS, MessageArgument.untrusted("value", level.dimension().identifier())));
return 0;
}
String dimensionId = level.dimension().identifier().toString();
if (!ModdedDimensionManager.teleport(player, source.getServer(), dimensionId, 8.5D, Double.MIN_VALUE, 8.5D)) {
fail(source, "Teleport failed: dimension " + dimensionId + " is not loaded.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_TELEPORT_FAILED_DIMENSION_IS_NOT_LOADED, MessageArgument.untrusted("dimensionId", dimensionId)));
return 0;
}
ok(source, "Teleporting " + player.getScoreboardName() + " to " + dimensionId + "...");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_TELEPORTING, MessageArgument.untrusted("value", player.getScoreboardName()), MessageArgument.untrusted("dimensionId", dimensionId)));
return 1;
}
@@ -496,16 +501,16 @@ public final class IrisModdedCommands {
MinecraftServer server = source.getServer();
ServerLevel level = target != null ? target : source.getLevel();
if (!(level.getChunkSource().getGenerator() instanceof IrisModdedChunkGenerator)) {
fail(source, level.dimension().identifier() + " is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_IS_NOT_GENERATED_BY_IRIS_2, MessageArgument.untrusted("value", level.dimension().identifier())));
return 0;
}
ServerLevel fallback = server.overworld();
if (fallback == level) {
fail(source, "Cannot evacuate the primary world; there is nowhere to send players.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_CANNOT_EVACUATE_PRIMARY_WORLD_THERE_IS_NOWHERE_SEND_PLAYERS));
return 0;
}
int count = ModdedDimensionManager.evacuate(server, level);
ok(source, "Evacuated " + count + " player(s) from " + level.dimension().identifier() + " to " + fallback.dimension().identifier() + ".");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_EVACUATED_PLAYER_S_FROM, MessageArgument.untrusted("count", count), MessageArgument.untrusted("value", level.dimension().identifier()), MessageArgument.untrusted("value2", fallback.dimension().identifier())));
return 1;
}
@@ -513,7 +518,7 @@ public final class IrisModdedCommands {
boolean to = !IrisSettings.get().getGeneral().isDebug();
IrisSettings.get().getGeneral().setDebug(to);
IrisSettings.get().forceSave();
ok(source, "Set debug to: " + to);
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SET_DEBUG, MessageArgument.untrusted("to", to)));
return 1;
}
@@ -522,39 +527,51 @@ public final class IrisModdedCommands {
IrisSettings.invalidate();
}
IrisSettings.get();
ok(source, "Hotloaded settings");
boolean localeLoaded = IrisLanguage.reload();
if (localeLoaded) {
ok(source, IrisLanguage.plain(
IrisMessages.COMMAND_RELOAD_SUCCESS,
MessageArgument.trusted("locale", IrisLanguage.activeLocale())
));
return 1;
}
fail(source, IrisLanguage.plain(
IrisMessages.COMMAND_RELOAD_FAILED,
MessageArgument.untrusted("locale", IrisSettings.get().getGeneral().getLanguage()),
MessageArgument.trusted("activeLocale", IrisLanguage.activeLocale())
));
return 1;
}
private static int whatHand(CommandSourceStack source) {
ServerPlayer player = source.getPlayer();
if (player == null) {
fail(source, "This command can only be used by players (it inspects your held item).");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_IT_INSPECTS));
return 0;
}
ItemStack stack = player.getMainHandItem();
if (stack.isEmpty()) {
fail(source, "Your main hand is empty.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_YOUR_MAIN_HAND_IS_EMPTY));
return 0;
}
ok(source, "Hand: " + BuiltInRegistries.ITEM.getKey(stack.getItem()) + " x" + stack.getCount());
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_HAND_X, MessageArgument.untrusted("value", BuiltInRegistries.ITEM.getKey(stack.getItem())), MessageArgument.untrusted("value2", stack.getCount())));
return 1;
}
private static int regen(CommandSourceStack source, int radius) {
ServerPlayer player = source.getPlayer();
if (player == null) {
fail(source, "This command can only be used by players.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS));
return 0;
}
ServerLevel level = source.getLevel();
if (!(level.getChunkSource().getGenerator() instanceof IrisModdedChunkGenerator irisGenerator)) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_4));
return 0;
}
Engine engine = engineFor(level);
if (engine == null) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_5));
return 0;
}
ModdedRegen.start(source, level, irisGenerator, engine, player, radius);
@@ -570,15 +587,15 @@ public final class IrisModdedCommands {
Engine engine = engineFor(level);
if (engine == null) {
if (withDimension) {
fail(source, level.dimension().identifier() + " is not generated by Iris; see /iris info for loaded Iris dimensions.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_IS_NOT_GENERATED_BY_IRIS_SEE_IRIS_INFO_LOADED_IRIS, MessageArgument.untrusted("value", level.dimension().identifier())));
} else {
fail(source, "The current dimension (" + level.dimension().identifier() + ") is not generated by Iris. Name one explicitly: /iris pregen start " + radius + " <dimension>; see /iris info for loaded Iris dimensions.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_CURRENT_DIMENSION_IS_NOT_GENERATED_BY_IRIS_NAME_ONE_EXPLICITLY, MessageArgument.untrusted("value", level.dimension().identifier()), MessageArgument.untrusted("radius", radius)));
}
return 0;
}
boolean showGui = gui && ModdedGuiHost.isGuiLaunchable();
if (!ModdedPregenJob.start(source.getServer(), level, engine, radius, centerX, centerZ, showGui, sync, !nocache)) {
fail(source, "A pregeneration task is already running. Stop it first with /iris pregen stop.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PREGENERATION_TASK_IS_ALREADY_RUNNING_STOP_IT_FIRST_WITH_IRIS));
return 0;
}
ModdedPregenBossBar.begin(source.getPlayer());
@@ -591,35 +608,34 @@ public final class IrisModdedCommands {
guiNote = " (GUI requested but unavailable: " + ModdedGuiHost.guiUnavailableReason() + ")";
}
String modeNote = " Mode: " + (sync ? "sync" : "async") + (nocache ? ", cache disabled." : ", resumable (checkpoint cache).");
ok(source, "Pregen started in " + level.dimension().identifier() + " of " + (radius * 2) + " by " + (radius * 2)
+ " blocks from " + centerX + "," + centerZ + "." + modeNote + " Progress logs to console; see /iris pregen status." + guiNote);
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PREGEN_STARTED_BY_BLOCKS_FROM_PROGRESS_LOGS_CONSOLE_SEE_IRIS, MessageArgument.untrusted("value", level.dimension().identifier()), MessageArgument.untrusted("value2", (radius * 2)), MessageArgument.untrusted("value3", (radius * 2)), MessageArgument.untrusted("centerX", centerX), MessageArgument.untrusted("centerZ", centerZ), MessageArgument.untrusted("modeNote", modeNote), MessageArgument.untrusted("guiNote", guiNote)));
return 1;
}
private static int pregenStop(CommandSourceStack source) {
if (ModdedPregenJob.stop()) {
ModdedPregenBossBar.clear();
ok(source, "Stopping pregeneration; finishing up the current region...");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_STOPPING_PREGENERATION_FINISHING_UP_CURRENT_REGION));
return 1;
}
fail(source, "No active pregeneration task to stop.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_NO_ACTIVE_PREGENERATION_TASK_STOP));
return 0;
}
private static int pregenPause(CommandSourceStack source) {
Boolean paused = ModdedPregenJob.pauseResume();
if (paused == null) {
fail(source, "No active pregeneration task to pause/resume.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_NO_ACTIVE_PREGENERATION_TASK_PAUSE_RESUME));
return 0;
}
ok(source, "Pregeneration is now " + (paused.booleanValue() ? "paused" : "running") + ".");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PREGENERATION_IS_NOW, MessageArgument.trusted("value", IrisLanguage.plain(paused.booleanValue() ? RuntimeUiMessages.STATUS_PAUSED_LOWER : RuntimeUiMessages.STATUS_RUNNING_LOWER))));
return 1;
}
private static int pregenStatus(CommandSourceStack source) {
Component status = ModdedPregenJob.statusComponent();
if (status == null) {
fail(source, "No active pregeneration task.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_NO_ACTIVE_PREGENERATION_TASK));
return 0;
}
ok(source, status);
@@ -629,8 +645,7 @@ public final class IrisModdedCommands {
private static int version(CommandSourceStack source) {
ModdedLoader loader = ModdedEngineBootstrap.loader();
int engines = engineCount(source.getServer());
ok(source, "Iris " + loader.modVersion() + " by Volmit Software on " + loader.platformName()
+ " (Minecraft " + loader.minecraftVersion() + "), " + engines + " Iris dimension(s)");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_IRIS_BY_VOLMIT_SOFTWARE_ON_MINECRAFT_IRIS_DIMENSION_S, MessageArgument.untrusted("value", loader.modVersion()), MessageArgument.untrusted("value2", loader.platformName()), MessageArgument.untrusted("value3", loader.minecraftVersion()), MessageArgument.untrusted("engines", engines)));
return 1;
}
@@ -661,9 +676,13 @@ public final class IrisModdedCommands {
+ " generated=" + engine.getGenerated()
+ " data=" + engine.getData().getDataFolder().getAbsolutePath());
}
ok(source, "Loaded dimensions: " + total + " (" + iris + " Iris)");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_LOADED_DIMENSIONS_IRIS, MessageArgument.untrusted("total", total), MessageArgument.untrusted("iris", iris)));
if (lines.isEmpty()) {
ok(source, filter == null ? "No Iris dimensions are loaded." : "No Iris dimension matches '" + filter + "'.");
if (filter == null) {
ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_NO_IRIS_DIMENSIONS_ARE_LOADED));
} else {
ok(source, IrisLanguage.plain(RuntimeUiMessages.MODDED_NO_DIMENSION_MATCH, MessageArgument.untrusted("filter", filter)));
}
return 0;
}
for (String line : lines) {
@@ -675,58 +694,58 @@ public final class IrisModdedCommands {
private static int what(CommandSourceStack source) {
ServerPlayer player = source.getPlayer();
if (player == null) {
fail(source, "This command can only be used by players.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_2));
return 0;
}
ServerLevel level = source.getLevel();
Engine engine = engineFor(level);
if (engine == null) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_6));
return 0;
}
BlockPos pos = player.blockPosition();
int relativeY = pos.getY() - engine.getMinHeight();
try {
IrisBiome biome = engine.getBiome(pos.getX(), relativeY, pos.getZ());
ok(source, "Biome: " + biome.getLoadKey() + " (" + biome.getName() + ")");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_BIOME, MessageArgument.untrusted("value", biome.getLoadKey()), MessageArgument.untrusted("value2", biome.getName())));
} catch (Throwable e) {
fail(source, "Biome lookup failed: " + e.getClass().getSimpleName());
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_BIOME_LOOKUP_FAILED_2, MessageArgument.untrusted("value", e.getClass().getSimpleName())));
}
try {
IrisRegion region = engine.getRegion(pos.getX(), pos.getZ());
ok(source, "Region: " + region.getLoadKey() + " (" + region.getName() + ")");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_REGION, MessageArgument.untrusted("value", region.getLoadKey()), MessageArgument.untrusted("value2", region.getName())));
} catch (Throwable e) {
fail(source, "Region lookup failed: " + e.getClass().getSimpleName());
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_REGION_LOOKUP_FAILED_2, MessageArgument.untrusted("value", e.getClass().getSimpleName())));
}
try {
IrisBiome cave = engine.getCaveBiome(pos.getX(), relativeY, pos.getZ());
ok(source, "Cave biome: " + (cave == null ? "none" : cave.getLoadKey()));
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_CAVE_BIOME, MessageArgument.untrusted("value", cave == null ? IrisLanguage.plain(RuntimeUiMessages.STATUS_NONE) : cave.getLoadKey())));
} catch (Throwable e) {
fail(source, "Cave biome lookup failed: " + e.getClass().getSimpleName());
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_CAVE_BIOME_LOOKUP_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName())));
}
int surfaceY = level.getHeight(Heightmap.Types.WORLD_SURFACE, pos.getX(), pos.getZ());
BlockState surface = level.getBlockState(new BlockPos(pos.getX(), surfaceY - 1, pos.getZ()));
ok(source, "Surface block: " + BuiltInRegistries.BLOCK.getKey(surface.getBlock()) + " (y=" + (surfaceY - 1) + ")");
ok(source, "Position: " + pos.getX() + " " + pos.getY() + " " + pos.getZ() + " (chunk " + (pos.getX() >> 4) + "," + (pos.getZ() >> 4) + ")");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SURFACE_BLOCK_Y, MessageArgument.untrusted("value", BuiltInRegistries.BLOCK.getKey(surface.getBlock())), MessageArgument.untrusted("value2", (surfaceY - 1))));
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_POSITION_CHUNK, MessageArgument.untrusted("value", pos.getX()), MessageArgument.untrusted("value2", pos.getY()), MessageArgument.untrusted("value3", pos.getZ()), MessageArgument.untrusted("value4", (pos.getX() >> 4)), MessageArgument.untrusted("value5", (pos.getZ() >> 4))));
return 1;
}
private static int whatBlock(CommandSourceStack source) {
ServerPlayer player = source.getPlayer();
if (player == null) {
fail(source, "This command can only be used by players (it inspects the block you are looking at).");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_IT_INSPECTS_2));
return 0;
}
HitResult hit = player.pick(128.0D, 1.0F, false);
if (hit.getType() != HitResult.Type.BLOCK || !(hit instanceof BlockHitResult blockHit)) {
fail(source, "Look at a block, not the sky.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_LOOK_AT_BLOCK_NOT_SKY));
return 0;
}
ServerLevel level = source.getLevel();
BlockPos pos = blockHit.getBlockPos();
BlockState state = level.getBlockState(pos);
PlatformBlockState platform = ModdedBlockState.of(state, null);
ok(source, "Block: " + platform.key() + " (y=" + pos.getY() + ")");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_BLOCK_Y, MessageArgument.untrusted("value", platform.key()), MessageArgument.untrusted("value2", pos.getY())));
List<String> flags = new ArrayList<>();
if (platform.isSolid()) {
flags.add("solid");
@@ -761,20 +780,27 @@ public final class IrisModdedCommands {
if (platform.hasTileEntity()) {
flags.add("tile entity");
}
ok(source, flags.isEmpty() ? "Properties: (none)" : "Properties: " + String.join(", ", flags));
if (flags.isEmpty()) {
ok(source, IrisLanguage.plain(IrisMessages.MODDED_PROPERTIES_NONE));
return 1;
}
ok(source, IrisLanguage.plain(
IrisMessages.MODDED_PROPERTIES,
MessageArgument.untrusted("properties", String.join(", ", flags))
));
return 1;
}
private static int whatMarkers(CommandSourceStack source, String markerRaw) {
ServerPlayer player = source.getPlayer();
if (player == null) {
fail(source, "This command can only be used by players (markers render as particles around you).");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_MARKERS_RENDER));
return 0;
}
ServerLevel level = source.getLevel();
Engine engine = engineFor(level);
if (engine == null) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_7));
return 0;
}
String marker = markerRaw.trim();
@@ -782,7 +808,7 @@ public final class IrisModdedCommands {
int chunkX = origin.getX() >> 4;
int chunkZ = origin.getZ() >> 4;
MinecraftServer server = source.getServer();
ok(source, "Scanning for '" + marker + "' markers around you...");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SCANNING_MARKERS_AROUND_YOU, MessageArgument.untrusted("marker", marker)));
Thread thread = new Thread(() -> {
List<int[]> hits = new ArrayList<>();
MatterMarker matterMarker = new MatterMarker(marker);
@@ -796,7 +822,7 @@ public final class IrisModdedCommands {
}
} catch (Throwable e) {
LOGGER.error("Iris marker scan failed for {}", marker, e);
server.execute(() -> fail(source, "Marker scan failed: " + e.getClass().getSimpleName()));
server.execute(() -> fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_MARKER_SCAN_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()))));
return;
}
server.execute(() -> {
@@ -805,7 +831,7 @@ public final class IrisModdedCommands {
hit[0] + 0.5D, hit[1] + 1.0D, hit[2] + 0.5D,
3, 0.2D, 0.2D, 0.2D, 0.0D);
}
ok(source, "Found " + hits.size() + " nearby marker(s) (" + marker + ")");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_FOUND_NEARBY_MARKER_S, MessageArgument.untrusted("value", hits.size()), MessageArgument.untrusted("marker", marker)));
});
}, "Iris Marker Scan");
thread.setDaemon(true);
@@ -816,18 +842,18 @@ public final class IrisModdedCommands {
private static int gotoBiome(CommandSourceStack source, String key) {
ServerPlayer player = source.getPlayer();
if (player == null) {
fail(source, "This command can only be used by players.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_3));
return 0;
}
ServerLevel level = source.getLevel();
Engine engine = engineFor(level);
if (engine == null) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_8));
return 0;
}
IrisBiome biome = engine.getData().getBiomeLoader().load(key.trim());
if (biome == null) {
fail(source, "Unknown biome: " + key);
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_UNKNOWN_BIOME_2, MessageArgument.untrusted("key", key)));
return 0;
}
locate(source, level, engine, player, Locator.surfaceBiome(biome.getLoadKey()), "biome " + biome.getLoadKey());
@@ -837,22 +863,22 @@ public final class IrisModdedCommands {
private static int gotoRegion(CommandSourceStack source, String key) {
ServerPlayer player = source.getPlayer();
if (player == null) {
fail(source, "This command can only be used by players.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_4));
return 0;
}
ServerLevel level = source.getLevel();
Engine engine = engineFor(level);
if (engine == null) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_9));
return 0;
}
IrisRegion region = engine.getData().getRegionLoader().load(key.trim());
if (region == null) {
fail(source, "Unknown region: " + key);
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_UNKNOWN_REGION_2, MessageArgument.untrusted("key", key)));
return 0;
}
if (!engine.getDimension().getRegions().contains(region.getLoadKey())) {
fail(source, region.getLoadKey() + " is not defined in the dimension!");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_IS_NOT_DEFINED_DIMENSION, MessageArgument.untrusted("value", region.getLoadKey())));
return 0;
}
locate(source, level, engine, player, Locator.region(region.getLoadKey()), "region " + region.getLoadKey());
@@ -863,19 +889,17 @@ public final class IrisModdedCommands {
ServerLevel level = source.getLevel();
Engine engine = engineFor(level);
if (engine == null) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_10));
return 0;
}
String key = keyRaw.trim();
if (!engine.hasObjectPlacement(key)) {
fail(source, key + " is not configured in any region/biome object placements ("
+ engine.getData().getObjectLoader().getPossibleKeys().length + " object keys loaded).");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_IS_NOT_CONFIGURED_ANY_REGION_BIOME_OBJECT_PLACEMENTS_OBJECT_KEYS, MessageArgument.untrusted("key", key), MessageArgument.untrusted("value", engine.getData().getObjectLoader().getPossibleKeys().length)));
return 0;
}
ServerPlayer player = source.getPlayer();
if (player == null) {
fail(source, "This command can only be used by players. (object key '" + key + "' resolved against "
+ engine.getData().getObjectLoader().getPossibleKeys().length + " loaded object keys)");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_OBJECT_KEY, MessageArgument.untrusted("key", key), MessageArgument.untrusted("value", engine.getData().getObjectLoader().getPossibleKeys().length)));
return 0;
}
locate(source, level, engine, player, Locator.object(key), "object " + key);
@@ -886,17 +910,17 @@ public final class IrisModdedCommands {
ServerLevel level = source.getLevel();
Engine engine = engineFor(level);
if (engine == null) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_11));
return 0;
}
String key = keyRaw.trim();
if (key.isEmpty()) {
fail(source, "Name an Iris or native structure to locate.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_NAME_IRIS_NATIVE_STRUCTURE_LOCATE));
return 0;
}
ServerPlayer player = source.getPlayer();
if (player == null) {
fail(source, "This command can only be used by players.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_5));
return 0;
}
Optional<NativeStructureTarget> resolved = resolveNativeStructure(source, level, engine, key);
@@ -905,7 +929,7 @@ public final class IrisModdedCommands {
locateIrisStructure(source, level, engine, player, key);
return 1;
}
fail(source, "Unknown structure '" + key + "'. Use tab completion to choose an Iris placement or a registered native/datapack structure.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_UNKNOWN_STRUCTURE_USE_TAB_COMPLETION_CHOOSE_IRIS_PLACEMENT_REGISTERED_NATIVE, MessageArgument.untrusted("key", key)));
return 0;
}
NativeStructureTarget target = resolved.get();
@@ -924,7 +948,7 @@ public final class IrisModdedCommands {
fail(source, nativeUnavailableMessage(target.key(), target.availability()));
return 0;
}
ok(source, "Searching for native structure " + target.key() + " within " + NATIVE_STRUCTURE_LOCATE_RADIUS + " chunks...");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCHING_NATIVE_STRUCTURE_WITHIN_CHUNKS, MessageArgument.untrusted("value", target.key()), MessageArgument.untrusted("NATIVESTRUCTURELOCATERADIUS", NATIVE_STRUCTURE_LOCATE_RADIUS)));
runNativeStructureLocate(source, level, player, target);
return 1;
}
@@ -934,18 +958,17 @@ public final class IrisModdedCommands {
MinecraftServer server = source.getServer();
int blockX = player.blockPosition().getX();
int blockZ = player.blockPosition().getZ();
ok(source, "Searching for Iris-placed structure " + key + "...");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCHING_IRIS_PLACED_STRUCTURE, MessageArgument.untrusted("key", key)));
Thread thread = new Thread(() -> {
try {
IrisStructureLocator.LocateResult result =
IrisStructureLocator.locate(engine, key, blockX, blockZ, 1024);
if (result.status() == IrisStructureLocator.LocateStatus.SEARCH_LIMIT_REACHED) {
server.execute(() -> fail(source, "Unable to locate Iris-placed structure " + key
+ ": the density search safety limit was reached before the full 1024-chunk radius was searched."));
server.execute(() -> fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_UNABLE_LOCATE_IRIS_PLACED_STRUCTURE_DENSITY_SEARCH_SAFETY_LIMIT_WAS, MessageArgument.untrusted("key", key))));
return;
}
if (!result.found()) {
server.execute(() -> fail(source, "Could not find Iris-placed structure " + key + " within 1024 chunks."));
server.execute(() -> fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_COULD_NOT_FIND_IRIS_PLACED_STRUCTURE_WITHIN_1024_CHUNKS, MessageArgument.untrusted("key", key))));
return;
}
int targetX = result.originX();
@@ -955,7 +978,7 @@ public final class IrisModdedCommands {
"Iris-placed structure " + key));
} catch (Throwable e) {
LOGGER.error("Iris structure locate failed for {}", key, e);
server.execute(() -> fail(source, "Search failed: " + e.getClass().getSimpleName()));
server.execute(() -> fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCH_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()))));
}
}, "Iris Structure Locator");
thread.setDaemon(true);
@@ -984,8 +1007,7 @@ public final class IrisModdedCommands {
NATIVE_STRUCTURE_LOCATE_RADIUS,
false);
if (found == null) {
fail(source, "Could not find native structure " + target.key() + " within "
+ NATIVE_STRUCTURE_LOCATE_RADIUS + " chunks.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_COULD_NOT_FIND_NATIVE_STRUCTURE_WITHIN_CHUNKS, MessageArgument.untrusted("value", target.key()), MessageArgument.untrusted("NATIVESTRUCTURELOCATERADIUS", NATIVE_STRUCTURE_LOCATE_RADIUS)));
return;
}
BlockPos position = found.getFirst();
@@ -998,18 +1020,18 @@ public final class IrisModdedCommands {
"native structure " + target.key());
} catch (Throwable e) {
LOGGER.error("Native structure locate failed for {}", target.key(), e);
fail(source, "Search for native structure " + target.key() + " failed: " + e.getClass().getSimpleName());
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCH_NATIVE_STRUCTURE_FAILED, MessageArgument.untrusted("value", target.key()), MessageArgument.untrusted("value2", e.getClass().getSimpleName())));
}
}
private static void teleportToStructure(CommandSourceStack source, ServerLevel level, ServerPlayer player,
int targetX, int targetY, int targetZ, String label) {
if (player.hasDisconnected() || player.isRemoved()) {
fail(source, "The player disconnected before the structure search completed.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PLAYER_DISCONNECTED_BEFORE_STRUCTURE_SEARCH_COMPLETED));
return;
}
if (player.level() != level) {
fail(source, "You changed dimensions before the structure search completed; run the command again.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_YOU_CHANGED_DIMENSIONS_BEFORE_STRUCTURE_SEARCH_COMPLETED_RUN_COMMAND_AGAIN));
return;
}
level.getChunk(targetX >> 4, targetZ >> 4);
@@ -1017,17 +1039,17 @@ public final class IrisModdedCommands {
boolean teleported = player.teleportTo(level, targetX + 0.5D, clampedY, targetZ + 0.5D,
Set.<Relative>of(), player.getYRot(), player.getXRot(), false);
if (!teleported) {
fail(source, "Found " + label + " at " + targetX + " " + clampedY + " " + targetZ + ", but teleportation failed.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_FOUND_AT_BUT_TELEPORTATION_FAILED, MessageArgument.untrusted("label", label), MessageArgument.untrusted("targetX", targetX), MessageArgument.untrusted("clampedY", clampedY), MessageArgument.untrusted("targetZ", targetZ)));
return;
}
ok(source, "Teleported to " + label + " at " + targetX + " " + clampedY + " " + targetZ);
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_TELEPORTED_AT, MessageArgument.untrusted("label", label), MessageArgument.untrusted("targetX", targetX), MessageArgument.untrusted("clampedY", clampedY), MessageArgument.untrusted("targetZ", targetZ)));
}
static int verifyStructures(CommandSourceStack source, String keyRaw) {
ServerLevel level = source.getLevel();
Engine engine = engineFor(level);
if (engine == null) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_12));
return 0;
}
String key = keyRaw == null ? "" : keyRaw.trim();
@@ -1056,11 +1078,7 @@ public final class IrisModdedCommands {
}
}
int irisPlaced = IrisStructureLocator.placedKeys(engine).size();
ok(source, "Structure reachability: " + available + " native generation-eligible, " + irisPlaced
+ " Iris-placed, " + disabled + " native disabled, " + suppressed
+ " native replaced by Iris placements, " + unreachableBiomes
+ " native excluded by this pack's biomes, and " + unsupported
+ " registered native structures unsupported in this dimension. Use /iris structure verify <key> for one structure.");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_STRUCTURE_REACHABILITY_NATIVE_GENERATION_ELIGIBLE_IRIS_PLACED_NATIVE_DISABLED_NATIVE, MessageArgument.untrusted("available", available), MessageArgument.untrusted("irisPlaced", irisPlaced), MessageArgument.untrusted("disabled", disabled), MessageArgument.untrusted("suppressed", suppressed), MessageArgument.untrusted("unreachableBiomes", unreachableBiomes), MessageArgument.untrusted("unsupported", unsupported)));
return 1;
}
@@ -1068,24 +1086,22 @@ public final class IrisModdedCommands {
Optional<NativeStructureTarget> target = resolveNativeStructure(source, level, engine, key);
if (target.isEmpty()) {
if (IrisStructureLocator.isPlaced(engine, key)) {
ok(source, "Structure " + key + " is Iris-placed and locatable with /iris goto structure " + key + ".");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_STRUCTURE_IS_IRIS_PLACED_LOCATABLE_WITH_IRIS_GOTO_STRUCTURE, MessageArgument.untrusted("key", key), MessageArgument.untrusted("key2", key)));
return 1;
}
fail(source, "Unknown structure '" + key + "'. It is neither Iris-placed nor registered by vanilla or a datapack.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_UNKNOWN_STRUCTURE_IT_IS_NEITHER_IRIS_PLACED_NOR_REGISTERED_BY, MessageArgument.untrusted("key", key)));
return 0;
}
NativeStructureTarget resolved = target.get();
if (resolved.availability() == NativeStructureAvailability.IRIS_SUPPRESSED) {
ok(source, "Structure " + resolved.key()
+ " is explicitly replaced by an Iris placement and locatable with /iris goto structure "
+ resolved.key() + ".");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_STRUCTURE_IS_EXPLICITLY_REPLACED_BY_IRIS_PLACEMENT_LOCATABLE_WITH_IRIS, MessageArgument.untrusted("value", resolved.key()), MessageArgument.untrusted("value2", resolved.key())));
return 1;
}
if (resolved.availability() != NativeStructureAvailability.AVAILABLE) {
fail(source, nativeUnavailableMessage(resolved.key(), resolved.availability()));
return 0;
}
ok(source, "Native structure " + resolved.key() + " is enabled, supported by this dimension's generator state, and locatable with /iris goto structure " + resolved.key() + ".");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_NATIVE_STRUCTURE_IS_ENABLED_SUPPORTED_BY_THIS_DIMENSION_S_GENERATOR, MessageArgument.untrusted("value", resolved.key()), MessageArgument.untrusted("value2", resolved.key())));
return 1;
}
@@ -1162,13 +1178,13 @@ public final class IrisModdedCommands {
ServerLevel level = source.getLevel();
Engine engine = engineFor(level);
if (engine == null) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_13));
return 0;
}
String type = typeRaw.trim();
ServerPlayer player = source.getPlayer();
if (player == null) {
fail(source, "This command can only be used by players. (POI type '" + type + "' accepted)");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_POI_TYPE, MessageArgument.untrusted("type", type)));
return 0;
}
locate(source, level, engine, player, Locator.poi(type), "POI " + type);
@@ -1179,13 +1195,13 @@ public final class IrisModdedCommands {
MinecraftServer server = source.getServer();
int chunkX = player.blockPosition().getX() >> 4;
int chunkZ = player.blockPosition().getZ() >> 4;
ok(source, "Searching for " + label + "...");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCHING, MessageArgument.untrusted("label", label)));
CompletableFuture<Position2> search;
try {
search = locator.find(engine, new Position2(chunkX, chunkZ), LOCATE_TIMEOUT_MS, (Integer checks) -> {
});
} catch (WrongEngineBroException e) {
fail(source, "The engine for this world has been closed; rejoin the dimension and try again.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_ENGINE_THIS_WORLD_HAS_BEEN_CLOSED_REJOIN_DIMENSION_TRY_AGAIN));
return;
}
UUID playerId = player.getUUID();
@@ -1212,7 +1228,7 @@ public final class IrisModdedCommands {
LOGGER.error("Iris locate failed for {}", label, failure);
server.execute(() -> {
if (ACTIVE_LOCATE_REQUESTS.remove(playerId, search)) {
fail(source, "Search failed: " + failure);
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_SEARCH_FAILED_2, MessageArgument.untrusted("failure", failure)));
}
});
return;
@@ -1220,7 +1236,7 @@ public final class IrisModdedCommands {
if (at == null) {
server.execute(() -> {
if (ACTIVE_LOCATE_REQUESTS.remove(playerId, search)) {
fail(source, "Could not find " + label + " within the search timeout.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_COULD_NOT_FIND_WITHIN_SEARCH_TIMEOUT, MessageArgument.untrusted("label", label)));
}
});
return;
@@ -1240,9 +1256,9 @@ public final class IrisModdedCommands {
IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
int blockY = engine.getMinHeight() + engine.getHeight(blockX, blockZ, false) + 2;
player.teleportTo(level, blockX + 0.5D, blockY, blockZ + 0.5D, Set.<Relative>of(), player.getYRot(), player.getXRot(), false);
ok(source, "Teleported to " + label + " at " + blockX + " " + blockY + " " + blockZ);
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_TELEPORTED_AT_2, MessageArgument.untrusted("label", label), MessageArgument.untrusted("blockX", blockX), MessageArgument.untrusted("blockY", blockY), MessageArgument.untrusted("blockZ", blockZ)));
} catch (GenerationSessionException e) {
fail(source, "The engine changed while locating " + label + "; try again.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_ENGINE_CHANGED_WHILE_LOCATING_TRY_AGAIN, MessageArgument.untrusted("label", label)));
}
}
@@ -1259,11 +1275,11 @@ public final class IrisModdedCommands {
ServerLevel level = source.getLevel();
Engine engine = engineFor(level);
if (engine == null) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_14));
return 0;
}
ok(source, "World seed: " + level.getSeed());
ok(source, "Engine seed: " + engine.getSeedManager().getSeed() + " (mixed: " + engine.getSeedManager().getFullMixedSeed() + ")");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_WORLD_SEED, MessageArgument.untrusted("value", level.getSeed())));
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_ENGINE_SEED_MIXED, MessageArgument.untrusted("value", engine.getSeedManager().getSeed()), MessageArgument.untrusted("value2", engine.getSeedManager().getFullMixedSeed())));
return 1;
}
@@ -1271,7 +1287,7 @@ public final class IrisModdedCommands {
ServerLevel level = source.getLevel();
Engine engine = engineFor(level);
if (engine == null) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_15));
return 0;
}
ModdedGoldenHash.start(source, level, engine, radius, threads, mode);
@@ -1282,14 +1298,14 @@ public final class IrisModdedCommands {
MinecraftServer server = source.getServer();
boolean defaultOverworld = PackDownloader.isDefaultOverworld(pack);
String downloadSource = defaultOverworld ? "beta release" : "branch " + branch;
ok(source, "Downloading IrisDimensions/" + pack + " (" + downloadSource + ")...");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_DOWNLOADING_IRISDIMENSIONS, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("downloadSource", downloadSource)));
Thread thread = new Thread(() -> {
boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), pack, branch,
(String message) -> server.execute(() -> ok(source, message)));
if (installed) {
server.execute(() -> ok(source, "Pack '" + pack + "' installed. Its exact dimension types and custom biomes join the forced Iris datapack on the next server restart; restart before creating worlds from this pack."));
server.execute(() -> ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_INSTALLED_ITS_EXACT_DIMENSION_TYPES_CUSTOM_BIOMES_JOIN_FORCED, MessageArgument.untrusted("pack", pack))));
} else {
server.execute(() -> fail(source, "Pack download failed for " + pack + " (" + downloadSource + "; see console)."));
server.execute(() -> fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("downloadSource", downloadSource))));
}
}, "Iris Pack Download");
thread.setDaemon(true);
@@ -1301,17 +1317,17 @@ public final class IrisModdedCommands {
ServerLevel level = source.getLevel();
Engine engine = engineFor(level);
if (engine == null) {
fail(source, "This dimension is not generated by Iris.");
fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_16));
return 0;
}
ok(source, "Generated: " + engine.getGenerated() + " chunk(s), " + String.format("%.1f", engine.getGeneratedPerSecond()) + "/s");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_GENERATED_CHUNK_S_S, MessageArgument.untrusted("value", engine.getGenerated()), MessageArgument.untrusted("value2", String.format("%.1f", engine.getGeneratedPerSecond()))));
KMap<String, Double> pulled = engine.getMetrics().pull();
Map<String, Double> sorted = new TreeMap<>(pulled);
for (Map.Entry<String, Double> entry : sorted.entrySet()) {
if (entry.getValue() == null || entry.getValue() <= 0D) {
continue;
}
ok(source, " " + entry.getKey() + ": " + String.format("%.2f", entry.getValue()) + "ms");
ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_MS, MessageArgument.untrusted("value", entry.getKey()), MessageArgument.untrusted("value2", String.format("%.2f", entry.getValue()))));
}
return 1;
}
@@ -24,6 +24,12 @@ import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.HoverEvent;
import net.minecraft.network.chat.MutableComponent;
import art.arcane.iris.core.localization.IrisMessages;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.ModdedHelpMessages;
import art.arcane.volmlib.util.director.help.DirectorHelpMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
import art.arcane.volmlib.util.localization.TextKey;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -50,124 +56,124 @@ final class ModdedCommandHelp {
static {
SECTIONS.put("", List.of(
Entry.command("version", "", "Print version information"),
Entry.command("info", "[dimension]", "List loaded Iris dimensions and pack details"),
Entry.command("what", "[block|hand|markers]", "Inspect the Iris biome, region, cave biome, surface and chunk at your position, the block you look at, your held item, or nearby markers"),
Entry.group("find", "Find and teleport to Iris biomes, regions, objects, Iris structures, native structures and points of interest", "goto"),
Entry.command("tp", "<dimension> [player]", "Teleport yourself or a named player into a loaded Iris dimension"),
Entry.command("evacuate", "[dimension]", "Teleport every player out of an Iris dimension to the primary world spawn"),
Entry.command("seed", "", "Print world and engine seed information"),
Entry.command("debug", "", "Toggle Iris debug logging and save settings.json"),
Entry.command("reload", "", "Reload settings.json (also hotloaded automatically every 3s)"),
Entry.command("download", "<pack> [branch]", "Download a pack project", "dl"),
Entry.command("metrics", "", "Print generation metrics for your current Iris dimension", "measure"),
Entry.command("regen", "[radius]", "Delete and regenerate nearby chunks in place", "rg"),
Entry.group("pregen", "Pregenerate an Iris dimension", "pregenerate"),
Entry.command("wand", "", "Get an Iris object wand"),
Entry.group("object", "Object wand, save, paste, analyze and undo tools", "o"),
Entry.group("edit", "Open pack biome, region and dimension json files in your desktop editor"),
Entry.command("create", "<name> <pack|pack:dimensionKey> [seed]", "Create and inject a persistent Iris dimension; quote pack:dimensionKey to pick a specific pack dimension"),
Entry.group("studio", "Pack project creation, packaging and reports", "std", "s"),
Entry.group("pack", "Pack validation and maintenance", "pk"),
Entry.group("world", "Runtime Iris dimension creation, removal and status", "w"),
Entry.group("datapack", "World datapack install and status helpers", "datapacks", "dp"),
Entry.group("structure", "Iris structure index, info and placement tools", "struct", "str"),
Entry.command("goldenhash", "[radius] [threads] [capture|verify]", "Generate deterministic block hashes for parity testing", "gold"),
Entry.group("developer", "Developer diagnostics: Sentry test, network interfaces, region-file scan", "dev")
Entry.command("version", "", ModdedHelpMessages.COMMAND_VERSION_PRINT_VERSION_INFORMATION),
Entry.command("info", "[dimension]", ModdedHelpMessages.COMMAND_INFO_LIST_LOADED_IRIS_DIMENSIONS_AND_PACK_DETAILS),
Entry.command("what", "[block|hand|markers]", ModdedHelpMessages.COMMAND_WHAT_INSPECT_THE_IRIS_BIOME_REGION_CAVE_BIOME_SURFACE_AND_CHUNK_AT_YOUR),
Entry.group("find", ModdedHelpMessages.GROUP_FIND_FIND_AND_TELEPORT_TO_IRIS_BIOMES_REGIONS_OBJECTS_IRIS_STRUCTURES_NATIVE_STRUCTURES, "goto"),
Entry.command("tp", "<dimension> [player]", ModdedHelpMessages.COMMAND_TP_TELEPORT_YOURSELF_OR_A_NAMED_PLAYER_INTO_A_LOADED_IRIS_DIMENSION),
Entry.command("evacuate", "[dimension]", ModdedHelpMessages.COMMAND_EVACUATE_TELEPORT_EVERY_PLAYER_OUT_OF_AN_IRIS_DIMENSION_TO_THE_PRIMARY_WORLD),
Entry.command("seed", "", ModdedHelpMessages.COMMAND_SEED_PRINT_WORLD_AND_ENGINE_SEED_INFORMATION),
Entry.command("debug", "", ModdedHelpMessages.COMMAND_DEBUG_TOGGLE_IRIS_DEBUG_LOGGING_AND_SAVE_SETTINGS_JSON),
Entry.command("reload", "", ModdedHelpMessages.COMMAND_RELOAD_RELOAD_SETTINGS_JSON_ALSO_HOTLOADED_AUTOMATICALLY_EVERY_3S),
Entry.command("download", "<pack> [branch]", ModdedHelpMessages.COMMAND_DOWNLOAD_DOWNLOAD_A_PACK_PROJECT, "dl"),
Entry.command("metrics", "", ModdedHelpMessages.COMMAND_METRICS_PRINT_GENERATION_METRICS_FOR_YOUR_CURRENT_IRIS_DIMENSION, "measure"),
Entry.command("regen", "[radius]", ModdedHelpMessages.COMMAND_REGEN_DELETE_AND_REGENERATE_NEARBY_CHUNKS_IN_PLACE, "rg"),
Entry.group("pregen", ModdedHelpMessages.GROUP_PREGEN_PREGENERATE_AN_IRIS_DIMENSION, "pregenerate"),
Entry.command("wand", "", ModdedHelpMessages.COMMAND_WAND_GET_AN_IRIS_OBJECT_WAND),
Entry.group("object", ModdedHelpMessages.GROUP_OBJECT_OBJECT_WAND_SAVE_PASTE_ANALYZE_AND_UNDO_TOOLS, "o"),
Entry.group("edit", ModdedHelpMessages.GROUP_EDIT_OPEN_PACK_BIOME_REGION_AND_DIMENSION_JSON_FILES_IN_YOUR_DESKTOP_EDITOR),
Entry.command("create", "<name> <pack|pack:dimensionKey> [seed]", ModdedHelpMessages.COMMAND_CREATE_CREATE_AND_INJECT_A_PERSISTENT_IRIS_DIMENSION_QUOTE_PACK_DIMENSIONKEY_TO_PICK),
Entry.group("studio", ModdedHelpMessages.GROUP_STUDIO_PACK_PROJECT_CREATION_PACKAGING_AND_REPORTS, "std", "s"),
Entry.group("pack", ModdedHelpMessages.GROUP_PACK_PACK_VALIDATION_AND_MAINTENANCE, "pk"),
Entry.group("world", ModdedHelpMessages.GROUP_WORLD_RUNTIME_IRIS_DIMENSION_CREATION_REMOVAL_AND_STATUS, "w"),
Entry.group("datapack", ModdedHelpMessages.GROUP_DATAPACK_WORLD_DATAPACK_INSTALL_AND_STATUS_HELPERS, "datapacks", "dp"),
Entry.group("structure", ModdedHelpMessages.GROUP_STRUCTURE_IRIS_STRUCTURE_INDEX_INFO_AND_PLACEMENT_TOOLS, "struct", "str"),
Entry.command("goldenhash", "[radius] [threads] [capture|verify]", ModdedHelpMessages.COMMAND_GOLDENHASH_GENERATE_DETERMINISTIC_BLOCK_HASHES_FOR_PARITY_TESTING, "gold"),
Entry.group("developer", ModdedHelpMessages.GROUP_DEVELOPER_DEVELOPER_DIAGNOSTICS_SENTRY_TEST_NETWORK_INTERFACES_REGION_FILE_SCAN, "dev")
));
SECTIONS.put("find", List.of(
Entry.command("biome", "<key>", "Find an Iris biome"),
Entry.command("region", "<key>", "Find an Iris region"),
Entry.command("object", "<key>", "Find an object placement"),
Entry.command("structure", "<key>", "Find an Iris-placed or native/datapack structure"),
Entry.command("poi", "<type>", "Find a supported point of interest")
Entry.command("biome", "<key>", ModdedHelpMessages.COMMAND_BIOME_FIND_AN_IRIS_BIOME),
Entry.command("region", "<key>", ModdedHelpMessages.COMMAND_REGION_FIND_AN_IRIS_REGION),
Entry.command("object", "<key>", ModdedHelpMessages.COMMAND_OBJECT_FIND_AN_OBJECT_PLACEMENT),
Entry.command("structure", "<key>", ModdedHelpMessages.COMMAND_STRUCTURE_FIND_AN_IRIS_PLACED_OR_NATIVE_DATAPACK_STRUCTURE),
Entry.command("poi", "<type>", ModdedHelpMessages.COMMAND_POI_FIND_A_SUPPORTED_POINT_OF_INTEREST)
));
SECTIONS.put("goto", SECTIONS.get("find"));
SECTIONS.put("edit", List.of(
Entry.command("biome", "[key]", "Open a biome json in your desktop editor; no key opens the biome at your position"),
Entry.command("region", "[key]", "Open a region json in your desktop editor; no key opens the region at your position"),
Entry.command("dimension", "", "Open the current pack's dimension json in your desktop editor")
Entry.command("biome", "[key]", ModdedHelpMessages.COMMAND_BIOME_OPEN_A_BIOME_JSON_IN_YOUR_DESKTOP_EDITOR_NO_KEY_OPENS_THE),
Entry.command("region", "[key]", ModdedHelpMessages.COMMAND_REGION_OPEN_A_REGION_JSON_IN_YOUR_DESKTOP_EDITOR_NO_KEY_OPENS_THE),
Entry.command("dimension", "", ModdedHelpMessages.COMMAND_DIMENSION_OPEN_THE_CURRENT_PACK_S_DIMENSION_JSON_IN_YOUR_DESKTOP_EDITOR)
));
SECTIONS.put("pregen", List.of(
Entry.command("start", "<radius> [dimension] [at] [x] [z] [gui] [sync] [nocache]", "Start pregeneration; radius in blocks, resumable checkpoint cache on by default, center via 'at <x> <z>', flags compose in any order"),
Entry.command("stop", "", "Stop the active pregeneration task", "x"),
Entry.command("pause", "", "Pause or resume pregeneration", "resume"),
Entry.command("status", "", "Show pregeneration status")
Entry.command("start", "<radius> [dimension] [at] [x] [z] [gui] [sync] [nocache]", ModdedHelpMessages.COMMAND_START_START_PREGENERATION_RADIUS_IN_BLOCKS_RESUMABLE_CHECKPOINT_CACHE_ON_BY_DEFAULT_CENTER),
Entry.command("stop", "", ModdedHelpMessages.COMMAND_STOP_STOP_THE_ACTIVE_PREGENERATION_TASK, "x"),
Entry.command("pause", "", ModdedHelpMessages.COMMAND_PAUSE_PAUSE_OR_RESUME_PREGENERATION, "resume"),
Entry.command("status", "", ModdedHelpMessages.COMMAND_STATUS_SHOW_PREGENERATION_STATUS)
));
SECTIONS.put("pregenerate", SECTIONS.get("pregen"));
SECTIONS.put("object", List.of(
Entry.command("wand", "", "Get an Iris object wand"),
Entry.command("dust", "", "Get dust that reveals object placements", "d"),
Entry.command("save", "[overwrite] <name>", "Save the selected wand volume as an object"),
Entry.command("paste", "[at] [x] [y] [z] [rotate] [degrees] <key>", "Paste an object at your position or a given position, optionally rotated"),
Entry.command("expand", "[amount]", "Expand the wand selection in your looking direction"),
Entry.command("contract", "[amount]", "Contract the wand selection in your looking direction", "-"),
Entry.command("shift", "[amount]", "Shift the wand selection in your looking direction"),
Entry.command("position1", "[look]", "Set selection point 1", "p1"),
Entry.command("position2", "[look]", "Set selection point 2", "p2"),
Entry.command("x+y", "", "Autoselect up and out", "xpy"),
Entry.command("x&y", "", "Autoselect up, down and out", "xay"),
Entry.command("analyze", "<key>", "Show object composition"),
Entry.command("shrink", "<key>", "Shrink an object to its minimum size"),
Entry.command("plausibilize", "<key|prefix/> [dryrun=true] [reach=N]", "Grow branches so tree leaves survive vanilla decay"),
Entry.command("undo", "[amount]", "Undo pasted objects", "u")
Entry.command("wand", "", ModdedHelpMessages.COMMAND_WAND_GET_AN_IRIS_OBJECT_WAND_2),
Entry.command("dust", "", ModdedHelpMessages.COMMAND_DUST_GET_DUST_THAT_REVEALS_OBJECT_PLACEMENTS, "d"),
Entry.command("save", "[overwrite] <name>", ModdedHelpMessages.COMMAND_SAVE_SAVE_THE_SELECTED_WAND_VOLUME_AS_AN_OBJECT),
Entry.command("paste", "[at] [x] [y] [z] [rotate] [degrees] <key>", ModdedHelpMessages.COMMAND_PASTE_PASTE_AN_OBJECT_AT_YOUR_POSITION_OR_A_GIVEN_POSITION_OPTIONALLY_ROTATED),
Entry.command("expand", "[amount]", ModdedHelpMessages.COMMAND_EXPAND_EXPAND_THE_WAND_SELECTION_IN_YOUR_LOOKING_DIRECTION),
Entry.command("contract", "[amount]", ModdedHelpMessages.COMMAND_CONTRACT_CONTRACT_THE_WAND_SELECTION_IN_YOUR_LOOKING_DIRECTION, "-"),
Entry.command("shift", "[amount]", ModdedHelpMessages.COMMAND_SHIFT_SHIFT_THE_WAND_SELECTION_IN_YOUR_LOOKING_DIRECTION),
Entry.command("position1", "[look]", ModdedHelpMessages.COMMAND_POSITION1_SET_SELECTION_POINT_1, "p1"),
Entry.command("position2", "[look]", ModdedHelpMessages.COMMAND_POSITION2_SET_SELECTION_POINT_2, "p2"),
Entry.command("x+y", "", ModdedHelpMessages.COMMAND_X_Y_AUTOSELECT_UP_AND_OUT, "xpy"),
Entry.command("x&y", "", ModdedHelpMessages.COMMAND_X_Y_AUTOSELECT_UP_DOWN_AND_OUT, "xay"),
Entry.command("analyze", "<key>", ModdedHelpMessages.COMMAND_ANALYZE_SHOW_OBJECT_COMPOSITION),
Entry.command("shrink", "<key>", ModdedHelpMessages.COMMAND_SHRINK_SHRINK_AN_OBJECT_TO_ITS_MINIMUM_SIZE),
Entry.command("plausibilize", "<key|prefix/> [dryrun=true] [reach=N]", ModdedHelpMessages.COMMAND_PLAUSIBILIZE_GROW_BRANCHES_SO_TREE_LEAVES_SURVIVE_VANILLA_DECAY),
Entry.command("undo", "[amount]", ModdedHelpMessages.COMMAND_UNDO_UNDO_PASTED_OBJECTS, "u")
));
SECTIONS.put("o", SECTIONS.get("object"));
SECTIONS.put("studio", List.of(
Entry.command("create", "<name> [template]", "Create a new pack project", "+"),
Entry.command("package", "[pack]", "Package a dimension into a compressed format"),
Entry.command("version", "[pack]", "Print a pack version"),
Entry.command("regions", "[radius]", "Calculate nearby region distribution"),
Entry.command("open", "<pack> [seed]", "Open a temporary studio dimension for a pack", "o"),
Entry.command("close", "", "Close the open studio dimension and discard its world", "x"),
Entry.command("tpstudio", "", "Teleport into the open studio dimension", "stp"),
Entry.command("status", "", "Show the open studio dimension and its pack"),
Entry.command("noise", "[generator] [seed]", "Open the Noise Explorer GUI on the server display", "nmap"),
Entry.command("map", "", "Open the Vision map GUI on the server display", "render"),
Entry.command("vscode", "[pack]", "Regenerate the .code-workspace for a pack and open it in your desktop editor", "vsc"),
Entry.command("update", "[pack]", "Regenerate the .code-workspace for a pack"),
Entry.command("importvanilla", "", "Explain vanilla import workflow", "importv", "iv")
Entry.command("create", "<name> [template]", ModdedHelpMessages.COMMAND_CREATE_CREATE_A_NEW_PACK_PROJECT, "+"),
Entry.command("package", "[pack]", ModdedHelpMessages.COMMAND_PACKAGE_PACKAGE_A_DIMENSION_INTO_A_COMPRESSED_FORMAT),
Entry.command("version", "[pack]", ModdedHelpMessages.COMMAND_VERSION_PRINT_A_PACK_VERSION),
Entry.command("regions", "[radius]", ModdedHelpMessages.COMMAND_REGIONS_CALCULATE_NEARBY_REGION_DISTRIBUTION),
Entry.command("open", "<pack> [seed]", ModdedHelpMessages.COMMAND_OPEN_OPEN_A_TEMPORARY_STUDIO_DIMENSION_FOR_A_PACK, "o"),
Entry.command("close", "", ModdedHelpMessages.COMMAND_CLOSE_CLOSE_THE_OPEN_STUDIO_DIMENSION_AND_DISCARD_ITS_WORLD, "x"),
Entry.command("tpstudio", "", ModdedHelpMessages.COMMAND_TPSTUDIO_TELEPORT_INTO_THE_OPEN_STUDIO_DIMENSION, "stp"),
Entry.command("status", "", ModdedHelpMessages.COMMAND_STATUS_SHOW_THE_OPEN_STUDIO_DIMENSION_AND_ITS_PACK),
Entry.command("noise", "[generator] [seed]", ModdedHelpMessages.COMMAND_NOISE_OPEN_THE_NOISE_EXPLORER_GUI_ON_THE_SERVER_DISPLAY, "nmap"),
Entry.command("map", "", ModdedHelpMessages.COMMAND_MAP_OPEN_THE_VISION_MAP_GUI_ON_THE_SERVER_DISPLAY, "render"),
Entry.command("vscode", "[pack]", ModdedHelpMessages.COMMAND_VSCODE_REGENERATE_THE_CODE_WORKSPACE_FOR_A_PACK_AND_OPEN_IT_IN_YOUR, "vsc"),
Entry.command("update", "[pack]", ModdedHelpMessages.COMMAND_UPDATE_REGENERATE_THE_CODE_WORKSPACE_FOR_A_PACK),
Entry.command("importvanilla", "", ModdedHelpMessages.COMMAND_IMPORTVANILLA_EXPLAIN_VANILLA_IMPORT_WORKFLOW, "importv", "iv")
));
SECTIONS.put("std", SECTIONS.get("studio"));
SECTIONS.put("s", SECTIONS.get("studio"));
SECTIONS.put("pack", List.of(
Entry.command("validate", "[pack]", "Validate a pack or every pack", "v"),
Entry.command("cleanup", "<pack> [apply]", "Preview or quarantine unused-resource candidates", "c"),
Entry.command("restore", "<pack> [apply]", "Preview or restore the latest quarantine", "r"),
Entry.command("status", "[pack]", "Show cached validation status", "s")
Entry.command("validate", "[pack]", ModdedHelpMessages.COMMAND_VALIDATE_VALIDATE_A_PACK_OR_EVERY_PACK, "v"),
Entry.command("cleanup", "<pack> [apply]", ModdedHelpMessages.COMMAND_CLEANUP_PREVIEW_OR_QUARANTINE_UNUSED_RESOURCE_CANDIDATES, "c"),
Entry.command("restore", "<pack> [apply]", ModdedHelpMessages.COMMAND_RESTORE_PREVIEW_OR_RESTORE_THE_LATEST_QUARANTINE, "r"),
Entry.command("status", "[pack]", ModdedHelpMessages.COMMAND_STATUS_SHOW_CACHED_VALIDATION_STATUS, "s")
));
SECTIONS.put("pk", SECTIONS.get("pack"));
SECTIONS.put("world", List.of(
Entry.command("enable", "<dimension> <pack|pack:dimensionKey> [seed|random]", "Create and inject a persistent Iris dimension at runtime; downloads the pack if missing, quote pack:dimensionKey to pick a specific pack dimension", "create"),
Entry.command("replace-overworld", "<pack|pack:dimensionKey> [seed|random]", "Inject an Iris primary world and route players there instead of the vanilla overworld"),
Entry.command("disable", "<dimension>", "Evacuate and unload an Iris dimension; world data on disk is kept for re-enabling"),
Entry.command("delete", "<dimension>", "Disable an Iris dimension and wipe its chunk and mantle data from disk", "remove", "rm"),
Entry.command("list", "", "List loaded Iris dimensions", "ls"),
Entry.command("status", "", "Show loaded Iris dimensions and the configured primary world")
Entry.command("enable", "<dimension> <pack|pack:dimensionKey> [seed|random]", ModdedHelpMessages.COMMAND_ENABLE_CREATE_AND_INJECT_A_PERSISTENT_IRIS_DIMENSION_AT_RUNTIME_DOWNLOADS_THE_PACK, "create"),
Entry.command("replace-overworld", "<pack|pack:dimensionKey> [seed|random]", ModdedHelpMessages.COMMAND_REPLACE_OVERWORLD_INJECT_AN_IRIS_PRIMARY_WORLD_AND_ROUTE_PLAYERS_THERE_INSTEAD_OF_THE),
Entry.command("disable", "<dimension>", ModdedHelpMessages.COMMAND_DISABLE_EVACUATE_AND_UNLOAD_AN_IRIS_DIMENSION_WORLD_DATA_ON_DISK_IS_KEPT),
Entry.command("delete", "<dimension>", ModdedHelpMessages.COMMAND_DELETE_DISABLE_AN_IRIS_DIMENSION_AND_WIPE_ITS_CHUNK_AND_MANTLE_DATA_FROM, "remove", "rm"),
Entry.command("list", "", ModdedHelpMessages.COMMAND_LIST_LIST_LOADED_IRIS_DIMENSIONS, "ls"),
Entry.command("status", "", ModdedHelpMessages.COMMAND_STATUS_SHOW_LOADED_IRIS_DIMENSIONS_AND_THE_CONFIGURED_PRIMARY_WORLD)
));
SECTIONS.put("w", SECTIONS.get("world"));
SECTIONS.put("datapack", List.of(
Entry.command("status", "", "Check loaded Iris dimension type overrides"),
Entry.command("install", "", "Install dimension type overrides for loaded Iris dimensions"),
Entry.command("list", "", "List configured and installed datapacks", "ls"),
Entry.command("ingest", "", "Explain Bukkit datapack ingest workflow", "pull"),
Entry.command("remove", "<id>", "Explain datapack removal workflow", "rm")
Entry.command("status", "", ModdedHelpMessages.COMMAND_STATUS_CHECK_LOADED_IRIS_DIMENSION_TYPE_OVERRIDES),
Entry.command("install", "", ModdedHelpMessages.COMMAND_INSTALL_INSTALL_DIMENSION_TYPE_OVERRIDES_FOR_LOADED_IRIS_DIMENSIONS),
Entry.command("list", "", ModdedHelpMessages.COMMAND_LIST_LIST_CONFIGURED_AND_INSTALLED_DATAPACKS, "ls"),
Entry.command("ingest", "", ModdedHelpMessages.COMMAND_INGEST_EXPLAIN_BUKKIT_DATAPACK_INGEST_WORKFLOW, "pull"),
Entry.command("remove", "<id>", ModdedHelpMessages.COMMAND_REMOVE_EXPLAIN_DATAPACK_REMOVAL_WORKFLOW, "rm")
));
SECTIONS.put("datapacks", SECTIONS.get("datapack"));
SECTIONS.put("dp", SECTIONS.get("datapack"));
SECTIONS.put("structure", List.of(
Entry.command("list", "", "Regenerate structure-index.json", "ls"),
Entry.command("info", "<key>", "Resolve an Iris structure graph and report bounds"),
Entry.command("place", "<key>", "Assemble and place an Iris structure at your location", "p"),
Entry.command("import", "", "Explain Bukkit structure import workflow", "import-all", "reimport", "imp", "all"),
Entry.command("capture", "", "Explain Bukkit structure capture workflow", "cap"),
Entry.command("verify", "[key]", "Report native and Iris structure reachability in the current dimension", "locateall")
Entry.command("list", "", ModdedHelpMessages.COMMAND_LIST_REGENERATE_STRUCTURE_INDEX_JSON, "ls"),
Entry.command("info", "<key>", ModdedHelpMessages.COMMAND_INFO_RESOLVE_AN_IRIS_STRUCTURE_GRAPH_AND_REPORT_BOUNDS),
Entry.command("place", "<key>", ModdedHelpMessages.COMMAND_PLACE_ASSEMBLE_AND_PLACE_AN_IRIS_STRUCTURE_AT_YOUR_LOCATION, "p"),
Entry.command("import", "", ModdedHelpMessages.COMMAND_IMPORT_EXPLAIN_BUKKIT_STRUCTURE_IMPORT_WORKFLOW, "import-all", "reimport", "imp", "all"),
Entry.command("capture", "", ModdedHelpMessages.COMMAND_CAPTURE_EXPLAIN_BUKKIT_STRUCTURE_CAPTURE_WORKFLOW, "cap"),
Entry.command("verify", "[key]", ModdedHelpMessages.COMMAND_VERIFY_REPORT_NATIVE_AND_IRIS_STRUCTURE_REACHABILITY_IN_THE_CURRENT_DIMENSION, "locateall")
));
SECTIONS.put("struct", SECTIONS.get("structure"));
SECTIONS.put("str", SECTIONS.get("structure"));
SECTIONS.put("developer", List.of(
Entry.command("sentry", "", "Send a test exception to the Iris error reporter"),
Entry.command("network", "", "List network interfaces and their addresses", "ip")
Entry.command("sentry", "", ModdedHelpMessages.COMMAND_SENTRY_SEND_A_TEST_EXCEPTION_TO_THE_IRIS_ERROR_REPORTER),
Entry.command("network", "", ModdedHelpMessages.COMMAND_NETWORK_LIST_NETWORK_INTERFACES_AND_THEIR_ADDRESSES, "ip")
));
SECTIONS.put("dev", SECTIONS.get("developer"));
}
@@ -179,7 +185,10 @@ final class ModdedCommandHelp {
String normalized = normalize(path);
List<Entry> entries = SECTIONS.get(normalized);
if (entries == null) {
ModdedCommandFeedback.fail(source, "Unknown Iris help section: " + normalized);
ModdedCommandFeedback.fail(source, IrisLanguage.plain(
IrisMessages.MODDED_HELP_UNKNOWN_SECTION,
MessageArgument.untrusted("section", normalized)
));
return 0;
}
@@ -208,9 +217,11 @@ final class ModdedCommandHelp {
String parent = parentPath(path);
String command = parent.isEmpty() ? "/iris" : "/iris help " + parent;
MutableComponent hover = Component.empty()
.append(text("Click to go back to ", DARK_GREEN))
.append(text(parent.isEmpty() ? "Iris" : parent, PARAMETER_ALT));
return text("〈 Back", BACK).withStyle((style) -> style
.append(text(IrisLanguage.plain(
IrisMessages.MODDED_HELP_BACK_HOVER,
MessageArgument.untrusted("parent", parent.isEmpty() ? "Iris" : parent)
), DARK_GREEN));
return text("" + IrisLanguage.plain(DirectorHelpMessages.BACK), BACK).withStyle((style) -> style
.withClickEvent(new ClickEvent.RunCommand(command))
.withHoverEvent(new HoverEvent.ShowText(hover)));
}
@@ -239,7 +250,7 @@ final class ModdedCommandHelp {
private static MutableComponent nodes(Entry entry) {
if (entry.group()) {
return text(" - Category of Commands", CATEGORY);
return text(" - " + IrisLanguage.plain(DirectorHelpMessages.COMMAND_GROUP), CATEGORY);
}
List<String> tokens = usageTokens(entry.usage());
@@ -273,18 +284,18 @@ final class ModdedCommandHelp {
hover.append(text(name, PARAMETER));
hover.append(Component.literal("\n"));
hover.append(text("", DESCRIPTION_ICON));
hover.append(text("Command parameter", DESCRIPTION));
hover.append(text(IrisLanguage.plain(IrisMessages.MODDED_HELP_COMMAND_PARAMETER), DESCRIPTION));
hover.append(Component.literal("\n"));
if (required) {
hover.append(text("", REQUIRED));
hover.append(text("This parameter is required.", REQUIRED_TEXT));
hover.append(text(IrisLanguage.plain(IrisMessages.MODDED_HELP_PARAMETER_REQUIRED), REQUIRED_TEXT));
} else {
hover.append(text("", DESCRIPTION_ICON));
hover.append(text("This parameter is optional.", USAGE));
hover.append(text(IrisLanguage.plain(IrisMessages.MODDED_HELP_PARAMETER_OPTIONAL), USAGE));
}
hover.append(Component.literal("\n"));
hover.append(text("", DARK_GREEN));
hover.append(text("This parameter is read as text by Brigadier.", HOVER_TYPE));
hover.append(text(IrisLanguage.plain(IrisMessages.MODDED_HELP_BRIGADIER_TEXT), HOVER_TYPE));
return title.withStyle((style) -> style.withHoverEvent(new HoverEvent.ShowText(hover)));
}
@@ -294,15 +305,15 @@ final class ModdedCommandHelp {
hover.append(text(names(entry), PARAMETER));
hover.append(Component.literal("\n"));
hover.append(text("", DESCRIPTION_ICON));
hover.append(text(entry.description(), DESCRIPTION));
hover.append(text(IrisLanguage.plain(entry.description()), DESCRIPTION));
hover.append(Component.literal("\n"));
hover.append(text("", USAGE_ICON));
if (entry.group()) {
hover.append(text("This is a command category. Click to run.", USAGE));
hover.append(text(IrisLanguage.plain(DirectorHelpMessages.COMMAND_GROUP), USAGE));
} else if (entry.usage().isBlank()) {
hover.append(text("There are no parameters. Click to type command.", USAGE));
hover.append(text(IrisLanguage.plain(DirectorHelpMessages.NO_PARAMETERS), USAGE));
} else {
hover.append(text("Hover over all of the parameters to learn more.", USAGE));
hover.append(text(IrisLanguage.plain(DirectorHelpMessages.PARAMETERS), USAGE));
hover.append(Component.literal("\n"));
hover.append(text("", EXAMPLE_ICON));
hover.append(text(suggestion, PARAMETER));
@@ -311,7 +322,7 @@ final class ModdedCommandHelp {
String parent = path.isEmpty() ? "/iris" : "/iris " + path;
if (entry.aliases().length > 0) {
hover.append(Component.literal("\n"));
hover.append(text("Aliases: ", DARK_GREEN));
hover.append(text(IrisLanguage.plain(DirectorHelpMessages.ALIASES) + ": ", DARK_GREEN));
List<String> aliases = new ArrayList<>(entry.aliases().length);
for (String alias : entry.aliases()) {
aliases.add(parent + " " + alias);
@@ -324,11 +335,11 @@ final class ModdedCommandHelp {
private static MutableComponent opNotice() {
MutableComponent notice = Component.empty();
notice.append(text("", REQUIRED));
notice.append(text("Iris commands need operator permission (level 2). ", REQUIRED_TEXT));
notice.append(text("Run ", DESCRIPTION));
notice.append(text("/op <you>", PARAMETER_ALT));
notice.append(text(" from the console (or enable cheats in singleplayer); ", DESCRIPTION));
notice.append(text("until then these commands will not run or tab-complete.", USAGE));
notice.append(text(IrisLanguage.plain(IrisMessages.MODDED_HELP_OPERATOR_NOTICE) + " ", REQUIRED_TEXT));
notice.append(text(IrisLanguage.plain(
IrisMessages.MODDED_HELP_OPERATOR_INSTRUCTION,
MessageArgument.trusted("command", "/op <you>")
), DESCRIPTION));
return notice;
}
@@ -404,12 +415,12 @@ final class ModdedCommandHelp {
return normalized;
}
private record Entry(String name, String usage, String description, boolean group, String... aliases) {
static Entry command(String name, String usage, String description, String... aliases) {
private record Entry(String name, String usage, TextKey description, boolean group, String... aliases) {
static Entry command(String name, String usage, TextKey description, String... aliases) {
return new Entry(name, usage, description, false, aliases);
}
static Entry group(String name, String description, String... aliases) {
static Entry group(String name, TextKey description, String... aliases) {
return new Entry(name, "", description, true, aliases);
}
}
@@ -45,6 +45,10 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.function.Predicate;
import art.arcane.iris.core.localization.IrisLanguage;
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<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
@@ -118,8 +122,7 @@ public final class ModdedDatapackCommands {
Engine engine = IrisModdedCommands.engineFor(level);
if (engine == null || engine.getDimension() == null) {
IrisModdedCommands.ok(source, dimensionId + ": type=" + typeKey + " active=" + activeMin + ".." + activeMax
+ " logical=" + active.logicalHeight() + " (pack=" + irisGenerator.dimensionKey() + ", engine not started; pack heights unknown)");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_TYPE_ACTIVE_LOGICAL_PACK_ENGINE_NOT_STARTED_PACK_HEIGHTS_UNKNOWN, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("typeKey", typeKey), MessageArgument.untrusted("activeMin", activeMin), MessageArgument.untrusted("activeMax", activeMax), MessageArgument.untrusted("value", active.logicalHeight()), MessageArgument.untrusted("value2", irisGenerator.dimensionKey())));
continue;
}
IrisDimension dimension = engine.getDimension();
@@ -128,22 +131,18 @@ public final class ModdedDatapackCommands {
int packLogical = dimension.getLogicalHeight();
boolean matches = packMin == activeMin && packMax == activeMax && packLogical == active.logicalHeight();
File override = overrideFile(server, irisGenerator.dimensionKey());
IrisModdedCommands.ok(source, dimensionId + ": type=" + typeKey
+ " active=" + activeMin + ".." + activeMax + " logical=" + active.logicalHeight()
+ " | pack '" + dimension.getLoadKey() + "' wants " + packMin + ".." + packMax + " logical=" + packLogical
+ " | " + (matches ? "MATCH" : "MISMATCH")
+ (override.isFile() ? " (world datapack override installed)" : ""));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_TYPE_ACTIVE_LOGICAL_PACK_WANTS_LOGICAL, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("typeKey", typeKey), MessageArgument.untrusted("activeMin", activeMin), MessageArgument.untrusted("activeMax", activeMax), MessageArgument.untrusted("value", active.logicalHeight()), MessageArgument.untrusted("value2", dimension.getLoadKey()), MessageArgument.untrusted("packMin", packMin), MessageArgument.untrusted("packMax", packMax), MessageArgument.untrusted("packLogical", packLogical), MessageArgument.trusted("value3", IrisLanguage.plain(matches ? RuntimeUiMessages.STATUS_MATCH : RuntimeUiMessages.STATUS_MISMATCH)), MessageArgument.trusted("value4", override.isFile() ? IrisLanguage.plain(RuntimeUiMessages.DATAPACK_OVERRIDE_SUFFIX) : "")));
if (!matches) {
mismatches++;
IrisModdedCommands.fail(source, " WARNING: the active dimension type does not match the pack. Iris will refuse to start this world engine instead of clipping terrain. Run /iris world enable <dimension> <pack> for new worlds, or /iris datapack install for already-loaded Iris dimensions, then restart.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_WARNING_ACTIVE_DIMENSION_TYPE_DOES_NOT_MATCH_PACK_IRIS_WILL));
}
}
if (irisLevels == 0) {
IrisModdedCommands.fail(source, "No Iris dimensions are loaded.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_NO_IRIS_DIMENSIONS_ARE_LOADED));
return 0;
}
if (mismatches == 0) {
IrisModdedCommands.ok(source, "All " + irisLevels + " Iris dimension(s) match their pack height ranges.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_ALL_IRIS_DIMENSION_S_MATCH_THEIR_PACK_HEIGHT_RANGES, MessageArgument.untrusted("irisLevels", irisLevels)));
}
return 1;
}
@@ -157,7 +156,7 @@ public final class ModdedDatapackCommands {
}
Engine engine = IrisModdedCommands.engineFor(level);
if (engine == null || engine.getDimension() == null) {
IrisModdedCommands.fail(source, level.dimension().identifier() + ": engine not started; cannot derive its dimension type yet.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_ENGINE_NOT_STARTED_CANNOT_DERIVE_ITS_DIMENSION_TYPE_YET, MessageArgument.untrusted("value", level.dimension().identifier())));
continue;
}
IrisDimension dimension = engine.getDimension();
@@ -166,7 +165,7 @@ public final class ModdedDatapackCommands {
json = dimension.getDimensionType().toJson(DataVersion.getLatest().get());
} catch (Throwable e) {
LOGGER.error("Iris dimension type generation failed for {}", dimension.getLoadKey(), e);
IrisModdedCommands.fail(source, level.dimension().identifier() + ": dimension type generation failed: " + e.getMessage());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_DIMENSION_TYPE_GENERATION_FAILED, MessageArgument.untrusted("value", level.dimension().identifier()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage()))));
continue;
}
File output = overrideFile(server, irisGenerator.dimensionKey());
@@ -176,11 +175,11 @@ public final class ModdedDatapackCommands {
written.add(output.getPath());
} catch (IOException e) {
LOGGER.error("Iris dimension type write failed for {}", output, e);
IrisModdedCommands.fail(source, "Failed to write " + output + ": " + e.getMessage());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_FAILED_WRITE, MessageArgument.untrusted("output", output), MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
}
}
if (written.isEmpty()) {
IrisModdedCommands.fail(source, "No Iris dimensions with running engines found; nothing was installed.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_NO_IRIS_DIMENSIONS_WITH_RUNNING_ENGINES_FOUND_NOTHING_WAS_INSTALLED));
return 0;
}
@@ -200,14 +199,14 @@ public final class ModdedDatapackCommands {
written.add(mcmeta.getPath());
} catch (IOException e) {
LOGGER.error("Iris pack.mcmeta write failed for {}", mcmeta, e);
IrisModdedCommands.fail(source, "Failed to write " + mcmeta + ": " + e.getMessage());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_FAILED_WRITE_2, MessageArgument.untrusted("mcmeta", mcmeta), MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
return 0;
}
for (String path : written) {
IrisModdedCommands.ok(source, "Wrote " + path);
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_WROTE, MessageArgument.untrusted("path", path)));
}
IrisModdedCommands.ok(source, "World datapack '" + WORLD_PACK_NAME + "' dimension type overrides installed. Restart the server for the dimension types to apply.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_WORLD_DATAPACK_DIMENSION_TYPE_OVERRIDES_INSTALLED_RESTART_SERVER_DIMENSION_TYPES, MessageArgument.untrusted("WORLDPACKNAME", WORLD_PACK_NAME)));
return 1;
}
@@ -239,12 +238,12 @@ public final class ModdedDatapackCommands {
}
}
IrisModdedCommands.ok(source, "Configured datapack imports: " + configured.size());
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_CONFIGURED_DATAPACK_IMPORTS, MessageArgument.untrusted("value", configured.size())));
for (String url : configured) {
IrisModdedCommands.ok(source, " - " + url);
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_MESSAGE, MessageArgument.untrusted("url", url)));
}
if (!configured.isEmpty()) {
IrisModdedCommands.ok(source, "Modrinth ingest is Bukkit-only; install these manually into world/datapacks if needed.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_MODRINTH_INGEST_IS_BUKKIT_ONLY_INSTALL_THESE_MANUALLY_INTO_WORLD));
}
File datapacks = worldDatapacksFolder(server);
@@ -257,9 +256,9 @@ public final class ModdedDatapackCommands {
}
}
}
IrisModdedCommands.ok(source, "Installed world datapacks: " + names.size());
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_INSTALLED_WORLD_DATAPACKS, MessageArgument.untrusted("value", names.size())));
for (String name : names) {
IrisModdedCommands.ok(source, " - " + name);
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DATAPACK_COMMANDS_MESSAGE_2, MessageArgument.untrusted("name", name)));
}
return 1;
}
@@ -33,6 +33,9 @@ import java.util.Collections;
import java.util.Enumeration;
import java.util.function.Predicate;
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<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
@@ -53,7 +56,7 @@ final class ModdedDeveloperCommands {
private static int sentry(CommandSourceStack source) {
IrisPlatforms.get().reportError(new Exception("This is an Iris Sentry test exception"));
ModdedCommandFeedback.ok(source, "Dispatched a test exception to the Iris error reporter (Sentry if enabled).");
ModdedCommandFeedback.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DEVELOPER_COMMANDS_DISPATCHED_TEST_EXCEPTION_IRIS_ERROR_REPORTER_SENTRY_IF_ENABLED));
return 1;
}
@@ -63,13 +66,13 @@ final class ModdedDeveloperCommands {
for (NetworkInterface networkInterface : Collections.list(interfaces)) {
ModdedCommandFeedback.ok(source, networkInterface.getDisplayName());
for (InetAddress address : Collections.list(networkInterface.getInetAddresses())) {
ModdedCommandFeedback.ok(source, " " + address.getHostAddress());
ModdedCommandFeedback.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DEVELOPER_COMMANDS_MESSAGE, MessageArgument.untrusted("value", address.getHostAddress())));
}
}
return 1;
} catch (SocketException error) {
LOGGER.error("Iris developer network dump failed", error);
ModdedCommandFeedback.fail(source, "Network scan failed: " + error.getClass().getSimpleName());
ModdedCommandFeedback.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_DEVELOPER_COMMANDS_NETWORK_SCAN_FAILED, MessageArgument.untrusted("value", error.getClass().getSimpleName())));
return 0;
}
}
@@ -18,10 +18,13 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.modded.ModdedBlockState;
import art.arcane.volmlib.util.localization.MessageArgument;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.DustParticleOptions;
import net.minecraft.network.chat.Component;
@@ -50,7 +53,7 @@ public final class ModdedDustRevealer {
public static void reveal(ServerPlayer player, ServerLevel level, BlockPos pos) {
Engine engine = IrisModdedCommands.engineFor(level);
if (engine == null) {
player.sendSystemMessage(Component.literal("This dimension is not generated by Iris."));
player.sendSystemMessage(Component.literal(IrisLanguage.plain(RuntimeUiMessages.DUST_IRIS_WORLD_REQUIRED)));
return;
}
describe(player, level, engine, pos);
@@ -60,7 +63,10 @@ public final class ModdedDustRevealer {
if (key == null) {
return;
}
player.sendSystemMessage(Component.literal("Found object " + key));
player.sendSystemMessage(Component.literal(IrisLanguage.plain(
RuntimeUiMessages.DUST_FOUND_OBJECT,
MessageArgument.untrusted("object", key)
)));
MinecraftServer server = level.getServer();
BlockPos origin = pos.immutable();
Thread thread = new Thread(() -> {
@@ -71,8 +77,11 @@ public final class ModdedDustRevealer {
hit.getX() + 0.5D, hit.getY() + 0.5D, hit.getZ() + 0.5D,
3, 0.25D, 0.25D, 0.25D, 0.0D);
}
player.sendSystemMessage(Component.literal("Revealed " + hits.size() + " block(s) of " + key
+ (hits.size() >= MAX_HITS ? " (capped)" : "")));
player.sendSystemMessage(Component.literal(IrisLanguage.plain(
hits.size() >= MAX_HITS ? RuntimeUiMessages.DUST_REVEALED_CAPPED : RuntimeUiMessages.DUST_REVEALED,
MessageArgument.trusted("count", hits.size()),
MessageArgument.untrusted("object", key)
)));
});
}, "Iris Dust Revealer");
thread.setDaemon(true);
@@ -133,31 +142,72 @@ public final class ModdedDustRevealer {
IrisRegion region = safe(() -> engine.getRegion(x, z));
List<String> lines = new ArrayList<>();
lines.add("--- Iris Dust @ " + x + ", " + y + ", " + z + " ---");
lines.add("Block: " + ModdedBlockState.serialize(level.getBlockState(pos)));
lines.add(IrisLanguage.plain(
RuntimeUiMessages.DUST_HEADER,
MessageArgument.trusted("x", x),
MessageArgument.trusted("y", y),
MessageArgument.trusted("z", z)
));
lines.add(IrisLanguage.plain(
RuntimeUiMessages.DUST_BLOCK,
MessageArgument.untrusted("block", ModdedBlockState.serialize(level.getBlockState(pos)))
));
if (offset > 0) {
lines.add("Position: +" + offset + " ABOVE surface (surface Y=" + surfaceY + ")");
lines.add(IrisLanguage.plain(
RuntimeUiMessages.DUST_POSITION_ABOVE,
MessageArgument.trusted("offset", offset),
MessageArgument.trusted("surfaceY", surfaceY)
));
} else if (offset < 0) {
lines.add("Position: " + (-offset) + " below surface (surface Y=" + surfaceY + ")");
lines.add(IrisLanguage.plain(
RuntimeUiMessages.DUST_POSITION_BELOW,
MessageArgument.trusted("offset", -offset),
MessageArgument.trusted("surfaceY", surfaceY)
));
} else {
lines.add("Position: at surface (Y=" + surfaceY + ")");
lines.add(IrisLanguage.plain(
RuntimeUiMessages.DUST_POSITION_AT,
MessageArgument.trusted("surfaceY", surfaceY)
));
}
lines.add("Object @block: " + (objectKey == null ? "none" : objectKey));
lines.add(IrisLanguage.plain(
RuntimeUiMessages.DUST_OBJECT_AT_BLOCK,
MessageArgument.untrusted(
"object",
objectKey == null ? IrisLanguage.plain(RuntimeUiMessages.DUST_NONE) : objectKey
)
));
if (surfaceBiome != null) {
lines.add("Surface biome: " + surfaceBiome.getLoadKey());
lines.add(IrisLanguage.plain(
RuntimeUiMessages.DUST_SURFACE_BIOME,
MessageArgument.untrusted("biome", surfaceBiome.getLoadKey())
));
}
if (biomeHere != null && (surfaceBiome == null || !biomeHere.getLoadKey().equals(surfaceBiome.getLoadKey()))) {
lines.add("Biome @Y: " + biomeHere.getLoadKey());
lines.add(IrisLanguage.plain(
RuntimeUiMessages.DUST_BIOME_AT_Y,
MessageArgument.untrusted("biome", biomeHere.getLoadKey())
));
}
if (caveBiome != null && (surfaceBiome == null || !caveBiome.getLoadKey().equals(surfaceBiome.getLoadKey()))) {
lines.add("Cave/Mantle biome: " + caveBiome.getLoadKey());
lines.add(IrisLanguage.plain(
RuntimeUiMessages.DUST_CAVE_BIOME,
MessageArgument.untrusted("biome", caveBiome.getLoadKey())
));
}
if (region != null) {
lines.add("Region: " + region.getLoadKey() + " (" + region.getName() + ")");
lines.add(IrisLanguage.plain(
RuntimeUiMessages.DUST_REGION,
MessageArgument.untrusted("region", region.getLoadKey()),
MessageArgument.untrusted("name", region.getName())
));
}
Set<String> objects = safe(() -> engine.getObjectsAt(x >> 4, z >> 4));
if (objects != null && !objects.isEmpty()) {
lines.add("Objects in chunk: " + objects);
lines.add(IrisLanguage.plain(
RuntimeUiMessages.DUST_OBJECTS_IN_CHUNK,
MessageArgument.untrusted("objects", objects)
));
}
for (String line : lines) {
player.sendSystemMessage(Component.literal(line));
@@ -36,6 +36,10 @@ import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.ModdedCommandMessages;
import art.arcane.iris.core.localization.RuntimeProgressMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
public final class ModdedGoldenHash {
public enum Mode {
AUTO,
@@ -80,13 +84,18 @@ public final class ModdedGoldenHash {
public static void start(CommandSourceStack source, ServerLevel level, Engine engine, int radius, int threads, Mode mode) {
if (!ACTIVE.compareAndSet(false, true)) {
IrisModdedCommands.fail(source, "A goldenhash scan is already running.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_GOLDEN_HASH_GOLDENHASH_SCAN_IS_ALREADY_RUNNING));
return;
}
ModdedGoldenHash scan = new ModdedGoldenHash(source, level, engine, radius, threads, mode);
int boundedRadius = Math.max(0, radius);
int chunks = (boundedRadius * 2 + 1) * (boundedRadius * 2 + 1);
scan.ok("GoldenHash started: " + chunks + " chunk(s) around 0,0 in buffers (world untouched), threads=" + Math.max(1, threads) + " mode=" + mode);
scan.ok(IrisLanguage.plain(
RuntimeProgressMessages.GOLDEN_STARTED,
MessageArgument.trusted("chunks", chunks),
MessageArgument.trusted("threads", Math.max(1, threads)),
MessageArgument.untrusted("mode", mode)
));
LOGGER.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(() -> {
@@ -161,14 +170,25 @@ public final class ModdedGoldenHash {
int doneCount = hashed.incrementAndGet();
int stride = total <= 64 ? 1 : 32;
if (doneCount % stride == 0 || doneCount == total) {
ModdedGoldenHash.this.ok("[" + doneCount + "/" + total + "] chunk " + chunkX + "," + chunkZ + " hashed");
ModdedGoldenHash.this.ok(IrisLanguage.plain(
RuntimeProgressMessages.GOLDEN_CHUNK_HASHED,
MessageArgument.trusted("done", doneCount),
MessageArgument.trusted("total", total),
MessageArgument.trusted("x", chunkX),
MessageArgument.trusted("z", chunkZ)
));
}
}
@Override
public void chunkFailed(int chunkX, int chunkZ, Throwable error) {
LOGGER.error("goldenhash chunk {},{} failed", chunkX, chunkZ, error);
ModdedGoldenHash.this.fail("Chunk " + chunkX + "," + chunkZ + " FAILED: " + error.getClass().getSimpleName());
ModdedGoldenHash.this.fail(IrisLanguage.plain(
RuntimeProgressMessages.GOLDEN_CHUNK_FAILED,
MessageArgument.trusted("x", chunkX),
MessageArgument.trusted("z", chunkZ),
MessageArgument.untrusted("type", error.getClass().getSimpleName())
));
}
};
}
@@ -69,6 +69,10 @@ import java.util.Map;
import java.util.UUID;
import java.util.function.Predicate;
import art.arcane.iris.core.localization.IrisLanguage;
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<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
@@ -218,55 +222,55 @@ public final class ModdedObjectCommands {
public static int giveWand(CommandSourceStack source) {
ServerPlayer player = source.getPlayer();
if (player == null) {
IrisModdedCommands.fail(source, "This command can only be used by players. (the wand is a physical item)");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_WAND_IS));
return 0;
}
if (!player.getInventory().add(ModdedWandService.createWand())) {
IrisModdedCommands.fail(source, "Your inventory is full.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_YOUR_INVENTORY_IS_FULL));
return 0;
}
IrisModdedCommands.ok(source, "Poof! Good luck building! (left click = corner 1, right click = corner 2)");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_POOF_GOOD_LUCK_BUILDING_LEFT_CLICK_CORNER_1_RIGHT_CLICK));
return 1;
}
private static int giveDust(CommandSourceStack source) {
ServerPlayer player = source.getPlayer();
if (player == null) {
IrisModdedCommands.fail(source, "This command can only be used by players. (the dust is a physical item)");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_DUST_IS));
return 0;
}
if (!player.getInventory().add(ModdedWandService.createDust())) {
IrisModdedCommands.fail(source, "Your inventory is full.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_YOUR_INVENTORY_IS_FULL_2));
return 0;
}
IrisModdedCommands.ok(source, "Right click a block to reveal the object it belongs to.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_RIGHT_CLICK_BLOCK_REVEAL_OBJECT_IT_BELONGS));
return 1;
}
private static int save(CommandSourceStack source, String nameRaw, boolean overwrite) {
ServerPlayer player = source.getPlayer();
if (player == null) {
IrisModdedCommands.fail(source, "This command can only be used by players. (saving captures your wand selection)");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_SAVING_CAPTURES));
return 0;
}
ServerLevel level = player.level();
Engine engine = IrisModdedCommands.engineFor(level);
if (engine == null) {
IrisModdedCommands.fail(source, "This dimension is not generated by Iris; objects save into the dimension's pack.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_OBJECTS_SAVE_INTO));
return 0;
}
if (!ModdedWandService.isHoldingWand(player)) {
IrisModdedCommands.fail(source, "Hold your Iris wand. (/iris wand)");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_HOLD_YOUR_IRIS_WAND_IRIS_WAND));
return 0;
}
ModdedWandService.Selection selection = ModdedWandService.selection(player);
if (selection == null) {
IrisModdedCommands.fail(source, "No area selected. Left/right click blocks with the wand first.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_NO_AREA_SELECTED_LEFT_RIGHT_CLICK_BLOCKS_WITH_WAND_FIRST));
return 0;
}
String name = nameRaw.trim().replace('\\', '/');
if (name.isEmpty() || name.contains("..")) {
IrisModdedCommands.fail(source, "Invalid object name: " + nameRaw);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_INVALID_OBJECT_NAME, MessageArgument.untrusted("nameRaw", nameRaw)));
return 0;
}
BlockPos min = selection.min();
@@ -276,12 +280,12 @@ public final class ModdedObjectCommands {
int d = max.getZ() - min.getZ() + 1;
long volume = (long) w * h * d;
if (volume > MAX_SAVE_VOLUME) {
IrisModdedCommands.fail(source, "Selection too large: " + volume + " blocks (max " + MAX_SAVE_VOLUME + ").");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_SELECTION_TOO_LARGE_BLOCKS_MAX, MessageArgument.untrusted("volume", volume), MessageArgument.untrusted("MAXSAVEVOLUME", MAX_SAVE_VOLUME)));
return 0;
}
File file = new File(engine.getData().getDataFolder(), "objects" + File.separator + name.replace('/', File.separatorChar) + ".iob");
if (file.exists() && !overwrite) {
IrisModdedCommands.fail(source, "File already exists. Use /iris object save overwrite " + name);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_FILE_ALREADY_EXISTS_USE_IRIS_OBJECT_SAVE_OVERWRITE, MessageArgument.untrusted("name", name)));
return 0;
}
int[] tilesSkipped = {0};
@@ -295,7 +299,7 @@ public final class ModdedObjectCommands {
object.write(file);
} catch (IOException e) {
LOGGER.error("Iris object save failed for {}", file.getAbsolutePath(), e);
IrisModdedCommands.fail(source, "Failed to save object: " + e.getMessage());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_FAILED_SAVE_OBJECT, MessageArgument.untrusted("value", String.valueOf(e.getMessage()))));
return 0;
}
StringBuilder tileNote = new StringBuilder();
@@ -308,8 +312,7 @@ public final class ModdedObjectCommands {
} else if (tilesSkipped[0] > 0) {
tileNote.append(" (").append(tilesSkipped[0]).append(" tile state(s) could not be captured)");
}
IrisModdedCommands.ok(source, "Saved " + engine.getData().getDataFolder().getName() + "/objects/" + name + ".iob: "
+ w + "x" + h + "x" + d + ", " + object.getBlocks().size() + " block(s)" + tileNote);
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());
return 1;
}
@@ -371,7 +374,7 @@ public final class ModdedObjectCommands {
LOGGER.error("Iris object load failed for {}", key, e);
}
if (object == null || object.getBlocks().size() == 0) {
IrisModdedCommands.fail(source, "Unknown or empty object: " + key);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_UNKNOWN_EMPTY_OBJECT, MessageArgument.untrusted("key", key)));
return 0;
}
@@ -379,12 +382,12 @@ public final class ModdedObjectCommands {
BlockPos target = at;
if (target == null) {
if (player == null) {
IrisModdedCommands.fail(source, "Console must specify coordinates: /iris object paste at <x> <y> <z> [rotate <degrees>] " + key);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_CONSOLE_MUST_SPECIFY_COORDINATES_IRIS_OBJECT_PASTE_AT_X_Y, MessageArgument.untrusted("key", key)));
return 0;
}
HitResult hit = player.pick(TARGET_RANGE, 1.0F, false);
if (hit.getType() != HitResult.Type.BLOCK || !(hit instanceof BlockHitResult blockHit)) {
IrisModdedCommands.fail(source, "You are not looking at a block within " + (int) TARGET_RANGE + " blocks.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_YOU_ARE_NOT_LOOKING_AT_BLOCK_WITHIN_BLOCKS, MessageArgument.untrusted("value", (int) TARGET_RANGE)));
return 0;
}
target = blockHit.getBlockPos().above();
@@ -398,14 +401,13 @@ public final class ModdedObjectCommands {
} catch (Throwable e) {
LOGGER.error("Iris paste failed for {}", key, e);
ModdedObjectUndo.record(player == null ? ModdedObjectUndo.CONSOLE : player.getUUID(), level, placer.undoSnapshot());
IrisModdedCommands.fail(source, "Paste failed: " + e.getClass().getSimpleName() + " (partial changes recorded for undo)");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_PASTE_FAILED_PARTIAL_CHANGES_RECORDED_UNDO, MessageArgument.untrusted("value", e.getClass().getSimpleName())));
return 0;
}
UUID owner = player == null ? ModdedObjectUndo.CONSOLE : player.getUUID();
ModdedObjectUndo.record(owner, level, placer.undoSnapshot());
String tileNote = tileNote(placer);
IrisModdedCommands.ok(source, "Placed " + key + " at " + target.getX() + " " + target.getY() + " " + target.getZ()
+ " rot=" + rotation + " (" + placer.writes() + " write(s), " + placer.nonAirWrites() + " non-air" + tileNote + ")");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_PLACED_AT_ROT_WRITE_S_NON_AIR, MessageArgument.untrusted("key", key), MessageArgument.untrusted("value", target.getX()), MessageArgument.untrusted("value2", target.getY()), MessageArgument.untrusted("value3", target.getZ()), MessageArgument.untrusted("rotation", rotation), MessageArgument.untrusted("value4", placer.writes()), MessageArgument.untrusted("value5", placer.nonAirWrites()), MessageArgument.untrusted("tileNote", tileNote)));
LOGGER.info("Iris paste: {} at {},{},{} rot={} writes={} nonAir={} tilesRestored={} tilesSkipped={}",
key, target.getX(), target.getY(), target.getZ(), rotation, placer.writes(), placer.nonAirWrites(), placer.restoredTiles(), placer.skippedTiles());
return placer.writes() > 0 ? 1 : 0;
@@ -414,16 +416,16 @@ public final class ModdedObjectCommands {
private static int resize(CommandSourceStack source, int amount, ResizeOp op) {
ServerPlayer player = source.getPlayer();
if (player == null) {
IrisModdedCommands.fail(source, "This command can only be used by players.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS));
return 0;
}
if (!ModdedWandService.isHoldingWand(player)) {
IrisModdedCommands.fail(source, "Hold your Iris wand. (/iris wand)");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_HOLD_YOUR_IRIS_WAND_IRIS_WAND_2));
return 0;
}
ModdedWandService.Selection selection = ModdedWandService.selection(player);
if (selection == null) {
IrisModdedCommands.fail(source, "No area selected.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_NO_AREA_SELECTED));
return 0;
}
Direction direction = Direction.getApproximateNearest(player.getLookAngle());
@@ -458,26 +460,25 @@ public final class ModdedObjectCommands {
BlockPos first = new BlockPos(mins[0], mins[1], mins[2]);
BlockPos second = new BlockPos(maxs[0], maxs[1], maxs[2]);
ModdedWandService.setSelection(player, first, second);
IrisModdedCommands.ok(source, op.name().toLowerCase(Locale.ROOT) + " " + amount + " " + direction.getName()
+ ": " + describe(first, second));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_MESSAGE, MessageArgument.untrusted("value", op.name().toLowerCase(Locale.ROOT)), MessageArgument.untrusted("amount", amount), MessageArgument.untrusted("value2", direction.getName()), MessageArgument.untrusted("value3", describe(first, second))));
return 1;
}
private static int position(CommandSourceStack source, boolean first, boolean look) {
ServerPlayer player = source.getPlayer();
if (player == null) {
IrisModdedCommands.fail(source, "This command can only be used by players.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_2));
return 0;
}
if (!ModdedWandService.isHoldingWand(player)) {
IrisModdedCommands.fail(source, "Ready your wand. (/iris wand)");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_READY_YOUR_WAND_IRIS_WAND));
return 0;
}
BlockPos pos;
if (look) {
HitResult hit = player.pick(TARGET_RANGE, 1.0F, false);
if (hit.getType() != HitResult.Type.BLOCK || !(hit instanceof BlockHitResult blockHit)) {
IrisModdedCommands.fail(source, "You are not looking at a block.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_YOU_ARE_NOT_LOOKING_AT_BLOCK));
return 0;
}
pos = blockHit.getBlockPos();
@@ -492,23 +493,23 @@ public final class ModdedObjectCommands {
} else {
ModdedWandService.setSelection(player, fallback, pos);
}
IrisModdedCommands.ok(source, "Position " + (first ? 1 : 2) + " set to " + pos.getX() + " " + pos.getY() + " " + pos.getZ());
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_POSITION_SET, MessageArgument.untrusted("value", (first ? 1 : 2)), MessageArgument.untrusted("value2", pos.getX()), MessageArgument.untrusted("value3", pos.getY()), MessageArgument.untrusted("value4", pos.getZ())));
return 1;
}
private static int autoSelect(CommandSourceStack source, boolean down) {
ServerPlayer player = source.getPlayer();
if (player == null) {
IrisModdedCommands.fail(source, "This command can only be used by players.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_3));
return 0;
}
if (!ModdedWandService.isHoldingWand(player)) {
IrisModdedCommands.fail(source, "Hold your wand!");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_HOLD_YOUR_WAND));
return 0;
}
ModdedWandService.Selection selection = ModdedWandService.selection(player);
if (selection == null) {
IrisModdedCommands.fail(source, "No area selected.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_NO_AREA_SELECTED_2));
return 0;
}
ServerLevel level = player.level();
@@ -516,7 +517,7 @@ public final class ModdedObjectCommands {
BlockPos max = selection.max();
long volume = (long) (max.getX() - min.getX() + 1) * (max.getY() - min.getY() + 1) * (max.getZ() - min.getZ() + 1);
if (volume > MAX_AUTOSELECT_VOLUME) {
IrisModdedCommands.fail(source, "Selection too large for auto-select: " + volume + " blocks (max " + MAX_AUTOSELECT_VOLUME + ").");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_SELECTION_TOO_LARGE_AUTO_SELECT_BLOCKS_MAX, MessageArgument.untrusted("volume", volume), MessageArgument.untrusted("MAXAUTOSELECTVOLUME", MAX_AUTOSELECT_VOLUME)));
return 0;
}
int levelMinY = level.getMinY();
@@ -561,7 +562,7 @@ public final class ModdedObjectCommands {
BlockPos first = new BlockPos(minX, bottomY, minZ);
BlockPos second = new BlockPos(maxX, topMaxY, maxZ);
ModdedWandService.setSelection(player, first, second);
IrisModdedCommands.ok(source, "Auto-select complete: " + describe(first, second));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_AUTO_SELECT_COMPLETE, MessageArgument.untrusted("value", describe(first, second))));
return 1;
}
@@ -590,11 +591,11 @@ public final class ModdedObjectCommands {
LOGGER.error("Iris object load failed for {}", key, e);
}
if (object == null) {
IrisModdedCommands.fail(source, "Unknown object: " + key);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_UNKNOWN_OBJECT, MessageArgument.untrusted("key", key)));
return 0;
}
IrisModdedCommands.ok(source, "Object Size: " + object.getW() + " * " + object.getH() + " * " + object.getD());
IrisModdedCommands.ok(source, "Blocks Used: " + object.getBlocks().size());
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_OBJECT_SIZE, MessageArgument.untrusted("value", object.getW()), MessageArgument.untrusted("value2", object.getH()), MessageArgument.untrusted("value3", object.getD())));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_BLOCKS_USED, MessageArgument.untrusted("value", object.getBlocks().size())));
Map<String, Integer> counts = new HashMap<>();
Iterator<PlatformBlockState> values = object.getBlocks().values();
while (values.hasNext()) {
@@ -603,15 +604,15 @@ public final class ModdedObjectCommands {
}
List<Map.Entry<String, Integer>> sorted = new ArrayList<>(counts.entrySet());
sorted.sort(Comparator.comparingInt((Map.Entry<String, Integer> entry) -> entry.getValue()).reversed());
IrisModdedCommands.ok(source, "== Blocks in object ==");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_BLOCKS_OBJECT));
int shown = 0;
for (Map.Entry<String, Integer> entry : sorted) {
IrisModdedCommands.ok(source, " - " + entry.getKey() + " * " + entry.getValue());
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_MESSAGE_2, MessageArgument.untrusted("value", entry.getKey()), MessageArgument.untrusted("value2", entry.getValue())));
shown++;
if (shown >= 10) {
int remaining = sorted.size() - shown;
if (remaining > 0) {
IrisModdedCommands.ok(source, " + " + remaining + " other block state(s)");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_OTHER_BLOCK_STATE_S, MessageArgument.untrusted("remaining", remaining)));
}
break;
}
@@ -630,22 +631,22 @@ public final class ModdedObjectCommands {
LOGGER.error("Iris object load failed for {}", key, e);
}
if (object == null) {
IrisModdedCommands.fail(source, "Unknown object: " + key);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_UNKNOWN_OBJECT_2, MessageArgument.untrusted("key", key)));
return 0;
}
IrisModdedCommands.ok(source, "Current Object Size: " + object.getW() + " * " + object.getH() + " * " + object.getD());
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_CURRENT_OBJECT_SIZE, MessageArgument.untrusted("value", object.getW()), MessageArgument.untrusted("value2", object.getH()), MessageArgument.untrusted("value3", object.getD())));
object.shrinkwrap();
IrisModdedCommands.ok(source, "New Object Size: " + object.getW() + " * " + object.getH() + " * " + object.getD());
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_NEW_OBJECT_SIZE, MessageArgument.untrusted("value", object.getW()), MessageArgument.untrusted("value2", object.getH()), MessageArgument.untrusted("value3", object.getD())));
File file = object.getLoadFile();
if (file == null) {
IrisModdedCommands.fail(source, "Object has no load file; cannot persist the shrink.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_OBJECT_HAS_NO_LOAD_FILE_CANNOT_PERSIST_SHRINK));
return 0;
}
try {
object.write(file);
} catch (IOException e) {
LOGGER.error("Iris object shrink save failed for {}", file.getAbsolutePath(), e);
IrisModdedCommands.fail(source, "Failed to save object " + file.getName() + ": " + e.getMessage());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_FAILED_SAVE_OBJECT_2, MessageArgument.untrusted("value", file.getName()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage()))));
return 0;
}
return 1;
@@ -667,7 +668,7 @@ public final class ModdedObjectCommands {
try {
reach = Integer.parseInt(lower.substring("reach=".length()));
} catch (NumberFormatException e) {
IrisModdedCommands.fail(source, "Invalid reach: " + token);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_INVALID_REACH, MessageArgument.untrusted("token", token)));
return 0;
}
continue;
@@ -680,16 +681,20 @@ public final class ModdedObjectCommands {
String target = targetBuilder.toString();
List<TreePlausibilizeBatch.Target> targets = TreePlausibilizeBatch.resolve(target, data);
if (targets.isEmpty()) {
IrisModdedCommands.fail(source, "No objects matched: " + target);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_NO_OBJECTS_MATCHED, MessageArgument.untrusted("target", target)));
return 0;
}
IrisModdedCommands.ok(source, "Plausibilize [reach=" + reach + (dryRun ? ", DRY" : "")
+ "] queued " + targets.size() + " object(s)");
IrisModdedCommands.ok(source, IrisLanguage.plain(
ModdedCommandMessages.MODDED_OBJECT_COMMANDS_PLAUSIBILIZE_REACH_QUEUED_OBJECT_S,
MessageArgument.trusted("reach", reach),
MessageArgument.trusted("value", dryRun ? IrisLanguage.plain(RuntimeUiMessages.TREE_DRY_SUFFIX) : ""),
MessageArgument.trusted("value2", targets.size())
));
boolean dry = dryRun;
int reachFinal = reach;
MinecraftServer server = source.getServer();
J.a(() -> TreePlausibilizeBatch.run(targets, dry, reachFinal, data, (String line) ->
server.execute(() -> IrisModdedCommands.ok(source, line))));
J.a(() -> TreePlausibilizeBatch.run(targets, dry, reachFinal, data, (TreePlausibilizeBatch.Output output) ->
server.execute(() -> IrisModdedCommands.ok(source, output.text()))));
return 1;
}
@@ -698,11 +703,11 @@ public final class ModdedObjectCommands {
UUID owner = player == null ? ModdedObjectUndo.CONSOLE : player.getUUID();
int available = ModdedObjectUndo.size(owner);
if (available == 0) {
IrisModdedCommands.fail(source, "Nothing to undo.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_NOTHING_UNDO));
return 0;
}
int reverted = ModdedObjectUndo.undo(owner, Math.min(amount, available));
IrisModdedCommands.ok(source, "Reverted " + reverted + " paste(s)!");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_OBJECT_COMMANDS_REVERTED_PASTE_S, MessageArgument.untrusted("reverted", reverted)));
return 1;
}
@@ -39,6 +39,9 @@ import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
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<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
@@ -101,7 +104,7 @@ public final class ModdedPackCommands {
private static int validate(CommandSourceStack source, String pack) {
File packsRoot = packsRoot();
if (!packsRoot.isDirectory()) {
IrisModdedCommands.fail(source, "Packs folder not found: " + packsRoot.getAbsolutePath());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_PACKS_FOLDER_NOT_FOUND, MessageArgument.untrusted("value", packsRoot.getAbsolutePath())));
return 0;
}
@@ -109,7 +112,7 @@ public final class ModdedPackCommands {
if (pack == null || pack.isBlank()) {
File[] dirs = packsRoot.listFiles(File::isDirectory);
if (dirs == null || dirs.length == 0) {
IrisModdedCommands.fail(source, "No packs to validate under " + packsRoot.getAbsolutePath());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_NO_PACKS_VALIDATE_UNDER, MessageArgument.untrusted("value", packsRoot.getAbsolutePath())));
return 0;
}
for (File dir : dirs) {
@@ -118,14 +121,14 @@ public final class ModdedPackCommands {
} else {
File target = PackDirectoryResolver.resolveExisting(packsRoot, pack);
if (target == null) {
IrisModdedCommands.fail(source, "Pack '" + pack + "' not found under " + packsRoot.getAbsolutePath());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_PACK_NOT_FOUND_UNDER, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("value", packsRoot.getAbsolutePath())));
return 0;
}
targets.add(target);
}
MinecraftServer server = source.getServer();
IrisModdedCommands.ok(source, "Validating " + targets.size() + " pack(s)...");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_VALIDATING_PACK_S, MessageArgument.untrusted("value", targets.size())));
Thread thread = new Thread(() -> {
int broken = 0;
for (File target : targets) {
@@ -138,12 +141,12 @@ public final class ModdedPackCommands {
server.execute(() -> report(source, result));
} catch (Throwable e) {
LOGGER.error("Iris pack validation failed for {}", target.getName(), e);
server.execute(() -> IrisModdedCommands.fail(source, "Validation of '" + target.getName() + "' failed: " + e.getMessage()));
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_VALIDATION_FAILED, MessageArgument.untrusted("value", target.getName()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage())))));
broken++;
}
}
int brokenTotal = broken;
server.execute(() -> IrisModdedCommands.ok(source, "Validation complete. Broken packs: " + brokenTotal + "/" + targets.size()));
server.execute(() -> IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_VALIDATION_COMPLETE_BROKEN_PACKS, MessageArgument.untrusted("brokenTotal", brokenTotal), MessageArgument.untrusted("value", targets.size()))));
}, "Iris Pack Validator");
thread.setDaemon(true);
thread.start();
@@ -153,7 +156,7 @@ public final class ModdedPackCommands {
private static int cleanup(CommandSourceStack source, String pack, boolean apply) {
File packFolder = PackDirectoryResolver.resolveExisting(packsRoot(), pack);
if (packFolder == null) {
IrisModdedCommands.fail(source, "Pack '" + pack + "' not found under " + packsRoot().getAbsolutePath());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_PACK_NOT_FOUND_UNDER_2, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("value", packsRoot().getAbsolutePath())));
return 0;
}
MinecraftServer server = source.getServer();
@@ -174,7 +177,7 @@ public final class ModdedPackCommands {
private static int restore(CommandSourceStack source, String pack, boolean apply) {
File packFolder = PackDirectoryResolver.resolveExisting(packsRoot(), pack);
if (packFolder == null) {
IrisModdedCommands.fail(source, "Pack '" + pack + "' not found under " + packsRoot().getAbsolutePath());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_PACK_NOT_FOUND_UNDER_3, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("value", packsRoot().getAbsolutePath())));
return 0;
}
MinecraftServer server = source.getServer();
@@ -196,21 +199,19 @@ public final class ModdedPackCommands {
if (pack == null || pack.isBlank()) {
Map<String, PackValidationResult> snapshot = PackValidationRegistry.snapshot();
if (snapshot.isEmpty()) {
IrisModdedCommands.fail(source, "No validation results recorded. Run /iris pack validate first.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_NO_VALIDATION_RESULTS_RECORDED_RUN_IRIS_PACK_VALIDATE_FIRST));
return 0;
}
for (Map.Entry<String, PackValidationResult> entry : snapshot.entrySet()) {
PackValidationResult result = entry.getValue();
String tag = result.isLoadable() ? "OK" : "BROKEN";
IrisModdedCommands.ok(source, tag + " " + entry.getKey()
+ " (blocking=" + result.getBlockingErrors().size()
+ ", warnings=" + result.getWarnings().size() + ")");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_BLOCKING_WARNINGS, MessageArgument.untrusted("tag", tag), MessageArgument.untrusted("value", entry.getKey()), MessageArgument.untrusted("value2", result.getBlockingErrors().size()), MessageArgument.untrusted("value3", result.getWarnings().size())));
}
return 1;
}
PackValidationResult result = PackValidationRegistry.get(pack);
if (result == null) {
IrisModdedCommands.fail(source, "No validation result for '" + pack + "'. Run /iris pack validate " + pack + ".");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_NO_VALIDATION_RESULT_RUN_IRIS_PACK_VALIDATE, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("pack2", pack)));
return 0;
}
report(source, result);
@@ -219,20 +220,19 @@ public final class ModdedPackCommands {
private static void report(CommandSourceStack source, PackValidationResult result) {
if (result.isLoadable()) {
IrisModdedCommands.ok(source, "Pack '" + result.getPackName() + "' is loadable."
+ " (warnings=" + result.getWarnings().size() + ")");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_PACK_IS_LOADABLE_WARNINGS, MessageArgument.untrusted("value", result.getPackName()), MessageArgument.untrusted("value2", result.getWarnings().size())));
} else {
IrisModdedCommands.fail(source, "Pack '" + result.getPackName() + "' is BROKEN:");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_PACK_IS_BROKEN, MessageArgument.untrusted("value", result.getPackName())));
for (String reason : result.getBlockingErrors()) {
IrisModdedCommands.fail(source, " - " + reason);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_MESSAGE, MessageArgument.untrusted("reason", reason)));
}
}
int warningMax = Math.min(10, result.getWarnings().size());
for (int i = 0; i < warningMax; i++) {
IrisModdedCommands.ok(source, " ! " + result.getWarnings().get(i));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_MESSAGE_2, MessageArgument.untrusted("value", result.getWarnings().get(i))));
}
if (result.getWarnings().size() > warningMax) {
IrisModdedCommands.ok(source, " ... and " + (result.getWarnings().size() - warningMax) + " more warning(s).");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_MORE_WARNING_S, MessageArgument.untrusted("value", (result.getWarnings().size() - warningMax))));
}
}
@@ -242,13 +242,12 @@ public final class ModdedPackCommands {
return;
}
if (!result.hasCandidates()) {
IrisModdedCommands.ok(source, "No cleanup candidates found for pack '" + pack + "'.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_NO_CLEANUP_CANDIDATES_FOUND_PACK, MessageArgument.untrusted("pack", pack)));
return;
}
IrisModdedCommands.ok(source, "Cleanup preview for pack '" + pack + "': "
+ result.candidatePaths().size() + " candidate(s). No files were changed.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_CLEANUP_PREVIEW_PACK_CANDIDATE_S_NO_FILES_WERE_CHANGED, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("value", result.candidatePaths().size())));
reportPaths(source, result.candidatePaths(), "candidate");
IrisModdedCommands.ok(source, "Run /iris pack cleanup " + pack + " apply to quarantine after a fresh scan.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_RUN_IRIS_PACK_CLEANUP_APPLY_QUARANTINE_AFTER_FRESH_SCAN, MessageArgument.untrusted("pack", pack)));
}
private static void reportCleanupApply(CommandSourceStack source, String pack, PackResourceCleanup.ApplyResult result) {
@@ -258,11 +257,10 @@ public final class ModdedPackCommands {
return;
}
if (!result.changed()) {
IrisModdedCommands.ok(source, "No cleanup candidates found for pack '" + pack + "'.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_NO_CLEANUP_CANDIDATES_FOUND_PACK_2, MessageArgument.untrusted("pack", pack)));
return;
}
IrisModdedCommands.ok(source, "Quarantined " + result.quarantinedPaths().size()
+ " cleanup candidate(s) under " + result.quarantinePath() + ".");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_QUARANTINED_CLEANUP_CANDIDATE_S_UNDER, MessageArgument.untrusted("value", result.quarantinedPaths().size()), MessageArgument.untrusted("value2", result.quarantinePath())));
reportPaths(source, result.quarantinedPaths(), "quarantined");
}
@@ -272,23 +270,22 @@ public final class ModdedPackCommands {
return;
}
if (!result.hasFiles()) {
IrisModdedCommands.ok(source, "Nothing to restore for pack '" + pack + "'.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_NOTHING_RESTORE_PACK, MessageArgument.untrusted("pack", pack)));
return;
}
IrisModdedCommands.ok(source, "Restore preview for " + result.dumpPath() + ": "
+ result.filePaths().size() + " file(s). No files were changed.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_RESTORE_PREVIEW_FILE_S_NO_FILES_WERE_CHANGED, MessageArgument.untrusted("value", result.dumpPath()), MessageArgument.untrusted("value2", result.filePaths().size())));
reportPaths(source, result.filePaths(), "file");
if (!result.conflicts().isEmpty()) {
IrisModdedCommands.fail(source, "Restore is blocked by " + result.conflicts().size() + " existing destination(s).");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_RESTORE_IS_BLOCKED_BY_EXISTING_DESTINATION_S, MessageArgument.untrusted("value", result.conflicts().size())));
reportPaths(source, result.conflicts(), "conflict");
return;
}
IrisModdedCommands.ok(source, "Run /iris pack restore " + pack + " apply to restore after a fresh conflict check.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_RUN_IRIS_PACK_RESTORE_APPLY_RESTORE_AFTER_FRESH_CONFLICT_CHECK, MessageArgument.untrusted("pack", pack)));
}
private static void reportRestoreApply(CommandSourceStack source, String pack, PackResourceCleanup.RestoreResult result) {
if (!result.conflicts().isEmpty()) {
IrisModdedCommands.fail(source, "Restore refused because " + result.conflicts().size() + " destination(s) already exist.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_RESTORE_REFUSED_BECAUSE_DESTINATION_S_ALREADY_EXIST, MessageArgument.untrusted("value", result.conflicts().size())));
reportPaths(source, result.conflicts(), "conflict");
return;
}
@@ -297,21 +294,20 @@ public final class ModdedPackCommands {
return;
}
if (!result.changed()) {
IrisModdedCommands.ok(source, "Nothing to restore for pack '" + pack + "'.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_NOTHING_RESTORE_PACK_2, MessageArgument.untrusted("pack", pack)));
return;
}
IrisModdedCommands.ok(source, "Restored " + result.restoredPaths().size()
+ " file(s) from " + result.dumpPath() + ".");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_RESTORED_FILE_S_FROM, MessageArgument.untrusted("value", result.restoredPaths().size()), MessageArgument.untrusted("value2", result.dumpPath())));
reportPaths(source, result.restoredPaths(), "restored");
}
private static void reportPaths(CommandSourceStack source, List<String> paths, String label) {
int max = Math.min(10, paths.size());
for (int i = 0; i < max; i++) {
IrisModdedCommands.ok(source, " - " + label + ": " + paths.get(i));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_MESSAGE_3, MessageArgument.untrusted("label", label), MessageArgument.untrusted("value", paths.get(i))));
}
if (paths.size() > max) {
IrisModdedCommands.ok(source, " ... and " + (paths.size() - max) + " more.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_PACK_COMMANDS_MORE, MessageArgument.untrusted("value", (paths.size() - max))));
}
}
}
@@ -1,13 +1,16 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.core.gui.PregeneratorJob;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.iris.core.protocol.IrisProtocolServer;
import art.arcane.iris.core.protocol.IrisSession;
import art.arcane.iris.spi.IrisServices;
import art.arcane.iris.spi.protocol.IrisProtocol;
import art.arcane.volmlib.util.format.Form;
import art.arcane.volmlib.util.localization.MessageArgument;
import art.arcane.volmlib.util.localization.TextKey;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerBossEvent;
import net.minecraft.server.level.ServerPlayer;
@@ -35,7 +38,12 @@ public final class ModdedPregenBossBar {
return;
}
viewer = player.getUUID();
bar = new ServerBossEvent(BAR_ID, Component.literal("Iris Pregen starting..."), BossEvent.BossBarColor.GREEN, BossEvent.BossBarOverlay.PROGRESS);
bar = new ServerBossEvent(
BAR_ID,
Component.literal(IrisLanguage.plain(RuntimeUiMessages.PREGEN_STARTING)),
BossEvent.BossBarColor.GREEN,
BossEvent.BossBarOverlay.PROGRESS
);
bar.setProgress(0.0F);
bar.addPlayer(player);
sinceUpdate = UPDATE_INTERVAL_TICKS;
@@ -73,22 +81,38 @@ public final class ModdedPregenBossBar {
}
private static Component nameFor(PregeneratorJob.PregenProgress progress) {
MutableComponent name = Component.empty();
name.append(ModdedCommandFeedback.text("Iris Pregen ", ModdedCommandFeedback.DARK_GREEN));
name.append(ModdedCommandFeedback.text(Form.f(progress.generated()) + "/" + Form.f(progress.totalChunks()), ModdedCommandFeedback.VALUE));
name.append(ModdedCommandFeedback.text(" " + String.format("%.1f", progress.percent()) + "%", ModdedCommandFeedback.USAGE));
TextKey message = progress.paused()
? RuntimeUiMessages.PREGEN_BOSSBAR_PAUSED
: RuntimeUiMessages.PREGEN_BOSSBAR_RUNNING;
if (progress.paused()) {
name.append(ModdedCommandFeedback.text(" PAUSED", ModdedCommandFeedback.REQUIRED));
return name;
return ModdedCommandFeedback.text(IrisLanguage.plain(
message,
MessageArgument.trusted("generated", Form.f(progress.generated())),
MessageArgument.trusted("total", Form.f(progress.totalChunks())),
MessageArgument.trusted("percent", String.format("%.1f", progress.percent()))
), ModdedCommandFeedback.DARK_GREEN);
}
name.append(ModdedCommandFeedback.text(" " + Form.f((int) progress.chunksPerSecond()) + "/s", ModdedCommandFeedback.VALUE));
if (progress.eta() > 0L) {
name.append(ModdedCommandFeedback.text(" ETA " + Form.duration(progress.eta(), 1), ModdedCommandFeedback.DARK_GREEN));
}
if (progress.failed() > 0L) {
name.append(ModdedCommandFeedback.text(" failed " + Form.f(progress.failed()), ModdedCommandFeedback.REQUIRED));
}
return name;
String eta = progress.eta() > 0L
? IrisLanguage.plain(
RuntimeUiMessages.PREGEN_ETA_FRAGMENT,
MessageArgument.trusted("eta", Form.duration(progress.eta(), 1))
)
: "";
String failed = progress.failed() > 0L
? IrisLanguage.plain(
RuntimeUiMessages.PREGEN_FAILED_FRAGMENT,
MessageArgument.trusted("failed", Form.f(progress.failed()))
)
: "";
return ModdedCommandFeedback.text(IrisLanguage.plain(
message,
MessageArgument.trusted("generated", Form.f(progress.generated())),
MessageArgument.trusted("total", Form.f(progress.totalChunks())),
MessageArgument.trusted("percent", String.format("%.1f", progress.percent())),
MessageArgument.trusted("speed", Form.f((int) progress.chunksPerSecond())),
MessageArgument.trusted("eta", eta),
MessageArgument.trusted("failed", failed)
), ModdedCommandFeedback.DARK_GREEN);
}
private static boolean hasClientPregenHud(ServerPlayer player) {
@@ -19,6 +19,8 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.core.gui.PregeneratorJob;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.iris.core.pregenerator.PregenPerformanceProfile;
import art.arcane.iris.core.pregenerator.PregenTask;
import art.arcane.iris.core.pregenerator.PregeneratorMethod;
@@ -27,6 +29,7 @@ import art.arcane.iris.core.pregenerator.methods.CachedPregenMethod;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.volmlib.util.format.Form;
import art.arcane.volmlib.util.math.Position2;
import art.arcane.volmlib.util.localization.MessageArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.server.MinecraftServer;
@@ -119,41 +122,66 @@ public final class ModdedPregenJob {
}
MutableComponent status = Component.empty();
status.append(ModdedCommandFeedback.header("Iris Pregen"));
status.append(ModdedCommandFeedback.header(IrisLanguage.plain(RuntimeUiMessages.PREGEN_HEADER)));
status.append(Component.literal("\n"));
status.append(ModdedCommandFeedback.text("Dimension ", ModdedCommandFeedback.DARK_GREEN));
status.append(ModdedCommandFeedback.text(dimension, ModdedCommandFeedback.PARAMETER_ALT));
status.append(ModdedCommandFeedback.text(" · Method ", ModdedCommandFeedback.DARK_GREEN));
status.append(ModdedCommandFeedback.text(progress.method(), ModdedCommandFeedback.PARAMETER));
status.append(ModdedCommandFeedback.text(IrisLanguage.plain(
RuntimeUiMessages.PREGEN_STATUS_CONTEXT,
MessageArgument.untrusted("dimension", dimension),
MessageArgument.untrusted("method", progress.method())
), ModdedCommandFeedback.DARK_GREEN));
status.append(Component.literal("\n"));
status.append(ModdedCommandFeedback.progressBar(progress.percent(), 32));
status.append(ModdedCommandFeedback.text(" " + String.format("%.1f", progress.percent()) + "%", ModdedCommandFeedback.USAGE));
status.append(ModdedCommandFeedback.text(" " + IrisLanguage.plain(
RuntimeUiMessages.PREGEN_STATUS_PROGRESS,
MessageArgument.trusted("percent", String.format("%.1f", progress.percent()))
), ModdedCommandFeedback.USAGE));
status.append(Component.literal("\n"));
status.append(ModdedCommandFeedback.text("Chunks ", ModdedCommandFeedback.DARK_GREEN));
status.append(ModdedCommandFeedback.text(Form.f(progress.generated()) + "/" + Form.f(progress.totalChunks()), ModdedCommandFeedback.VALUE));
status.append(ModdedCommandFeedback.text(" · Speed ", ModdedCommandFeedback.DARK_GREEN));
status.append(ModdedCommandFeedback.text(Form.f((int) progress.chunksPerSecond()) + "/s", ModdedCommandFeedback.VALUE));
if (progress.failed() > 0) {
status.append(ModdedCommandFeedback.text(" · Failed ", ModdedCommandFeedback.DARK_GREEN));
status.append(ModdedCommandFeedback.text(Form.f(progress.failed()), ModdedCommandFeedback.REQUIRED));
}
status.append(ModdedCommandFeedback.text(chunksStatus(progress), ModdedCommandFeedback.DARK_GREEN));
status.append(Component.literal("\n"));
status.append(ModdedCommandFeedback.text("ETA ", ModdedCommandFeedback.DARK_GREEN));
status.append(ModdedCommandFeedback.text(Form.duration(progress.eta(), 2), ModdedCommandFeedback.VALUE));
status.append(ModdedCommandFeedback.text(" · Elapsed ", ModdedCommandFeedback.DARK_GREEN));
status.append(ModdedCommandFeedback.text(Form.duration(progress.elapsed(), 2), ModdedCommandFeedback.VALUE));
if (progress.paused()) {
status.append(ModdedCommandFeedback.text(" · PAUSED", ModdedCommandFeedback.REQUIRED, true, false));
}
status.append(ModdedCommandFeedback.text(IrisLanguage.plain(
progress.paused()
? RuntimeUiMessages.PREGEN_STATUS_TIME_PAUSED
: RuntimeUiMessages.PREGEN_STATUS_TIME,
MessageArgument.trusted("eta", Form.duration(progress.eta(), 2)),
MessageArgument.trusted("elapsed", Form.duration(progress.elapsed(), 2))
), ModdedCommandFeedback.DARK_GREEN));
status.append(Component.literal("\n"));
status.append(ModdedCommandFeedback.button("Pause/Resume", "/iris pregen pause", "Toggle pregeneration pause state", true));
status.append(ModdedCommandFeedback.button(
IrisLanguage.plain(RuntimeUiMessages.PREGEN_PAUSE_BUTTON),
"/iris pregen pause",
IrisLanguage.plain(RuntimeUiMessages.PREGEN_PAUSE_HOVER),
true
));
status.append(ModdedCommandFeedback.text(" ", ModdedCommandFeedback.OPTIONAL));
status.append(ModdedCommandFeedback.button("Stop", "/iris pregen stop", "Finish the current region and stop pregeneration", true));
status.append(ModdedCommandFeedback.button(
IrisLanguage.plain(RuntimeUiMessages.PREGEN_STOP_BUTTON),
"/iris pregen stop",
IrisLanguage.plain(RuntimeUiMessages.PREGEN_STOP_HOVER),
true
));
status.append(Component.literal("\n"));
status.append(ModdedCommandFeedback.footer());
return status;
}
private static String chunksStatus(PregeneratorJob.PregenProgress progress) {
if (progress.failed() > 0) {
return IrisLanguage.plain(
RuntimeUiMessages.PREGEN_STATUS_CHUNKS_FAILED,
MessageArgument.trusted("generated", Form.f(progress.generated())),
MessageArgument.trusted("total", Form.f(progress.totalChunks())),
MessageArgument.trusted("speed", Form.f((int) progress.chunksPerSecond())),
MessageArgument.trusted("failed", Form.f(progress.failed()))
);
}
return IrisLanguage.plain(
RuntimeUiMessages.PREGEN_STATUS_CHUNKS,
MessageArgument.trusted("generated", Form.f(progress.generated())),
MessageArgument.trusted("total", Form.f(progress.totalChunks())),
MessageArgument.trusted("speed", Form.f((int) progress.chunksPerSecond()))
);
}
private static ActivePregen matchingActive(String worldIdentity) {
ActivePregen active = ACTIVE.get();
return active != null && active.targets(worldIdentity) ? active : null;
@@ -61,6 +61,8 @@ import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
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;
@@ -88,7 +90,7 @@ public final class ModdedRegen {
public static void start(CommandSourceStack source, ServerLevel level, IrisModdedChunkGenerator generator, Engine engine, ServerPlayer player, int radius) {
if (!ACTIVE.compareAndSet(false, true)) {
IrisModdedCommands.fail(source, "A regen is already running.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_REGEN_REGEN_IS_ALREADY_RUNNING));
return;
}
int centerX = player.blockPosition().getX() >> 4;
@@ -48,6 +48,9 @@ import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
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<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
@@ -116,7 +119,7 @@ public final class ModdedStructureCommands {
private static IrisData dataFor(CommandSourceStack source) {
Engine engine = IrisModdedCommands.engineFor(source.getLevel());
if (engine == null) {
IrisModdedCommands.fail(source, "This dimension is not generated by Iris. Run this from an Iris dimension so the pack can be resolved.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_RUN_THIS_FROM));
return null;
}
return engine.getData();
@@ -128,7 +131,7 @@ public final class ModdedStructureCommands {
return 0;
}
File file = StructureIndexService.write(data);
IrisModdedCommands.ok(source, "Wrote structure index: " + file.getPath());
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_WROTE_STRUCTURE_INDEX, MessageArgument.untrusted("value", file.getPath())));
return 1;
}
@@ -140,14 +143,14 @@ public final class ModdedStructureCommands {
String key = keyRaw.trim();
IrisStructure structure = data.load(IrisStructure.class, key, false);
if (structure == null) {
IrisModdedCommands.fail(source, "No iris structure '" + key + "' in this pack");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_NO_IRIS_STRUCTURE_THIS_PACK, MessageArgument.untrusted("key", key)));
return 0;
}
StructureAssembler assembler = StructureAssembler.forData(
data, structure, new IrisPosition(0, 64, 0));
KList<PlacedStructurePiece> pieces = assembler.assemble(new RNG(1234));
if (pieces == null || pieces.isEmpty()) {
IrisModdedCommands.fail(source, "Structure '" + key + "' assembled 0 pieces (check startPool '" + structure.getStartPool() + "')");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_STRUCTURE_ASSEMBLED_0_PIECES_CHECK_STARTPOOL, MessageArgument.untrusted("key", key), MessageArgument.untrusted("value", structure.getStartPool())));
return 0;
}
int minX = Integer.MAX_VALUE;
@@ -160,15 +163,14 @@ public final class ModdedStructureCommands {
maxX = Math.max(maxX, piece.getMaxX());
maxZ = Math.max(maxZ, piece.getMaxZ());
}
IrisModdedCommands.ok(source, "Structure '" + key + "': " + pieces.size() + " pieces, footprint "
+ (maxX - minX + 1) + "x" + (maxZ - minZ + 1) + " blocks (sample seed 1234)");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_STRUCTURE_PIECES_FOOTPRINT_X_BLOCKS_SAMPLE_SEED_1234, MessageArgument.untrusted("key", key), MessageArgument.untrusted("value", pieces.size()), MessageArgument.untrusted("value2", (maxX - minX + 1)), MessageArgument.untrusted("value3", (maxZ - minZ + 1))));
return 1;
}
private static int place(CommandSourceStack source, String keyRaw) {
ServerPlayer player = source.getPlayer();
if (player == null) {
IrisModdedCommands.fail(source, "This command can only be used by players (the structure is assembled at your position).");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_STRUCTURE_IS));
return 0;
}
ServerLevel level = source.getLevel();
@@ -180,7 +182,7 @@ public final class ModdedStructureCommands {
String key = keyRaw.trim();
IrisStructure structure = data.load(IrisStructure.class, key, false);
if (structure == null) {
IrisModdedCommands.fail(source, "No iris structure '" + key + "' in this pack");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_NO_IRIS_STRUCTURE_THIS_PACK_2, MessageArgument.untrusted("key", key)));
return 0;
}
int originX = player.blockPosition().getX();
@@ -191,7 +193,7 @@ public final class ModdedStructureCommands {
RNG rng = new RNG((long) originX * 341873128712L + originZ);
KList<PlacedStructurePiece> pieces = assembler.assemble(rng);
if (pieces == null || pieces.isEmpty()) {
IrisModdedCommands.fail(source, "Structure '" + key + "' assembled 0 pieces");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_STRUCTURE_ASSEMBLED_0_PIECES, MessageArgument.untrusted("key", key)));
return 0;
}
ModdedObjectPlacer placer = new ModdedObjectPlacer(level, engine);
@@ -210,12 +212,12 @@ public final class ModdedStructureCommands {
} catch (Throwable e) {
LOGGER.error("Iris structure place failed for {}", key, e);
ModdedObjectUndo.record(owner, level, placer.undoSnapshot());
IrisModdedCommands.fail(source, "Place failed: " + e.getClass().getSimpleName() + " (partial changes recorded for undo)");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_PLACE_FAILED_PARTIAL_CHANGES_RECORDED_UNDO, MessageArgument.untrusted("value", e.getClass().getSimpleName())));
return 0;
}
ModdedObjectUndo.record(owner, level, placer.undoSnapshot());
String tileNote = ModdedObjectCommands.tileNote(placer);
IrisModdedCommands.ok(source, "Placed '" + key + "' (" + pieces.size() + " pieces, " + placer.writes() + " write(s)" + tileNote + ") at your location. /iris object undo to revert.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STRUCTURE_COMMANDS_PLACED_PIECES_WRITE_S_AT_YOUR_LOCATION_IRIS_OBJECT_UNDO, MessageArgument.untrusted("key", key), MessageArgument.untrusted("value", pieces.size()), MessageArgument.untrusted("value2", placer.writes()), MessageArgument.untrusted("tileNote", tileNote)));
return 1;
}
@@ -82,6 +82,9 @@ import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.Pattern;
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<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
@@ -184,7 +187,7 @@ public final class ModdedStudioCommands {
}
private static int openHelp(CommandSourceStack source) {
IrisModdedCommands.fail(source, "Provide a dimension pack: /iris studio open <pack> [seed]");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PROVIDE_DIMENSION_PACK_IRIS_STUDIO_OPEN_PACK_SEED));
return 0;
}
@@ -215,22 +218,22 @@ public final class ModdedStudioCommands {
}
if (generatorKey == null || generatorKey.isBlank()) {
NoiseExplorerGUI.launch();
IrisModdedCommands.ok(source, "Opening the Noise Explorer on the server display.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_OPENING_NOISE_EXPLORER_ON_SERVER_DISPLAY));
return 1;
}
if (engine == null) {
IrisModdedCommands.fail(source, "This dimension is not generated by Iris; run /iris studio noise from an Iris dimension to resolve generators, or omit the generator name.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_RUN_IRIS_STUDIO));
return 0;
}
IrisGenerator generator = engine.getData().getGeneratorLoader().load(generatorKey.trim());
if (generator == null) {
IrisModdedCommands.fail(source, "Unknown generator '" + generatorKey + "' in pack " + engine.getDimension().getLoadKey() + ".");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_UNKNOWN_GENERATOR_PACK, MessageArgument.untrusted("generatorKey", generatorKey), MessageArgument.untrusted("value", engine.getDimension().getLoadKey())));
return 0;
}
long mixedSeed = new RNG(seed).nextParallelRNG(3245).lmax();
Supplier<Function2<Double, Double, Double>> supplier = () -> (Double x, Double z) -> generator.getHeight(x, z, mixedSeed);
NoiseExplorerGUI.launch(supplier, generatorKey.trim());
IrisModdedCommands.ok(source, "Opening the Noise Explorer for generator '" + generatorKey.trim() + "' (seed " + seed + ").");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_OPENING_NOISE_EXPLORER_GENERATOR_SEED, MessageArgument.untrusted("value", generatorKey.trim()), MessageArgument.untrusted("seed", seed)));
return 1;
}
@@ -238,7 +241,7 @@ public final class ModdedStudioCommands {
ServerLevel level = source.getLevel();
Engine engine = IrisModdedCommands.engineFor(level);
if (engine == null) {
IrisModdedCommands.fail(source, "This dimension is not generated by Iris; stand in an Iris (or studio) dimension and run /iris studio map.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_STAND_IRIS_STUDIO));
return 0;
}
if (!GuiHost.isAvailable() || !IrisSettings.get().getGui().isUseServerLaunchedGuis()) {
@@ -248,7 +251,7 @@ public final class ModdedStudioCommands {
ServerPlayer player = source.getPlayer();
ModdedGuiHost.bindContext(source.getServer(), level, engine, player == null ? null : player.getUUID());
VisionGUI.launch(engine);
IrisModdedCommands.ok(source, "Opening the Vision map for " + level.dimension().identifier() + " on the server display.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_OPENING_VISION_MAP_ON_SERVER_DISPLAY, MessageArgument.untrusted("value", level.dimension().identifier())));
return 1;
}
@@ -269,10 +272,10 @@ public final class ModdedStudioCommands {
workspace = ModdedWorkspaceGenerator.writeWorkspace(IrisData.get(folder), folder);
} catch (IOException e) {
LOGGER.error("Iris workspace write failed for {}", folder, e);
IrisModdedCommands.fail(source, "Failed to write workspace for " + folder.getAbsolutePath() + ": " + e.getMessage());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_FAILED_WRITE_WORKSPACE, MessageArgument.untrusted("value", folder.getAbsolutePath()), MessageArgument.untrusted("value2", String.valueOf(e.getMessage()))));
return 0;
}
IrisModdedCommands.ok(source, "Workspace regenerated: " + workspace.getAbsolutePath() + " with JSON schemas for autocomplete.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_WORKSPACE_REGENERATED_WITH_JSON_SCHEMAS_AUTOCOMPLETE, MessageArgument.untrusted("value", workspace.getAbsolutePath())));
if (!open) {
return 1;
}
@@ -284,10 +287,10 @@ public final class ModdedStudioCommands {
Desktop.getDesktop().open(workspace);
} catch (Throwable e) {
LOGGER.error("Iris workspace open failed for {}", workspace, e);
IrisModdedCommands.fail(source, "Could not open " + workspace.getName() + ": " + e.getClass().getSimpleName());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_COULD_NOT_OPEN, MessageArgument.untrusted("value", workspace.getName()), MessageArgument.untrusted("value2", e.getClass().getSimpleName())));
return 0;
}
IrisModdedCommands.ok(source, "Opening " + workspace.getName() + " in your editor.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_OPENING_YOUR_EDITOR, MessageArgument.untrusted("value", workspace.getName())));
return 1;
}
@@ -319,14 +322,14 @@ public final class ModdedStudioCommands {
if (name == null || name.isBlank()) {
Engine engine = IrisModdedCommands.engineFor(source.getLevel());
if (engine == null || engine.getDimension() == null) {
IrisModdedCommands.fail(source, "This dimension is not generated by Iris; specify a pack name explicitly.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS_SPECIFY_PACK_NAME));
return null;
}
name = engine.getDimension().getLoadKey();
}
File folder = new File(ModdedPackCommands.packsRoot(), name);
if (!folder.isDirectory() || !new File(folder, "dimensions").isDirectory()) {
IrisModdedCommands.fail(source, "Pack '" + name + "' not found under " + ModdedPackCommands.packsRoot().getAbsolutePath());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACK_NOT_FOUND_UNDER, MessageArgument.untrusted("name", name), MessageArgument.untrusted("value", ModdedPackCommands.packsRoot().getAbsolutePath())));
return null;
}
return folder;
@@ -346,14 +349,14 @@ public final class ModdedStudioCommands {
private static int open(CommandSourceStack source, String pack, long seed) {
if (pack == null || pack.isBlank()) {
IrisModdedCommands.fail(source, "Provide a dimension pack: /iris studio open <pack> [seed]");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PROVIDE_DIMENSION_PACK_IRIS_STUDIO_OPEN_PACK_SEED_2));
return 0;
}
ServerPlayer player = source.getPlayer();
UUID owner = player == null ? CONSOLE_OWNER : player.getUUID();
String dimensionId = player == null ? studioConsoleDimensionId() : studioDimensionId(player);
MinecraftServer server = source.getServer();
IrisModdedCommands.ok(source, "Opening studio for '" + pack + "' (seed " + seed + ")...");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_OPENING_STUDIO_SEED, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("seed", seed)));
Thread thread = new Thread(() -> openAsync(source, server, owner, dimensionId, pack, seed), "Iris Studio Open");
thread.setDaemon(true);
thread.start();
@@ -364,18 +367,18 @@ public final class ModdedStudioCommands {
try {
File packFolder = new File(ModdedPackCommands.packsRoot(), pack);
if (!new File(packFolder, "dimensions/" + pack + ".json").isFile()) {
server.execute(() -> IrisModdedCommands.ok(source, "Pack '" + pack + "' missing; downloading IrisDimensions/" + pack + "..."));
server.execute(() -> IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACK_MISSING_DOWNLOADING_IRISDIMENSIONS, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("pack2", pack))));
boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), pack, "master",
(String line) -> server.execute(() -> IrisModdedCommands.ok(source, line)));
if (!installed || !new File(packFolder, "dimensions/" + pack + ".json").isFile()) {
server.execute(() -> IrisModdedCommands.fail(source, "Pack '" + pack + "' could not be downloaded; check the name and try /iris download " + pack + "."));
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACK_COULD_NOT_BE_DOWNLOADED_CHECK_NAME_TRY_IRIS_DOWNLOAD, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("pack2", pack))));
return;
}
}
IrisData data = IrisData.get(packFolder);
IrisDimension dimension = data.getDimensionLoader().load(pack);
if (dimension == null) {
server.execute(() -> IrisModdedCommands.fail(source, "Pack '" + pack + "' has no dimensions/" + pack + ".json"));
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACK_HAS_NO_DIMENSIONS_JSON, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("pack2", pack))));
return;
}
server.execute(() -> {
@@ -387,7 +390,7 @@ public final class ModdedStudioCommands {
});
} catch (Throwable e) {
LOGGER.error("Iris studio open failed for {}", pack, e);
server.execute(() -> IrisModdedCommands.fail(source, "Studio open failed: " + e.getClass().getSimpleName() + (e.getMessage() == null ? "" : " - " + e.getMessage())));
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)))));
}
}
@@ -397,7 +400,7 @@ public final class ModdedStudioCommands {
handle = ModdedDimensionManager.create(server, dimensionId, pack, pack, seed);
} catch (Throwable e) {
LOGGER.error("Iris console studio injection failed for {} ({})", dimensionId, pack, e);
IrisModdedCommands.fail(source, "Studio injection failed: " + e.getClass().getSimpleName() + (e.getMessage() == null ? "" : " - " + e.getMessage()));
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;
}
STUDIOS.put(CONSOLE_OWNER, dimensionId);
@@ -411,10 +414,10 @@ public final class ModdedStudioCommands {
} catch (Throwable e) {
LOGGER.error("Iris console studio surface probe failed for {}", dimensionId, e);
}
IrisModdedCommands.ok(source, "Console studio open: " + dimensionId + " now runs '" + pack + "' seed " + seed + " (transient; not written to iris-dimensions.json and cleared on restart).");
IrisModdedCommands.ok(source, "Enter it with: /execute in " + dimensionId + " run tp @s 8.5 " + surface + " 8.5");
IrisModdedCommands.ok(source, "Pregen it with: /iris pregen start <radius> " + dimensionId);
IrisModdedCommands.ok(source, "Remove it with: /iris studio close");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_CONSOLE_STUDIO_OPEN_NOW_RUNS_SEED_TRANSIENT_NOT_WRITTEN_IRIS, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("seed", seed)));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_ENTER_IT_WITH_EXECUTE_RUN_TP_S_8_5_8, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("surface", surface)));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PREGEN_IT_WITH_IRIS_PREGEN_START_RADIUS, MessageArgument.untrusted("dimensionId", dimensionId)));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_REMOVE_IT_WITH_IRIS_STUDIO_CLOSE));
}
private static void injectAndTeleport(CommandSourceStack source, MinecraftServer server, UUID owner, String dimensionId, String pack, long seed) {
@@ -427,7 +430,7 @@ public final class ModdedStudioCommands {
handle = ModdedDimensionManager.create(server, dimensionId, pack, pack, seed);
} catch (Throwable e) {
LOGGER.error("Iris studio injection failed for {} ({})", dimensionId, pack, e);
IrisModdedCommands.fail(source, "Studio injection failed: " + e.getClass().getSimpleName() + (e.getMessage() == null ? "" : " - " + e.getMessage()));
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;
}
STUDIOS.put(owner, dimensionId);
@@ -442,7 +445,7 @@ public final class ModdedStudioCommands {
LOGGER.error("Iris studio surface probe failed for {}", dimensionId, e);
}
player.teleportTo(studio, 8.5D, surface, 8.5D, java.util.Set.<Relative>of(), player.getYRot(), player.getXRot(), false);
IrisModdedCommands.ok(source, "Studio open: " + dimensionId + " now runs '" + pack + "' seed " + seed + ". Use /iris studio close when done.");
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)));
}
private static int close(CommandSourceStack source) {
@@ -451,17 +454,17 @@ public final class ModdedStudioCommands {
UUID owner = player == null ? CONSOLE_OWNER : player.getUUID();
String dimensionId = STUDIOS.remove(owner);
if (dimensionId == null) {
IrisModdedCommands.fail(source, "You do not have an open studio. Use /iris studio open <pack> first.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_YOU_DO_NOT_HAVE_OPEN_STUDIO_USE_IRIS_STUDIO_OPEN));
return 0;
}
try {
ModdedDimensionManager.remove(server, dimensionId, true);
} catch (Throwable e) {
LOGGER.error("Iris studio close failed for {}", dimensionId, e);
IrisModdedCommands.fail(source, "Studio close failed: " + e.getClass().getSimpleName() + (e.getMessage() == null ? "" : " - " + e.getMessage()));
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;
}
IrisModdedCommands.ok(source, "Studio closed: " + dimensionId + " was evacuated, unloaded and its region data deleted.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_STUDIO_CLOSED_WAS_EVACUATED_UNLOADED_ITS_REGION_DATA_DELETED, MessageArgument.untrusted("dimensionId", dimensionId)));
return 1;
}
@@ -475,14 +478,14 @@ public final class ModdedStudioCommands {
}
}
if (studios.isEmpty()) {
IrisModdedCommands.ok(source, "No studio dimensions are currently open.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_NO_STUDIO_DIMENSIONS_ARE_CURRENTLY_OPEN));
return 1;
}
IrisModdedCommands.ok(source, "Active studio dimension(s): " + studios.size());
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_ACTIVE_STUDIO_DIMENSION_S, MessageArgument.untrusted("value", studios.size())));
for (ModdedDimensionManager.Handle handle : studios) {
UUID owner = ownerOf(handle.dimensionId());
String ownerName = owner == null ? "unclaimed" : ownerName(server, owner);
IrisModdedCommands.ok(source, " " + handle.dimensionId() + ": pack '" + handle.pack() + "' seed " + handle.seed() + " owner " + ownerName);
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACK_SEED_OWNER, MessageArgument.untrusted("value", handle.dimensionId()), MessageArgument.untrusted("value2", handle.pack()), MessageArgument.untrusted("value3", handle.seed()), MessageArgument.untrusted("ownerName", ownerName)));
}
return 1;
}
@@ -507,19 +510,19 @@ public final class ModdedStudioCommands {
private static int tpStudio(CommandSourceStack source) {
ServerPlayer player = source.getPlayer();
if (player == null) {
IrisModdedCommands.fail(source, "This command can only be used by players.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS));
return 0;
}
MinecraftServer server = source.getServer();
String dimensionId = STUDIOS.get(player.getUUID());
if (dimensionId == null) {
IrisModdedCommands.fail(source, "You do not have an open studio. Use /iris studio open <pack> first.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_YOU_DO_NOT_HAVE_OPEN_STUDIO_USE_IRIS_STUDIO_OPEN_2));
return 0;
}
ServerLevel studio = ModdedDimensionManager.level(server, dimensionId);
if (studio == null) {
STUDIOS.remove(player.getUUID());
IrisModdedCommands.fail(source, "Your studio dimension is no longer loaded. Use /iris studio open <pack> again.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_YOUR_STUDIO_DIMENSION_IS_NO_LONGER_LOADED_USE_IRIS_STUDIO));
return 0;
}
int surface = studio.getMaxY();
@@ -532,7 +535,7 @@ public final class ModdedStudioCommands {
LOGGER.error("Iris tpstudio surface probe failed", e);
}
player.teleportTo(studio, 8.5D, surface, 8.5D, java.util.Set.<Relative>of(), player.getYRot(), player.getXRot(), false);
IrisModdedCommands.ok(source, "Teleported to your studio (" + dimensionId + ").");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_TELEPORTED_YOUR_STUDIO, MessageArgument.untrusted("dimensionId", dimensionId)));
return 1;
}
@@ -544,36 +547,36 @@ public final class ModdedStudioCommands {
IrisData data = IrisData.get(folder);
IrisDimension dimension = data.getDimensionLoader().load(folder.getName());
if (dimension == null) {
IrisModdedCommands.fail(source, "Pack '" + folder.getName() + "' has no dimensions/" + folder.getName() + ".json");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACK_HAS_NO_DIMENSIONS_JSON_2, MessageArgument.untrusted("value", folder.getName()), MessageArgument.untrusted("value2", folder.getName())));
return 0;
}
IrisModdedCommands.ok(source, "The \"" + dimension.getName() + "\" pack has version: " + dimension.getVersion());
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACK_HAS_VERSION, MessageArgument.untrusted("value", dimension.getName()), MessageArgument.untrusted("value2", dimension.getVersion())));
return 1;
}
private static int create(CommandSourceStack source, String nameRaw, String template) {
String name = nameRaw.toLowerCase(Locale.ROOT);
if (!PROJECT_NAME.matcher(name).matches()) {
IrisModdedCommands.fail(source, "Invalid project name '" + nameRaw + "' (allowed: a-z, 0-9, _ and -)");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_INVALID_PROJECT_NAME_ALLOWED_Z_0_9, MessageArgument.untrusted("nameRaw", nameRaw)));
return 0;
}
File packsRoot = ModdedPackCommands.packsRoot();
File target = new File(packsRoot, name);
if (target.exists()) {
IrisModdedCommands.fail(source, "Pack '" + name + "' already exists at " + target.getAbsolutePath());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACK_ALREADY_EXISTS_AT, MessageArgument.untrusted("name", name), MessageArgument.untrusted("value", target.getAbsolutePath())));
return 0;
}
MinecraftServer server = source.getServer();
IrisModdedCommands.ok(source, "Creating project '" + name + "' from template '" + template + "'...");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_CREATING_PROJECT_FROM_TEMPLATE, MessageArgument.untrusted("name", name), MessageArgument.untrusted("template", template)));
Thread thread = new Thread(() -> {
try {
File templateFolder = new File(packsRoot, template);
if (!new File(templateFolder, "dimensions/" + template + ".json").isFile()) {
server.execute(() -> IrisModdedCommands.ok(source, "Template '" + template + "' is not installed; downloading IrisDimensions/" + template + "..."));
server.execute(() -> IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_TEMPLATE_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS, MessageArgument.untrusted("template", template), MessageArgument.untrusted("template2", template))));
boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), template, "master",
(String line) -> server.execute(() -> IrisModdedCommands.ok(source, line)));
if (!installed || !new File(templateFolder, "dimensions/" + template + ".json").isFile()) {
server.execute(() -> IrisModdedCommands.fail(source, "Template '" + template + "' could not be downloaded; install a pack with dimensions/" + template + ".json first."));
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_TEMPLATE_COULD_NOT_BE_DOWNLOADED_INSTALL_PACK_WITH_DIMENSIONS_JSON, MessageArgument.untrusted("template", template), MessageArgument.untrusted("template2", template))));
return;
}
}
@@ -584,12 +587,12 @@ public final class ModdedStudioCommands {
LOGGER.error("Iris studio create workspace generation failed for {}", name, e);
}
server.execute(() -> {
IrisModdedCommands.ok(source, "Created project '" + name + "' at " + target.getAbsolutePath());
IrisModdedCommands.ok(source, "Edit dimensions/" + name + ".json and the rest of the pack. A VSCode workspace with JSON schema autocomplete was generated; open it or rerun /iris studio vscode.");
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);
server.execute(() -> IrisModdedCommands.fail(source, "Project creation failed: " + e.getClass().getSimpleName() + (e.getMessage() == null ? "" : " - " + e.getMessage())));
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");
thread.setDaemon(true);
@@ -604,14 +607,14 @@ public final class ModdedStudioCommands {
}
MinecraftServer server = source.getServer();
String dimKey = folder.getName();
IrisModdedCommands.ok(source, "Packaging dimension '" + dimKey + "'...");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACKAGING_DIMENSION, MessageArgument.untrusted("dimKey", dimKey)));
Thread thread = new Thread(() -> {
try {
File result = compilePackage(folder, dimKey);
server.execute(() -> IrisModdedCommands.ok(source, "Package compiled: " + result.getAbsolutePath()));
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);
server.execute(() -> IrisModdedCommands.fail(source, "Packaging failed: " + e.getClass().getSimpleName() + (e.getMessage() == null ? "" : " - " + e.getMessage())));
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");
thread.setDaemon(true);
@@ -758,18 +761,18 @@ public final class ModdedStudioCommands {
private static int regions(CommandSourceStack source, int radius) {
ServerPlayer player = source.getPlayer();
if (player == null) {
IrisModdedCommands.fail(source, "This command can only be used by players (sampling is centered on your position).");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_THIS_COMMAND_CAN_ONLY_BE_USED_BY_PLAYERS_SAMPLING_IS));
return 0;
}
Engine engine = IrisModdedCommands.engineFor(source.getLevel());
if (engine == null) {
IrisModdedCommands.fail(source, "This dimension is not generated by Iris.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_THIS_DIMENSION_IS_NOT_GENERATED_BY_IRIS));
return 0;
}
MinecraftServer server = source.getServer();
int blockX = player.blockPosition().getX();
int blockZ = player.blockPosition().getZ();
IrisModdedCommands.ok(source, "Sampling region distribution in " + (radius * 2) + "x" + (radius * 2) + " chunks around you...");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_SAMPLING_REGION_DISTRIBUTION_X_CHUNKS_AROUND_YOU, MessageArgument.untrusted("value", (radius * 2)), MessageArgument.untrusted("value2", (radius * 2))));
Thread thread = new Thread(() -> {
try {
int diameter = radius * 2;
@@ -787,11 +790,11 @@ public final class ModdedStudioCommands {
server.execute(() -> counts.forEach((String key, AtomicInteger count) -> {
IrisRegion region = engine.getData().getRegionLoader().load(key);
String rarity = region == null ? "?" : String.valueOf(region.getRarity());
IrisModdedCommands.ok(source, key + ": rarity=" + rarity + " / " + Form.f((double) count.get() / totalTasks * 100, 2) + "%");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_RARITY, MessageArgument.untrusted("key", key), MessageArgument.untrusted("rarity", rarity), MessageArgument.untrusted("value", Form.f((double) count.get() / totalTasks * 100, 2))));
}));
} catch (Throwable e) {
LOGGER.error("Iris region sampling failed", e);
server.execute(() -> IrisModdedCommands.fail(source, "Region sampling failed: " + e.getClass().getSimpleName()));
server.execute(() -> IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_REGION_SAMPLING_FAILED, MessageArgument.untrusted("value", e.getClass().getSimpleName()))));
}
}, "Iris Region Sampler");
thread.setDaemon(true);
@@ -18,6 +18,9 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.core.localization.RuntimeUiMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.component.DataComponents;
@@ -78,10 +81,10 @@ public final class ModdedWandService {
public static ItemStack createWand() {
ItemStack stack = new ItemStack(Items.BLAZE_ROD);
stack.set(DataComponents.CUSTOM_NAME, Component.literal("Wand of Iris").withStyle(ChatFormatting.BOLD, ChatFormatting.GOLD));
stack.set(DataComponents.CUSTOM_NAME, Component.literal(IrisLanguage.plain(RuntimeUiMessages.WAND_NAME)).withStyle(ChatFormatting.BOLD, ChatFormatting.GOLD));
stack.set(DataComponents.LORE, new ItemLore(List.of(
Component.literal("Left click a block to set the first corner"),
Component.literal("Right click a block to set the second corner"))));
Component.literal(IrisLanguage.plain(RuntimeUiMessages.WAND_LORE_FIRST)),
Component.literal(IrisLanguage.plain(RuntimeUiMessages.WAND_LORE_SECOND)))));
stack.set(DataComponents.UNBREAKABLE, Unit.INSTANCE);
stack.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, Boolean.TRUE);
stack.set(DataComponents.CUSTOM_DATA, CustomData.of(flagTag(WAND_TAG)));
@@ -90,9 +93,9 @@ public final class ModdedWandService {
public static ItemStack createDust() {
ItemStack stack = new ItemStack(Items.GLOWSTONE_DUST);
stack.set(DataComponents.CUSTOM_NAME, Component.literal("Dust of Revealing").withStyle(ChatFormatting.BOLD, ChatFormatting.YELLOW));
stack.set(DataComponents.CUSTOM_NAME, Component.literal(IrisLanguage.plain(RuntimeUiMessages.DUST_NAME)).withStyle(ChatFormatting.BOLD, ChatFormatting.YELLOW));
stack.set(DataComponents.LORE, new ItemLore(List.of(
Component.literal("Right click on a block to reveal it's placement structure!"))));
Component.literal(IrisLanguage.plain(RuntimeUiMessages.DUST_LORE)))));
stack.set(DataComponents.UNBREAKABLE, Unit.INSTANCE);
stack.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, Boolean.TRUE);
stack.set(DataComponents.CUSTOM_DATA, CustomData.of(flagTag(DUST_TAG)));
@@ -161,7 +164,13 @@ public final class ModdedWandService {
return first ? new Selection(dimension, corner, other) : new Selection(dimension, other, corner);
});
level.playSound(null, pos, SoundEvents.END_PORTAL_FRAME_FILL, SoundSource.PLAYERS, 1.0F, first ? 0.67F : 1.17F);
player.sendOverlayMessage(Component.literal("Position " + (first ? 1 : 2) + " set to " + corner.getX() + ", " + corner.getY() + ", " + corner.getZ()));
player.sendOverlayMessage(Component.literal(IrisLanguage.plain(
RuntimeUiMessages.WAND_POSITION_SET,
MessageArgument.trusted("position", first ? 1 : 2),
MessageArgument.trusted("x", corner.getX()),
MessageArgument.trusted("y", corner.getY()),
MessageArgument.trusted("z", corner.getZ())
)));
}
public static Selection selection(ServerPlayer player) {
@@ -51,6 +51,10 @@ import java.util.Locale;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Predicate;
import art.arcane.iris.core.localization.IrisLanguage;
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<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
@@ -169,13 +173,13 @@ public final class ModdedWorldCommands {
if (packFolder.isDirectory()) {
return enableInstalled(source, server, dimensionId, pack, packDimension, seed);
}
IrisModdedCommands.ok(source, "Pack '" + pack + "' is not installed; downloading IrisDimensions/" + pack + "...");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_PACK_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("pack2", pack)));
Thread thread = new Thread(() -> {
boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), pack, "master",
(String line) -> server.execute(() -> IrisModdedCommands.ok(source, line)));
server.execute(() -> {
if (!installed || !packFolder.isDirectory()) {
IrisModdedCommands.fail(source, "Pack '" + pack + "' could not be downloaded; check the name or install it with /iris download " + pack + ".");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_PACK_COULD_NOT_BE_DOWNLOADED_CHECK_NAME_INSTALL_IT_WITH, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("pack2", pack)));
return;
}
enableInstalled(source, server, dimensionId, pack, packDimension, seed);
@@ -197,11 +201,11 @@ public final class ModdedWorldCommands {
ModdedDimensionManager.createPersistent(server, dimensionId, pack, packDimension, seed);
} catch (Throwable e) {
LOGGER.error("Iris world injection failed for {} (pack={} dim={})", dimensionId, pack, packDimension, e);
IrisModdedCommands.fail(source, "Failed to inject Iris world '" + dimensionId + "': " + e.getClass().getSimpleName() + (e.getMessage() == null ? "" : " - " + e.getMessage()));
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;
}
IrisModdedCommands.ok(source, "Created Iris world " + dimensionId + " from pack '" + pack + "' dimension '" + packDimension + "' (seed " + seed + ").");
IrisModdedCommands.ok(source, "It is live now and re-injected on every startup. Teleport in with /iris world status or a portal; no restart required.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_CREATED_IRIS_WORLD_FROM_PACK_DIMENSION_SEED, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("packDimension", packDimension), MessageArgument.untrusted("seed", seed)));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_IT_IS_LIVE_NOW_RE_INJECTED_ON_EVERY_STARTUP_TELEPORT));
return 1;
}
@@ -228,21 +232,21 @@ public final class ModdedWorldCommands {
ModdedDimensionManager.createPersistent(server, dimensionId, pack, packDimension, seed);
} catch (Throwable e) {
LOGGER.error("Iris primary world injection failed for {} (pack={} dim={})", dimensionId, pack, packDimension, e);
IrisModdedCommands.fail(source, "Failed to inject Iris primary world: " + e.getClass().getSimpleName() + (e.getMessage() == null ? "" : " - " + e.getMessage()));
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;
}
ModdedModConfig.setPrimaryWorld(dimensionId);
ModdedPrimaryWorldRouter.clear();
IrisModdedCommands.ok(source, "Iris primary world set to " + dimensionId + " (pack '" + pack + "' dimension '" + packDimension + "' seed " + seed + ").");
IrisModdedCommands.ok(source, "The vanilla overworld generator cannot be hot-swapped, so this does NOT regenerate the existing overworld.");
IrisModdedCommands.ok(source, "Instead, " + dimensionId + " is now the configured primary world: players in the vanilla overworld are routed there on join, and it re-injects on every startup.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_IRIS_PRIMARY_WORLD_SET_PACK_DIMENSION_SEED, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("packDimension", packDimension), MessageArgument.untrusted("seed", seed)));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_VANILLA_OVERWORLD_GENERATOR_CANNOT_BE_HOT_SWAPPED_SO_THIS_DOES));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_INSTEAD_IS_NOW_CONFIGURED_PRIMARY_WORLD_PLAYERS_VANILLA_OVERWORLD_ARE, MessageArgument.untrusted("dimensionId", dimensionId)));
return 1;
}
private static int clearMainWorld(CommandSourceStack source) {
ModdedModConfig.setMainWorld("", 0L);
MainWorldService.clearOverride();
IrisModdedCommands.ok(source, "Iris main world override cleared. The overworld keeps its current generator; edit server.properties level-type and restart to change it back.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_IRIS_MAIN_WORLD_OVERRIDE_CLEARED_OVERWORLD_KEEPS_ITS_CURRENT_GENERATOR));
return 1;
}
@@ -258,7 +262,7 @@ public final class ModdedWorldCommands {
try {
seed = Long.parseLong(seedRaw.trim());
} catch (NumberFormatException e) {
IrisModdedCommands.fail(source, "Invalid seed '" + seedRaw + "'. Use a number or 'random'.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_INVALID_SEED_USE_NUMBER_RANDOM, MessageArgument.untrusted("seedRaw", seedRaw)));
return 0;
}
}
@@ -272,13 +276,13 @@ public final class ModdedWorldCommands {
if (packFolder.isDirectory()) {
return applyMainWorld(source, pack, packDimension, packRaw, seed);
}
IrisModdedCommands.ok(source, "Pack '" + pack + "' is not installed; downloading IrisDimensions/" + pack + "...");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_PACK_IS_NOT_INSTALLED_DOWNLOADING_IRISDIMENSIONS_2, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("pack2", pack)));
Thread thread = new Thread(() -> {
boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), pack, "master",
(String line) -> server.execute(() -> IrisModdedCommands.ok(source, line)));
server.execute(() -> {
if (!installed || !packFolder.isDirectory()) {
IrisModdedCommands.fail(source, "Pack '" + pack + "' could not be downloaded; check the name or install it with /iris download " + pack + ".");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_PACK_COULD_NOT_BE_DOWNLOADED_CHECK_NAME_INSTALL_IT_WITH_2, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("pack2", pack)));
return;
}
applyMainWorld(source, pack, packDimension, packRaw, seed);
@@ -299,23 +303,23 @@ public final class ModdedWorldCommands {
}
} catch (Throwable e) {
LOGGER.error("Iris main world pack load failed for {} (dim={})", pack, packDimension, e);
IrisModdedCommands.fail(source, "Pack '" + pack + "' is not ready yet (still loading or validating). Try the command again in a moment.");
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;
}
ModdedModConfig.setMainWorld(packRef, seed);
if (!MainWorldService.stage(packRef, seed)) {
IrisModdedCommands.fail(source, "Failed to write server.properties; check file permissions and set level-type manually.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_FAILED_WRITE_SERVER_PROPERTIES_CHECK_FILE_PERMISSIONS_SET_LEVEL_TYPE));
return 0;
}
String preset = MainWorldService.presetIdFor(packRef);
IrisModdedCommands.ok(source, "Iris main world set to '" + pack + "' (preset " + preset + ", seed " + (seed == 0L ? "random" : Long.toString(seed)) + ").");
IrisModdedCommands.ok(source, "server.properties level-type is now " + preset + ". On the next restart the overworld, nether, and end regenerate as this Iris world.");
IrisModdedCommands.ok(source, "Player data (inventories, advancements, stats) is kept; existing terrain in those dimensions is replaced. This applies once.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_IRIS_MAIN_WORLD_SET_PRESET_SEED, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("preset", preset), MessageArgument.untrusted("value", seed == 0L ? IrisLanguage.plain(RuntimeUiMessages.STATUS_RANDOM) : Long.toString(seed))));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_SERVER_PROPERTIES_LEVEL_TYPE_IS_NOW_ON_NEXT_RESTART_OVERWORLD, MessageArgument.untrusted("preset", preset)));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_PLAYER_DATA_INVENTORIES_ADVANCEMENTS_STATS_IS_KEPT_EXISTING_TERRAIN_THOSE));
if (ModdedModConfig.get().mainWorldAutoRestart()) {
IrisModdedCommands.ok(source, "mainWorldAutoRestart is enabled - stopping the server now so your restart wrapper brings it back on the new main world.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_MAINWORLDAUTORESTART_IS_ENABLED_STOPPING_SERVER_NOW_SO_YOUR_RESTART_WRAPPER));
source.getServer().halt(false);
} else {
IrisModdedCommands.ok(source, "Restart the server now to generate it. (Set mainWorldAutoRestart=true in modded.json to have Iris stop the server for you.)");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_RESTART_SERVER_NOW_GENERATE_IT_SET_MAINWORLDAUTORESTART_TRUE_MODDED_JSON));
}
return 1;
}
@@ -325,12 +329,11 @@ public final class ModdedWorldCommands {
ModdedStartup.requirePackForWorldCreation(pack);
return false;
} catch (BrokenPackException e) {
IrisModdedCommands.fail(source, "Refusing to create world '" + dimensionId
+ "' using pack '" + pack + "' because required validation failed:");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_REFUSING_CREATE_WORLD_USING_PACK_BECAUSE_REQUIRED_VALIDATION_FAILED, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("pack", pack)));
for (String reason : e.getReasons()) {
IrisModdedCommands.fail(source, " - " + reason);
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_MESSAGE, MessageArgument.untrusted("reason", reason)));
}
IrisModdedCommands.fail(source, "Fix the pack and run /iris pack validate " + pack + " to revalidate.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_FIX_PACK_RUN_IRIS_PACK_VALIDATE_REVALIDATE, MessageArgument.untrusted("pack", pack)));
return true;
}
}
@@ -338,13 +341,13 @@ public final class ModdedWorldCommands {
private static boolean loadPackDimension(CommandSourceStack source, String pack, String packDimension) {
File packFolder = new File(ModdedPackCommands.packsRoot(), pack);
if (!packFolder.isDirectory()) {
IrisModdedCommands.fail(source, "Pack '" + pack + "' was not found under " + ModdedPackCommands.packsRoot().getAbsolutePath());
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_PACK_WAS_NOT_FOUND_UNDER, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("value", ModdedPackCommands.packsRoot().getAbsolutePath())));
return false;
}
IrisData data = IrisData.get(packFolder);
IrisDimension dimension = data.getDimensionLoader().load(packDimension);
if (dimension == null) {
IrisModdedCommands.fail(source, "Pack '" + pack + "' does not contain dimensions/" + packDimension + ".json");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_PACK_DOES_NOT_CONTAIN_DIMENSIONS_JSON, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("packDimension", packDimension)));
return false;
}
return true;
@@ -364,7 +367,7 @@ public final class ModdedWorldCommands {
&& packDimension.matches("[A-Za-z0-9_/.-]+") && !packDimension.contains("..")) {
return true;
}
IrisModdedCommands.fail(source, "Invalid pack reference '" + pack + (pack.equals(packDimension) ? "" : ":" + packDimension) + "'. Use pack or pack:dimensionKey.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_INVALID_PACK_REFERENCE_USE_PACK_PACK_DIMENSIONKEY, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("value", (pack.equals(packDimension) ? "" : ":" + packDimension))));
return false;
}
@@ -379,7 +382,7 @@ public final class ModdedWorldCommands {
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
IrisModdedCommands.fail(source, "Invalid seed '" + seedRaw + "'. Use a number or 'random'.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_INVALID_SEED_USE_NUMBER_RANDOM_2, MessageArgument.untrusted("seedRaw", seedRaw)));
return null;
}
}
@@ -398,7 +401,7 @@ public final class ModdedWorldCommands {
removed = ModdedDimensionManager.removePersistent(server, dimensionId, wipeStorage);
} catch (Throwable e) {
LOGGER.error("Iris world removal failed for {}", dimensionId, e);
IrisModdedCommands.fail(source, "Failed to remove Iris world '" + dimensionId + "': " + e.getClass().getSimpleName() + (e.getMessage() == null ? "" : " - " + e.getMessage()));
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;
}
if (dimensionId.equals(ModdedModConfig.get().primaryWorld())) {
@@ -407,17 +410,17 @@ public final class ModdedWorldCommands {
}
if (!removed) {
if (wipeStorage) {
IrisModdedCommands.ok(source, "Iris world '" + dimensionId + "' was not loaded; cleared its persistent registry entry and wiped its stored chunk/mantle data.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_IRIS_WORLD_WAS_NOT_LOADED_CLEARED_ITS_PERSISTENT_REGISTRY_ENTRY, MessageArgument.untrusted("dimensionId", dimensionId)));
} else {
IrisModdedCommands.ok(source, "Iris world '" + dimensionId + "' was not loaded; cleared its persistent registry entry. World data on disk is kept.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_IRIS_WORLD_WAS_NOT_LOADED_CLEARED_ITS_PERSISTENT_REGISTRY_ENTRY_2, MessageArgument.untrusted("dimensionId", dimensionId)));
}
return 1;
}
if (wipeStorage) {
IrisModdedCommands.ok(source, "Deleted Iris world '" + dimensionId + "': evacuated, unloaded, chunk/mantle data wiped, and dropped from the startup registry.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_DELETED_IRIS_WORLD_EVACUATED_UNLOADED_CHUNK_MANTLE_DATA_WIPED_DROPPED, MessageArgument.untrusted("dimensionId", dimensionId)));
} else {
IrisModdedCommands.ok(source, "Disabled Iris world '" + dimensionId + "': evacuated, unloaded, and dropped from the startup registry.");
IrisModdedCommands.ok(source, "World data on disk is kept; re-enable with /iris world enable " + dimensionId + " <pack> or delete it with /iris world delete " + dimensionId + ".");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_DISABLED_IRIS_WORLD_EVACUATED_UNLOADED_DROPPED_FROM_STARTUP_REGISTRY, MessageArgument.untrusted("dimensionId", dimensionId)));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_WORLD_DATA_ON_DISK_IS_KEPT_RE_ENABLE_WITH_IRIS, MessageArgument.untrusted("dimensionId", dimensionId), MessageArgument.untrusted("dimensionId2", dimensionId)));
}
return 1;
}
@@ -428,27 +431,27 @@ public final class ModdedWorldCommands {
for (ServerLevel level : server.getAllLevels()) {
if (level.getChunkSource().getGenerator() instanceof IrisModdedChunkGenerator generator) {
loaded++;
IrisModdedCommands.ok(source, "Loaded Iris level: " + level.dimension().identifier() + " -> pack '" + generator.activePack() + "' dimension '" + generator.activeDimensionKey() + "'");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_LOADED_IRIS_LEVEL_PACK_DIMENSION, MessageArgument.untrusted("value", level.dimension().identifier()), MessageArgument.untrusted("value2", generator.activePack()), MessageArgument.untrusted("value3", generator.activeDimensionKey())));
}
}
String primary = ModdedModConfig.get().primaryWorld();
if (!primary.isBlank()) {
IrisModdedCommands.ok(source, "Primary world: " + primary + (ModdedModConfig.get().routePlayersToPrimaryWorld() ? " (players routed there)" : " (routing disabled)"));
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_PRIMARY_WORLD, MessageArgument.untrusted("primary", primary), MessageArgument.trusted("value", IrisLanguage.plain(ModdedModConfig.get().routePlayersToPrimaryWorld() ? RuntimeUiMessages.PRIMARY_PLAYERS_ROUTED_SUFFIX : RuntimeUiMessages.PRIMARY_ROUTING_DISABLED_SUFFIX))));
}
if (loaded == 0) {
IrisModdedCommands.fail(source, "No Iris dimensions are currently loaded. Create one with /iris world create <name> <pack>.");
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_NO_IRIS_DIMENSIONS_ARE_CURRENTLY_LOADED_CREATE_ONE_WITH_IRIS));
}
return loaded > 0 ? 1 : 0;
}
private static int list(CommandSourceStack source) {
List<String> dimensions = loadedIrisDimensions(source.getServer());
IrisModdedCommands.ok(source, "Loaded Iris dimensions: " + dimensions.size());
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_LOADED_IRIS_DIMENSIONS, MessageArgument.untrusted("value", dimensions.size())));
for (String dimension : dimensions) {
IrisModdedCommands.ok(source, " - " + dimension);
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_MESSAGE_2, MessageArgument.untrusted("dimension", dimension)));
}
if (dimensions.isEmpty()) {
IrisModdedCommands.ok(source, "Use /iris world create <name> <pack> to inject one without restarting.");
IrisModdedCommands.ok(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_WORLD_COMMANDS_USE_IRIS_WORLD_CREATE_NAME_PACK_INJECT_ONE_WITHOUT_RESTARTING));
}
return 1;
}
@@ -19,6 +19,7 @@
package art.arcane.iris.modded.service;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.localization.IrisLanguage;
import art.arcane.iris.spi.IrisPlatforms;
import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
@@ -52,12 +53,14 @@ public final class ModdedSettingsHotloadService implements ModdedTickableService
lastPollAt = now;
long modified = settingsFile().lastModified();
if (modified == lastModified) {
IrisLanguage.update();
return;
}
if (IrisSettings.settings != null) {
IrisSettings.invalidate();
}
IrisSettings.get();
IrisLanguage.reload();
lastModified = settingsFile().lastModified();
LOGGER.info("Hotloaded settings.json");
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "Vorgenerierungs-HUD umschalten"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "Alternar HUD de pregeneración"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "Vaihda esigeneroinnin HUD"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "Activer ou désactiver le HUD de prégénération"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "הפעלה או כיבוי של תצוגת הקדם-יצירה"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "Attiva o disattiva l'HUD di pregenerazione"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "事前生成 HUD の切り替え"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "사전 생성 HUD 전환"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "Perjungti išankstinio generavimo HUD"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "HUD voor vooraf genereren in- of uitschakelen"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "Przełącz interfejs wstępnego generowania"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "Alternar HUD de pré-geração"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "Переключить HUD предварительной генерации"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "Ön oluşturma HUD'unu aç veya kapat"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "Bật hoặc tắt HUD tạo trước"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "切换预生成 HUD"
}
@@ -0,0 +1,4 @@
{
"key.categories.irisworldgen.iris": "Iris",
"key.irisworldgen.toggle_pregen_hud": "切換預先生成 HUD"
}