From 657aaeaccf0893924b6b6b67e2d5a801e854dd3c Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Thu, 3 Sep 2026 05:03:28 -0400 Subject: [PATCH] Add /iris language and /iris debugdump on both platforms /iris language opens a locale menu and sets the language for the caller or, for a gamemaster, the server default; /iris debugdump writes a diagnostic report and optionally uploads it. Neither needs iris.all, so a plain player can reach them. Every modded command now executes inside a language audience scope, and the Bukkit dispatcher opens one around execution, tab completion and help. Permissions, message keys and the shipped locales follow. --- .../src/main/java/art/arcane/iris/Iris.java | 34 ++++ .../iris/core/commands/CommandIris.java | 14 ++ .../arcane/iris/core/service/CommandSVC.java | 58 +++++-- .../src/main/resources/paper-plugin.yml | 6 + .../plugin/src/main/resources/plugin.yml | 6 + .../arcane/iris/PaperPluginMetadataTest.java | 9 +- .../commands/CommandLanguageRoutingTest.java | 56 +++++++ .../iris/core/link/IrisPapiLifecycleTest.java | 13 +- .../iris/modded/ModdedEngineBootstrap.java | 1 + .../modded/command/ModdedCommandTree.java | 147 ++++++++++-------- .../command/ModdedDatapackCommands.java | 18 +-- .../command/ModdedDeveloperCommands.java | 6 +- .../command/ModdedLanguageCommands.java | 92 +++++++++++ .../modded/command/ModdedObjectCommands.java | 60 +++---- .../modded/command/ModdedPackCommands.java | 34 ++-- .../command/ModdedStructureCommands.java | 26 ++-- .../modded/command/ModdedStudioCommands.java | 67 ++++---- .../modded/command/ModdedWhatCommands.java | 28 ++-- .../modded/command/ModdedWorldCommands.java | 38 ++--- .../command/IrisModdedCommandParityTest.java | 4 + .../command/ModdedLanguageCommandsTest.java | 125 +++++++++++++++ .../localization/DirectorCommandMessages.java | 4 + core/src/main/resources/languages/de_DE.json | 2 + core/src/main/resources/languages/es_ES.json | 2 + core/src/main/resources/languages/fi_FI.json | 2 + core/src/main/resources/languages/fr_FR.json | 2 + core/src/main/resources/languages/he_IL.json | 2 + core/src/main/resources/languages/it_IT.json | 2 + core/src/main/resources/languages/ja-JP.json | 2 + core/src/main/resources/languages/ko_KR.json | 2 + core/src/main/resources/languages/lt_LT.json | 2 + core/src/main/resources/languages/nl_NL.json | 2 + core/src/main/resources/languages/pl_PL.json | 2 + core/src/main/resources/languages/pt_PT.json | 2 + core/src/main/resources/languages/ru_RU.json | 2 + core/src/main/resources/languages/tr_TR.json | 2 + core/src/main/resources/languages/vi_VI.json | 2 + core/src/main/resources/languages/zh_CN.json | 2 + core/src/main/resources/languages/zh_TW.json | 2 + 39 files changed, 654 insertions(+), 226 deletions(-) create mode 100644 adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/commands/CommandLanguageRoutingTest.java create mode 100644 adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedLanguageCommands.java create mode 100644 adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModdedLanguageCommandsTest.java diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/Iris.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/Iris.java index 6180a12bf..8e7e5b7bf 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/Iris.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/Iris.java @@ -18,6 +18,7 @@ package art.arcane.iris; +import art.arcane.volmlib.util.diagnostics.BukkitDebugDump; import art.arcane.iris.engine.IrisEngineEffects; import art.arcane.iris.engine.IrisWorldManager; @@ -50,6 +51,8 @@ import art.arcane.iris.core.link.IrisPapiInstaller; import art.arcane.iris.core.link.IrisPapiListener; import art.arcane.iris.core.link.IrisPapiState; import art.arcane.iris.core.localization.IrisLanguage; +import art.arcane.volmlib.util.director.help.DirectorMiniMenu; +import art.arcane.volmlib.util.localization.BukkitLanguageSwitcher; import art.arcane.iris.core.link.MultiverseCoreLink; import art.arcane.iris.core.nms.INMS; import art.arcane.iris.core.gui.BukkitGuiHost; @@ -199,6 +202,8 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware { private final BukkitWorldReconciler worldReconciler = new BukkitWorldReconciler(this); private final PendingWorldDeleteQueue pendingWorldDeletes = new PendingWorldDeleteQueue(this); private final PendingWorldReplacementManager pendingWorldReplacements = new PendingWorldReplacementManager(this); + private BukkitLanguageSwitcher languageSwitcher; + private BukkitDebugDump debugDump; private volatile SettingsHotloadWatch settingsHotloadWatch; private volatile Thread serverLifecycleThread; @@ -602,6 +607,10 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware { MultiBurst.burst.reopen(); MultiBurst.ioBurst.reopen(); IrisLanguage.initialize(); + debugDump = BukkitDebugDump.create(this); + languageSwitcher = BukkitLanguageSwitcher.register(this, IrisLanguage.selections(), + new BukkitLanguageSwitcher.Options("iris", "iris.all", + DirectorMiniMenu.Theme.irisGreen(), IrisLanguage.directorResolver(), IrisLanguage.editorOptions())); PaperLibBootstrap.install(); SimdSupport.install(); services = new KMap<>(); @@ -861,7 +870,32 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware { } } + public BukkitDebugDump debugDump() { + return debugDump; + } + + public void selectLanguage(CommandSender sender, String[] arguments) { + BukkitLanguageSwitcher current = languageSwitcher; + if (current != null) { + current.command(sender, arguments); + } + } + + public List completeLanguage(CommandSender sender, String[] arguments) { + BukkitLanguageSwitcher current = languageSwitcher; + return current == null ? List.of() : current.complete(sender, arguments); + } + public void onDisable() { + if (debugDump != null) { + debugDump.close(); + debugDump = null; + } + if (languageSwitcher != null) { + languageSwitcher.close(); + languageSwitcher = null; + } + IrisLanguage.shutdown(); teardownPapi(); boolean serverStopping = IrisToolbelt.isServerStopping(); boolean restartingAtStartupBoundary = startupBoundaryRestart.get(); diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java index 3081f0248..4d60a7f55 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/commands/CommandIris.java @@ -50,6 +50,7 @@ import art.arcane.iris.util.common.format.C; import art.arcane.iris.util.common.plugin.VolmitSender; import art.arcane.iris.util.common.scheduling.J; import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; import org.bukkit.Location; import org.bukkit.NamespacedKey; import org.bukkit.World; @@ -95,6 +96,19 @@ public class CommandIris implements DirectorExecutor { private CommandDatapack datapack; VolmitSender sender = Iris.getSender(); + @Director(name = "debugdump", sync = true, description = "Create and optionally upload a diagnostic report", descriptionKey = "iris.director.commandiris.director.debugdump") + public void debugdump( + @Param(name = "upload", defaultValue = "true", description = "Upload the report to mclo.gs", descriptionKey = "iris.director.commandiris.param.debugdump_upload") boolean upload, + @Param(name = "sender", contextual = true) CommandSender sender + ) { + Iris.instance.debugDump().request(sender, upload); + } + + @Director(description = "Choose your language or the server default") + public void language() { + Iris.instance.selectLanguage(sender().getS(), new String[0]); + } + @Director(description = "Create a new world", descriptionKey = "iris.director.commandiris.director.create_new_world", aliases = {"c"}) public void create( @Param(aliases = "world-name", description = "The name of the world to create", descriptionKey = "iris.director.commandiris.param.name_world_create") diff --git a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/CommandSVC.java b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/CommandSVC.java index 50d40b23b..8316a7653 100644 --- a/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/CommandSVC.java +++ b/adapters/bukkit/plugin/src/main/java/art/arcane/iris/core/service/CommandSVC.java @@ -55,6 +55,8 @@ import org.jetbrains.annotations.Nullable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.List; import java.util.Map; import java.util.Optional; @@ -62,11 +64,14 @@ import java.util.Optional; import art.arcane.iris.core.localization.IrisLanguage; import art.arcane.iris.core.localization.IrisMessages; import art.arcane.volmlib.util.localization.MessageArgument; +import art.arcane.volmlib.util.localization.LanguageAudience; import art.arcane.volmlib.util.plugin.ComponentMessenger; public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, DirectorInvocationHook { private static final String ROOT_COMMAND = "iris"; private static final String ROOT_PERMISSION = "iris.all"; + private final ThreadLocal> languageScopes = ThreadLocal.withInitial(ArrayDeque::new); + private final transient AtomicCache directorCache = new AtomicCache<>(); private final transient ThreadLocal dispatchSenders = new ThreadLocal<>(); @@ -102,6 +107,12 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D private DirectorContextRegistry buildDirectorContexts() { DirectorContextRegistry contexts = new DirectorContextRegistry(); + contexts.register(CommandSender.class, (invocation, map) -> { + if (invocation.getSender() instanceof BukkitDirectorSender sender) { + return sender.sender(); + } + return null; + }); for (Map.Entry, DirectorContextHandler> entry : DirectorContextHandler.contextHandlers.entrySet()) { registerContextHandler(contexts, entry.getKey(), entry.getValue()); @@ -139,6 +150,7 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D @Override public void beforeInvoke(DirectorInvocation invocation, DirectorRuntimeNode node) { if (invocation.getSender() instanceof BukkitDirectorSender sender) { + languageScopes.get().push(LanguageAudience.open(sender.sender() instanceof Player player ? player.getUniqueId() : null)); DirectorContext.touch(new VolmitSender(sender.sender())); } } @@ -146,6 +158,13 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D @Override public void afterInvoke(DirectorInvocation invocation, DirectorRuntimeNode node) { DirectorContext.remove(); + Deque scopes = languageScopes.get(); + if (!scopes.isEmpty()) { + scopes.pop().close(); + } + if (scopes.isEmpty()) { + languageScopes.remove(); + } } @Nullable @@ -169,7 +188,12 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D } public void executeRoot(CommandSender sender, String label, String[] args) { - if (!sender.hasPermission(ROOT_PERMISSION)) { + if (args.length > 0 && args[0].equalsIgnoreCase("language")) { + Iris.instance.selectLanguage(sender, Arrays.copyOfRange(args, 1, args.length)); + return; + } + if (!(args.length > 0 && args[0].equalsIgnoreCase("debugdump")) + && !sender.hasPermission(ROOT_PERMISSION)) { ComponentMessenger.sendSection(sender, IrisLanguage.text( IrisMessages.COMMAND_PERMISSION_DENIED, MessageArgument.trusted("permission", ROOT_PERMISSION) @@ -181,7 +205,9 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D } public List tabCompleteRoot(CommandSender sender, String alias, String[] args) { - List suggestions = runDirectorTab(sender, alias, args); + List suggestions = args.length > 0 && args[0].equalsIgnoreCase("language") + ? Iris.instance.completeLanguage(sender, Arrays.copyOfRange(args, 1, args.length)) + : runDirectorTab(sender, alias, args); if (sender instanceof Player player && IrisSettings.get().getGeneral().isCommandSounds()) { player.playSound(player.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.25f, RNG.r.f(0.125f, 1.95f)); } @@ -235,23 +261,25 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D } private boolean sendHelpIfRequested(CommandSender sender, String[] args) { - Optional request = DirectorMiniMenu.resolveHelp(getDirector(), Arrays.asList(args)); - if (request.isEmpty()) { - return false; - } + try (LanguageAudience.Scope audience = LanguageAudience.open(sender instanceof Player player ? player.getUniqueId() : null)) { + Optional request = DirectorMiniMenu.resolveHelp(getDirector(), Arrays.asList(args)); + if (request.isEmpty()) { + return false; + } - DirectorMiniMenu.deliver( - sender, - request.get(), - DirectorMiniMenu.Theme.irisGreen(), - IrisLanguage.directorResolver() - ); - return true; + DirectorMiniMenu.deliver( + sender, + request.get(), + DirectorMiniMenu.Theme.irisGreen(), + IrisLanguage.directorResolver() + ); + return true; + } } private DirectorExecutionResult runDirector(CommandSender sender, String label, String[] args) { dispatchSenders.set(sender); - try { + try (LanguageAudience.Scope audience = LanguageAudience.open(sender instanceof Player player ? player.getUniqueId() : null)) { return getDirector().execute(new DirectorInvocation(new BukkitDirectorSender(sender), label, Arrays.asList(args))); } catch (Throwable e) { Iris.warn("Director command execution failed: " + e.getClass().getSimpleName() + " " + e.getMessage()); @@ -263,7 +291,7 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D private List runDirectorTab(CommandSender sender, String alias, String[] args) { DirectorContext.touch(new VolmitSender(sender)); - try { + try (LanguageAudience.Scope audience = LanguageAudience.open(sender instanceof Player player ? player.getUniqueId() : null)) { return getDirector().tabComplete(new DirectorInvocation(new BukkitDirectorSender(sender), alias, Arrays.asList(args))); } catch (Throwable e) { Iris.warn("Director tab completion failed: " + e.getClass().getSimpleName() + " " + e.getMessage()); diff --git a/adapters/bukkit/plugin/src/main/resources/paper-plugin.yml b/adapters/bukkit/plugin/src/main/resources/paper-plugin.yml index e42021fe2..c8bebaf82 100644 --- a/adapters/bukkit/plugin/src/main/resources/paper-plugin.yml +++ b/adapters/bukkit/plugin/src/main/resources/paper-plugin.yml @@ -10,6 +10,12 @@ authors: [ cyberpwn, NextdoorPsycho, Vatuu ] website: VolmitSoftware.com description: More than a Dimension! permissions: + iris.debugdump: + description: Allows creating and optionally uploading diagnostic reports. + default: op + iris.language.self: + description: Allows choosing your own language for this plugin. + default: true iris.all: description: Allows use of the full /iris command tree (worlds, studio, pregen, packs, developer tools). default: op diff --git a/adapters/bukkit/plugin/src/main/resources/plugin.yml b/adapters/bukkit/plugin/src/main/resources/plugin.yml index 65e3c0a88..1cf377ae4 100644 --- a/adapters/bukkit/plugin/src/main/resources/plugin.yml +++ b/adapters/bukkit/plugin/src/main/resources/plugin.yml @@ -26,6 +26,12 @@ commands: iris: aliases: [ ir, irs ] permissions: + iris.debugdump: + description: Allows creating and optionally uploading diagnostic reports. + default: op + iris.language.self: + description: Allows choosing your own language for this plugin. + default: true iris.all: description: Allows use of the full /iris command tree (worlds, studio, pregen, packs, developer tools). default: op diff --git a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/PaperPluginMetadataTest.java b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/PaperPluginMetadataTest.java index ef02d7427..2f9c7158e 100644 --- a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/PaperPluginMetadataTest.java +++ b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/PaperPluginMetadataTest.java @@ -57,7 +57,14 @@ public class PaperPluginMetadataTest { assertTrue(metadata.contains("folia-supported: true")); assertTrue(metadata.contains("load: STARTUP")); assertFalse(metadata.contains("commands:")); - assertTrue(metadata.contains("permissions:\n iris.all:\n" + assertTrue(metadata.contains("permissions:\n")); + assertTrue(metadata.contains(" iris.debugdump:\n" + + " description: Allows creating and optionally uploading diagnostic reports.\n" + + " default: op")); + assertTrue(metadata.contains(" iris.language.self:\n" + + " description: Allows choosing your own language for this plugin.\n" + + " default: true")); + assertTrue(metadata.contains(" iris.all:\n" + " description: Allows use of the full /iris command tree (worlds, studio, pregen, packs, developer tools).\n" + " default: op\n" + " iris.treefeller:\n" diff --git a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/commands/CommandLanguageRoutingTest.java b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/commands/CommandLanguageRoutingTest.java new file mode 100644 index 000000000..5acf8e61d --- /dev/null +++ b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/commands/CommandLanguageRoutingTest.java @@ -0,0 +1,56 @@ +package art.arcane.iris.core.commands; + +import art.arcane.iris.Iris; +import art.arcane.iris.core.service.CommandSVC; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class CommandLanguageRoutingTest { + private Iris previousPlugin; + private Iris plugin; + + @Before + public void installPlugin() { + previousPlugin = Iris.instance; + plugin = mock(Iris.class); + Iris.instance = plugin; + } + + @After + public void restorePlugin() { + Iris.instance = previousPlugin; + } + + @Test + public void forwardsEveryLanguageArgumentBeforeCheckingTheAdminRootPermission() { + Player player = mock(Player.class); + String[] arguments = {"language", "self", "de_DE"}; + + new CommandSVC().executeRoot(player, "iris", arguments); + + verify(plugin).selectLanguage(player, new String[]{"self", "de_DE"}); + verify(player, never()).hasPermission("iris.all"); + } + + @Test + public void forwardsLanguageTabArgumentsToTheSharedCompletionService() { + CommandSender sender = mock(CommandSender.class); + String[] arguments = {"language", "self", "de"}; + String[] languageArguments = {"self", "de"}; + when(plugin.completeLanguage(sender, languageArguments)).thenReturn(List.of("de_DE")); + + assertEquals(List.of("de_DE"), new CommandSVC().tabCompleteRoot(sender, "iris", arguments)); + verify(plugin).completeLanguage(sender, languageArguments); + } +} diff --git a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/link/IrisPapiLifecycleTest.java b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/link/IrisPapiLifecycleTest.java index 193e614fa..919fa76ea 100644 --- a/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/link/IrisPapiLifecycleTest.java +++ b/adapters/bukkit/plugin/src/test/java/art/arcane/iris/core/link/IrisPapiLifecycleTest.java @@ -82,12 +82,15 @@ public class IrisPapiLifecycleTest { @Test public void bothDisablePathsTearThePlaceholderSurfaceDown() throws Exception { - String source = source(); + String disable = body("public void onDisable() {"); + String preUnload = body("public void onPreUnload(ReloadAware.PreUnloadReason reason) {"); + int disableTeardown = disable.indexOf("teardownPapi();"); + int preUnloadTeardown = preUnload.indexOf("teardownPapi();"); - assertTrue("onDisable must tear the expansion down", - source.contains("public void onDisable() {\n teardownPapi();")); - assertTrue("the BileTools pre-unload hook must tear the expansion down", - source.contains("public void onPreUnload(ReloadAware.PreUnloadReason reason) {\n teardownPapi();")); + assertTrue("onDisable must tear the expansion down before branching into runtime shutdown", + disableTeardown >= 0 && disableTeardown < disable.indexOf("IrisToolbelt.isServerStopping()")); + assertTrue("the BileTools pre-unload hook must tear the expansion down before any shutdown branch", + preUnloadTeardown >= 0 && preUnloadTeardown < preUnload.indexOf("IrisToolbelt.isServerStopping()")); } @Test diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java index 9eaf970d5..4fd0ed2d1 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java @@ -435,6 +435,7 @@ public final class ModdedEngineBootstrap { } private static void restorePlatform(IrisPlatform platform) { + IrisLanguage.shutdown(); IrisPlatforms.unbind(); if (platform != null) { IrisPlatforms.bind(platform); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandTree.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandTree.java index aa793f621..327aa72df 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandTree.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedCommandTree.java @@ -18,7 +18,10 @@ package art.arcane.iris.modded.command; +import art.arcane.volmlib.util.localization.LanguageAudience; +import com.mojang.brigadier.Command; import com.mojang.brigadier.arguments.IntegerArgumentType; +import net.minecraft.server.level.ServerPlayer; import com.mojang.brigadier.arguments.LongArgumentType; import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.ArgumentBuilder; @@ -66,16 +69,17 @@ final class ModdedCommandTree { static LiteralArgumentBuilder rootTree() { LiteralArgumentBuilder root = Commands.literal("iris"); - root.executes((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), "")); + root.executes(ModdedCommandTree.localized((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), ""))); root.then(helpTree()); + root.then(ModdedLanguageCommands.tree()); root.then(Commands.literal("version").requires(READ_ONLY) - .executes((CommandContext context) -> IrisModdedCommands.version(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.version(context.getSource())))); root.then(Commands.literal("info").requires(READ_ONLY) - .executes((CommandContext context) -> IrisModdedCommands.info(context.getSource(), null)) + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.info(context.getSource(), null))) .then(Commands.argument("dimension", StringArgumentType.greedyString()).suggests(ModdedCommandSuggestions.DIMENSION_NAMES) - .executes((CommandContext context) -> IrisModdedCommands.info(context.getSource(), StringArgumentType.getString(context, "dimension"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.info(context.getSource(), StringArgumentType.getString(context, "dimension")))))); // ModdedWhatCommands.tree() gates itself at LEVEL_GAMEMASTERS and keeps that gate: // "what markers" drives lease-gated 9x9 mantle scans, and the Bukkit twin puts the @@ -86,27 +90,27 @@ final class ModdedCommandTree { root.then(teleportTree("tp")); root.then(Commands.literal("evacuate").requires(GATE) - .executes((CommandContext context) -> IrisModdedCommands.evacuate(context.getSource(), null)) + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.evacuate(context.getSource(), null))) .then(Commands.argument("dimension", DimensionArgument.dimension()).suggests(ModdedCommandSuggestions.DIMENSION_NAMES) - .executes((CommandContext context) -> IrisModdedCommands.evacuate(context.getSource(), DimensionArgument.getDimension(context, "dimension"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.evacuate(context.getSource(), DimensionArgument.getDimension(context, "dimension")))))); root.then(Commands.literal("debug").requires(GATE) - .executes((CommandContext context) -> IrisModdedCommands.debug(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.debug(context.getSource())))); root.then(Commands.literal("reload").requires(GATE) - .executes((CommandContext context) -> IrisModdedCommands.reload(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.reload(context.getSource())))); root.then(Commands.literal("height").requires(READ_ONLY) - .executes((CommandContext context) -> IrisModdedCommands.height(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.height(context.getSource())))); root.then(Commands.literal("worlds").requires(READ_ONLY) - .executes((CommandContext context) -> IrisModdedCommands.info(context.getSource(), null))); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.info(context.getSource(), null)))); root.then(Commands.literal("accesslist").requires(GATE) - .executes((CommandContext context) -> IrisModdedCommands.info(context.getSource(), null))); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.info(context.getSource(), null)))); root.then(gotoTree("goto")); root.then(gotoTree("find")); root.then(Commands.literal("seed").requires(GATE) - .executes((CommandContext context) -> IrisModdedCommands.seed(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.seed(context.getSource())))); root.then(goldenhashTree("goldenhash")); root.then(goldenhashTree("gold")); @@ -124,11 +128,11 @@ final class ModdedCommandTree { root.then(pregenTree("pregenerate")); root.then(Commands.literal("wand").requires(GATE) - .executes((CommandContext context) -> ModdedObjectCommands.giveWand(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedObjectCommands.giveWand(context.getSource())))); root.then(Commands.literal("dust").requires(GATE) - .executes((CommandContext context) -> ModdedObjectCommands.giveDust(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedObjectCommands.giveDust(context.getSource())))); root.then(Commands.literal("d").requires(GATE) - .executes((CommandContext context) -> ModdedObjectCommands.giveDust(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedObjectCommands.giveDust(context.getSource())))); root.then(ModdedObjectCommands.tree("object")); root.then(ModdedObjectCommands.tree("o")); root.then(editTree()); @@ -155,122 +159,131 @@ final class ModdedCommandTree { return root; } + static Command localized(Command command) { + return context -> { + ServerPlayer player = context.getSource().getPlayer(); + try (LanguageAudience.Scope audience = LanguageAudience.open(player == null ? null : player.getUUID())) { + return command.run(context); + } + }; + } + private static LiteralArgumentBuilder createTree(String name) { return Commands.literal(name).requires(GATE) .then(Commands.argument("name", StringArgumentType.word()) - .executes((CommandContext context) -> + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedWorldCommands.createWorld( context.getSource(), StringArgumentType.getString(context, "name"), "overworld", - 1337L)) + 1337L))) .then(Commands.argument("pack", StringArgumentType.string()).suggests(ModdedCommandSuggestions.PACK_NAMES) - .executes((CommandContext context) -> ModdedWorldCommands.createWorld(context.getSource(), + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedWorldCommands.createWorld(context.getSource(), StringArgumentType.getString(context, "name"), StringArgumentType.getString(context, "pack"), - 1337L)) + 1337L))) .then(Commands.argument("seed", LongArgumentType.longArg()) - .executes((CommandContext context) -> ModdedWorldCommands.createWorld(context.getSource(), + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedWorldCommands.createWorld(context.getSource(), StringArgumentType.getString(context, "name"), StringArgumentType.getString(context, "pack"), - LongArgumentType.getLong(context, "seed")))))); + LongArgumentType.getLong(context, "seed"))))))); } private static LiteralArgumentBuilder teleportTree(String name) { return Commands.literal(name).requires(GATE) .then(Commands.argument("dimension", DimensionArgument.dimension()).suggests(ModdedCommandSuggestions.DIMENSION_NAMES) - .executes((CommandContext context) -> - IrisModdedCommands.tp(context.getSource(), DimensionArgument.getDimension(context, "dimension"), null)) + .executes(ModdedCommandTree.localized((CommandContext context) -> + IrisModdedCommands.tp(context.getSource(), DimensionArgument.getDimension(context, "dimension"), null))) .then(Commands.argument("player", EntityArgument.player()) - .executes((CommandContext context) -> + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.tp(context.getSource(), DimensionArgument.getDimension(context, "dimension"), - EntityArgument.getPlayer(context, "player"))))); + EntityArgument.getPlayer(context, "player")))))); } private static LiteralArgumentBuilder helpTree() { return Commands.literal("help").requires(READ_ONLY) - .executes((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), "")) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), ""))) .then(Commands.argument("section", StringArgumentType.greedyString()) - .executes((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), StringArgumentType.getString(context, "section")))); + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), StringArgumentType.getString(context, "section"))))); } private static LiteralArgumentBuilder downloadTree(String name) { return Commands.literal(name).requires(GATE) .then(Commands.argument("source", StringArgumentType.greedyString()).suggests(DOWNLOAD_SOURCES) - .executes((CommandContext context) -> IrisModdedCommands.download( + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.download( context.getSource(), StringArgumentType.getString(context, "source") - ))); + )))); } private static LiteralArgumentBuilder metricsTree(String name) { return Commands.literal(name).requires(READ_ONLY) - .executes((CommandContext context) -> IrisModdedCommands.metrics(context.getSource())); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.metrics(context.getSource()))); } private static LiteralArgumentBuilder regenTree(String name) { return Commands.literal(name).requires(GATE) - .executes((CommandContext context) -> IrisModdedCommands.regen(context.getSource(), 0)) + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.regen(context.getSource(), 0))) .then(Commands.argument("radius", IntegerArgumentType.integer(0, 64)) - .executes((CommandContext context) -> IrisModdedCommands.regen(context.getSource(), IntegerArgumentType.getInteger(context, "radius")))); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.regen(context.getSource(), IntegerArgumentType.getInteger(context, "radius"))))); } private static LiteralArgumentBuilder gotoTree(String name) { return Commands.literal(name).requires(GATE) - .executes((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name)) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name))) .then(Commands.literal("biome") .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(ModdedCommandSuggestions.BIOME_KEYS) - .executes((CommandContext context) -> ModdedLocateCommands.gotoBiome(context.getSource(), StringArgumentType.getString(context, "key"))))) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedLocateCommands.gotoBiome(context.getSource(), StringArgumentType.getString(context, "key")))))) .then(Commands.literal("region") .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(ModdedCommandSuggestions.REGION_KEYS) - .executes((CommandContext context) -> ModdedLocateCommands.gotoRegion(context.getSource(), StringArgumentType.getString(context, "key"))))) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedLocateCommands.gotoRegion(context.getSource(), StringArgumentType.getString(context, "key")))))) .then(Commands.literal("object") .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(ModdedCommandSuggestions.OBJECT_KEYS) - .executes((CommandContext context) -> ModdedLocateCommands.gotoObject(context.getSource(), StringArgumentType.getString(context, "key"))))) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedLocateCommands.gotoObject(context.getSource(), StringArgumentType.getString(context, "key")))))) .then(Commands.literal("river") .then(Commands.argument("type", StringArgumentType.greedyString()).suggests(ModdedCommandSuggestions.HYDROLOGY_TYPES) - .executes((CommandContext context) -> ModdedLocateCommands.gotoRiver(context.getSource(), StringArgumentType.getString(context, "type"))))) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedLocateCommands.gotoRiver(context.getSource(), StringArgumentType.getString(context, "type")))))) .then(Commands.literal("unregistered") - .executes((CommandContext context) -> - ModdedUnregisteredStructures.print(context.getSource()))) + .executes(ModdedCommandTree.localized((CommandContext context) -> + ModdedUnregisteredStructures.print(context.getSource())))) .then(Commands.literal("structure") .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(ModdedCommandSuggestions.STRUCTURE_KEYS) - .executes((CommandContext context) -> ModdedLocateCommands.gotoStructure(context.getSource(), StringArgumentType.getString(context, "key"))))) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedLocateCommands.gotoStructure(context.getSource(), StringArgumentType.getString(context, "key")))))) .then(Commands.literal("poi") .then(Commands.argument("type", StringArgumentType.greedyString()).suggests(ModdedCommandSuggestions.POI_TYPES) - .executes((CommandContext context) -> ModdedLocateCommands.gotoPoi(context.getSource(), StringArgumentType.getString(context, "type"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedLocateCommands.gotoPoi(context.getSource(), StringArgumentType.getString(context, "type")))))); } private static LiteralArgumentBuilder pregenTree(String name) { RequiredArgumentBuilder radius = Commands.argument("radius", IntegerArgumentType.integer(1, 100000)) - .executes((CommandContext context) -> ModdedPregenCommands.pregenStart(context, false, false, false, false, false)); + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedPregenCommands.pregenStart(context, false, false, false, false, false))); attachPregenCenter(radius, false); attachPregenFlags(radius, false, false, false, false, false); RequiredArgumentBuilder dimension = Commands.argument("dimension", DimensionArgument.dimension()).suggests(ModdedCommandSuggestions.DIMENSION_NAMES) - .executes((CommandContext context) -> ModdedPregenCommands.pregenStart(context, true, false, false, false, false)); + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedPregenCommands.pregenStart(context, true, false, false, false, false))); attachPregenCenter(dimension, true); attachPregenFlags(dimension, true, false, false, false, false); radius.then(dimension); return Commands.literal(name).requires(GATE) - .executes((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name)) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name))) .then(Commands.literal("start") .then(radius)) .then(Commands.literal("stop") - .executes((CommandContext context) -> ModdedPregenCommands.pregenStop(context.getSource()))) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedPregenCommands.pregenStop(context.getSource())))) .then(Commands.literal("x") - .executes((CommandContext context) -> ModdedPregenCommands.pregenStop(context.getSource()))) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedPregenCommands.pregenStop(context.getSource())))) .then(Commands.literal("pause") - .executes((CommandContext context) -> ModdedPregenCommands.pregenPause(context.getSource()))) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedPregenCommands.pregenPause(context.getSource())))) .then(Commands.literal("resume") - .executes((CommandContext context) -> ModdedPregenCommands.pregenPause(context.getSource()))) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedPregenCommands.pregenPause(context.getSource())))) .then(Commands.literal("status") - .executes((CommandContext context) -> ModdedPregenCommands.pregenStatus(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedPregenCommands.pregenStatus(context.getSource())))); } private static void attachPregenCenter(ArgumentBuilder node, boolean withDimension) { RequiredArgumentBuilder z = Commands.argument("z", IntegerArgumentType.integer()) - .executes((CommandContext context) -> ModdedPregenCommands.pregenStart(context, withDimension, true, false, false, false)); + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedPregenCommands.pregenStart(context, withDimension, true, false, false, false))); attachPregenFlags(z, withDimension, true, false, false, false); node.then(Commands.literal("at") .then(Commands.argument("x", IntegerArgumentType.integer()) @@ -291,22 +304,22 @@ final class ModdedCommandTree { private static LiteralArgumentBuilder pregenFlagNode(String name, boolean withDimension, boolean withCenter, boolean gui, boolean sync, boolean nocache) { LiteralArgumentBuilder flag = Commands.literal(name) - .executes((CommandContext context) -> ModdedPregenCommands.pregenStart(context, withDimension, withCenter, gui, sync, nocache)); + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedPregenCommands.pregenStart(context, withDimension, withCenter, gui, sync, nocache))); attachPregenFlags(flag, withDimension, withCenter, gui, sync, nocache); return flag; } private static LiteralArgumentBuilder goldenhashTree(String name) { LiteralArgumentBuilder radiusAndThreads = Commands.literal(name).requires(GATE) - .executes((CommandContext context) -> IrisModdedCommands.goldenhash(context.getSource(), 8, 8, ModdedGoldenHash.Mode.AUTO)); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.goldenhash(context.getSource(), 8, 8, ModdedGoldenHash.Mode.AUTO))); attachModes(radiusAndThreads, (CommandContext context) -> 8, (CommandContext context) -> 8); com.mojang.brigadier.builder.RequiredArgumentBuilder radius = Commands.argument("radius", IntegerArgumentType.integer(0, 256)) - .executes((CommandContext context) -> IrisModdedCommands.goldenhash(context.getSource(), IntegerArgumentType.getInteger(context, "radius"), 8, ModdedGoldenHash.Mode.AUTO)); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.goldenhash(context.getSource(), IntegerArgumentType.getInteger(context, "radius"), 8, ModdedGoldenHash.Mode.AUTO))); attachModes(radius, (CommandContext context) -> IntegerArgumentType.getInteger(context, "radius"), (CommandContext context) -> 8); com.mojang.brigadier.builder.RequiredArgumentBuilder threads = Commands.argument("threads", IntegerArgumentType.integer(1, 64)) - .executes((CommandContext context) -> IrisModdedCommands.goldenhash(context.getSource(), IntegerArgumentType.getInteger(context, "radius"), IntegerArgumentType.getInteger(context, "threads"), ModdedGoldenHash.Mode.AUTO)); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.goldenhash(context.getSource(), IntegerArgumentType.getInteger(context, "radius"), IntegerArgumentType.getInteger(context, "threads"), ModdedGoldenHash.Mode.AUTO))); attachModes(threads, (CommandContext context) -> IntegerArgumentType.getInteger(context, "radius"), (CommandContext context) -> IntegerArgumentType.getInteger(context, "threads")); radius.then(threads); @@ -320,33 +333,33 @@ final class ModdedCommandTree { private static void attachModes(com.mojang.brigadier.builder.ArgumentBuilder node, IntExtractor radius, IntExtractor threads) { node.then(Commands.literal("capture") - .executes((CommandContext context) -> IrisModdedCommands.goldenhash(context.getSource(), radius.extract(context), threads.extract(context), ModdedGoldenHash.Mode.CAPTURE))); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.goldenhash(context.getSource(), radius.extract(context), threads.extract(context), ModdedGoldenHash.Mode.CAPTURE)))); node.then(Commands.literal("verify") - .executes((CommandContext context) -> IrisModdedCommands.goldenhash(context.getSource(), radius.extract(context), threads.extract(context), ModdedGoldenHash.Mode.VERIFY))); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.goldenhash(context.getSource(), radius.extract(context), threads.extract(context), ModdedGoldenHash.Mode.VERIFY)))); } private static LiteralArgumentBuilder editTree() { return Commands.literal("edit").requires(GATE) - .executes((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), "edit")) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), "edit"))) .then(Commands.literal("biome") - .executes((CommandContext context) -> ModdedEditCommands.editBiome(context.getSource(), null)) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedEditCommands.editBiome(context.getSource(), null))) .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(ModdedCommandSuggestions.BIOME_KEYS) - .executes((CommandContext context) -> ModdedEditCommands.editBiome(context.getSource(), StringArgumentType.getString(context, "key"))))) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedEditCommands.editBiome(context.getSource(), StringArgumentType.getString(context, "key")))))) .then(Commands.literal("b") - .executes((CommandContext context) -> ModdedEditCommands.editBiome(context.getSource(), null)) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedEditCommands.editBiome(context.getSource(), null))) .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(ModdedCommandSuggestions.BIOME_KEYS) - .executes((CommandContext context) -> ModdedEditCommands.editBiome(context.getSource(), StringArgumentType.getString(context, "key"))))) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedEditCommands.editBiome(context.getSource(), StringArgumentType.getString(context, "key")))))) .then(Commands.literal("region") - .executes((CommandContext context) -> ModdedEditCommands.editRegion(context.getSource(), null)) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedEditCommands.editRegion(context.getSource(), null))) .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(ModdedCommandSuggestions.REGION_KEYS) - .executes((CommandContext context) -> ModdedEditCommands.editRegion(context.getSource(), StringArgumentType.getString(context, "key"))))) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedEditCommands.editRegion(context.getSource(), StringArgumentType.getString(context, "key")))))) .then(Commands.literal("r") - .executes((CommandContext context) -> ModdedEditCommands.editRegion(context.getSource(), null)) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedEditCommands.editRegion(context.getSource(), null))) .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(ModdedCommandSuggestions.REGION_KEYS) - .executes((CommandContext context) -> ModdedEditCommands.editRegion(context.getSource(), StringArgumentType.getString(context, "key"))))) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedEditCommands.editRegion(context.getSource(), StringArgumentType.getString(context, "key")))))) .then(Commands.literal("dimension") - .executes((CommandContext context) -> ModdedEditCommands.editDimension(context.getSource()))) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedEditCommands.editDimension(context.getSource())))) .then(Commands.literal("d") - .executes((CommandContext context) -> ModdedEditCommands.editDimension(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedEditCommands.editDimension(context.getSource())))); } } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDatapackCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDatapackCommands.java index 43fe5d559..a7642c62d 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDatapackCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDatapackCommands.java @@ -64,18 +64,18 @@ public final class ModdedDatapackCommands { public static LiteralArgumentBuilder tree(String name) { LiteralArgumentBuilder root = Commands.literal(name).requires(GATE); - root.executes((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name)); + root.executes(ModdedCommandTree.localized((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name))); root.then(Commands.literal("status") - .executes((CommandContext context) -> status(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> status(context.getSource())))); root.then(Commands.literal("install") - .executes((CommandContext context) -> install(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> install(context.getSource())))); root.then(Commands.literal("list") - .executes((CommandContext context) -> list(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> list(context.getSource())))); root.then(Commands.literal("ls") - .executes((CommandContext context) -> list(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> list(context.getSource())))); root.then(message("ingest", "Managed Modrinth datapack ingest is Bukkit-only. On modded servers install the datapack folder or zip in world/datapacks, enable it, restart, then use /iris datapack list to confirm it is enabled. Registered structures generate natively in Iris dimensions.")); root.then(message("pull", "Managed Modrinth datapack ingest is Bukkit-only. On modded servers install the datapack folder or zip in world/datapacks, enable it, restart, then use /iris datapack list to confirm it is enabled. Registered structures generate natively in Iris dimensions.")); @@ -88,15 +88,15 @@ public final class ModdedDatapackCommands { private static LiteralArgumentBuilder message(String name, String text) { return Commands.literal(name) - .executes((CommandContext context) -> { + .executes(ModdedCommandTree.localized((CommandContext context) -> { IrisModdedCommands.fail(context.getSource(), text); return 0; - }) + })) .then(Commands.argument("args", StringArgumentType.greedyString()) - .executes((CommandContext context) -> { + .executes(ModdedCommandTree.localized((CommandContext context) -> { IrisModdedCommands.fail(context.getSource(), text); return 0; - })); + }))); } private static File worldDatapacksFolder(MinecraftServer server) { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDeveloperCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDeveloperCommands.java index 1188d9cc3..ade09c587 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDeveloperCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedDeveloperCommands.java @@ -42,11 +42,11 @@ final class ModdedDeveloperCommands { static LiteralArgumentBuilder tree(String name) { return Commands.literal(name).requires(GATE) - .executes((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), "developer")) + .executes(ModdedCommandTree.localized((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), "developer"))) .then(Commands.literal("network") - .executes((CommandContext context) -> network(context.getSource()))) + .executes(ModdedCommandTree.localized((CommandContext context) -> network(context.getSource())))) .then(Commands.literal("ip") - .executes((CommandContext context) -> network(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> network(context.getSource())))); } private static int network(CommandSourceStack source) { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedLanguageCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedLanguageCommands.java new file mode 100644 index 000000000..e9c90214c --- /dev/null +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedLanguageCommands.java @@ -0,0 +1,92 @@ +package art.arcane.iris.modded.command; + +import art.arcane.iris.core.localization.IrisLanguage; +import art.arcane.iris.spi.IrisLogging; +import art.arcane.volmlib.util.director.help.DirectorHelpMessages; +import art.arcane.volmlib.util.localization.PluginLanguageService; +import art.arcane.volmlib.util.localization.VolmitLocales; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +final class ModdedLanguageCommands { + private ModdedLanguageCommands() { + } + + static LiteralArgumentBuilder tree() { + return Commands.literal("language") + .executes(ModdedCommandTree.localized(context -> open(context.getSource()))) + .then(scope("self", false)) + .then(scope("server", true).requires(Commands.hasPermission(Commands.LEVEL_GAMEMASTERS))); + } + + private static LiteralArgumentBuilder scope(String name, boolean server) { + return Commands.literal(name) + .then(Commands.argument("locale", StringArgumentType.word()) + .suggests((context, builder) -> SharedSuggestionProvider.suggest(IrisLanguage.availableLocales(), builder)) + .executes(ModdedCommandTree.localized(context -> select(context.getSource(), server, StringArgumentType.getString(context, "locale"))))); + } + + private static int open(CommandSourceStack source) { + ModdedCommandFeedback.clear(source); + ModdedCommandFeedback.send(source, ModdedCommandFeedback.banner("/iris language")); + ModdedCommandFeedback.send(source, ModdedCommandFeedback.button( + "〈 " + IrisLanguage.plain(DirectorHelpMessages.BACK), "/iris", "Iris", true)); + ModdedCommandFeedback.send(source, ModdedCommandFeedback.text( + "Iris language: " + IrisLanguage.activeLocale(), ModdedCommandFeedback.DESCRIPTION)); + for (String locale : IrisLanguage.availableLocales()) { + String name = VolmitLocales.displayName(locale).orElse(locale); + ModdedCommandFeedback.send(source, ModdedCommandFeedback.text("⇀ ", ModdedCommandFeedback.DARK_GREEN) + .append(ModdedCommandFeedback.button(name + " [me]", "/iris language self " + locale, "Use " + name, true)) + .append(ModdedCommandTree.isGamemaster(source) + ? ModdedCommandFeedback.button(" [server]", "/iris language server " + locale, "Set the server default", true) + : Component.empty())); + } + ModdedCommandFeedback.send(source, ModdedCommandFeedback.button( + "Use server default", "/iris language self reset", "Clear your personal language", true)); + ModdedCommandFeedback.ok(source, ModdedCommandFeedback.footer()); + return 1; + } + + private static int select(CommandSourceStack source, boolean server, String locale) { + PluginLanguageService service = IrisLanguage.selections(); + if (service == null) { + source.sendFailure(Component.literal("Iris language service is unavailable.")); + return 0; + } + CompletableFuture selection; + if (server) { + selection = service.selectDefault(locale).thenApply(ignored -> service.defaultLocale()); + } else { + ServerPlayer player = source.getPlayer(); + if (player == null) { + source.sendFailure(Component.literal("Personal language selection requires a player.")); + return 0; + } + UUID playerId = player.getUUID(); + CompletableFuture personal = locale.equals("reset") + ? service.clearPlayer(playerId) : service.selectPlayer(playerId, locale); + selection = personal.thenApply(ignored -> service.effectiveLocale(playerId)); + } + selection.whenComplete((activeLocale, failure) -> source.getServer().execute(() -> { + if (failure != null) { + IrisLogging.reportError(failure); + source.sendFailure(Component.literal("Language selection failed. See the server log.")); + } else if (!locale.equals("reset") + && !locale.replace('-', '_').equalsIgnoreCase(activeLocale.replace('-', '_'))) { + source.sendSuccess(() -> Component.literal("Iris: " + locale + + " unavailable; using English (" + activeLocale + ")."), false); + } else { + source.sendSuccess(() -> Component.literal("Language updated: " + activeLocale), false); + } + })); + return 1; + } +} diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java index 1c3792707..603aa8e2d 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedObjectCommands.java @@ -107,21 +107,21 @@ public final class ModdedObjectCommands { ModdedObjectUndo.init(); LiteralArgumentBuilder root = Commands.literal(name).requires(GATE); - root.executes((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name)); + root.executes(ModdedCommandTree.localized((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name))); root.then(Commands.literal("wand") - .executes((CommandContext context) -> giveWand(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> giveWand(context.getSource())))); root.then(Commands.literal("dust") - .executes((CommandContext context) -> giveDust(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> giveDust(context.getSource())))); root.then(Commands.literal("d") - .executes((CommandContext context) -> giveDust(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> giveDust(context.getSource())))); root.then(Commands.literal("save") .then(Commands.literal("overwrite") .then(Commands.argument("name", StringArgumentType.greedyString()) - .executes((CommandContext context) -> save(context.getSource(), StringArgumentType.getString(context, "name"), true)))) + .executes(ModdedCommandTree.localized((CommandContext context) -> save(context.getSource(), StringArgumentType.getString(context, "name"), true))))) .then(Commands.argument("name", StringArgumentType.greedyString()) - .executes((CommandContext context) -> save(context.getSource(), StringArgumentType.getString(context, "name"), false)))); + .executes(ModdedCommandTree.localized((CommandContext context) -> save(context.getSource(), StringArgumentType.getString(context, "name"), false))))); root.then(pasteTree()); @@ -131,13 +131,13 @@ public final class ModdedObjectCommands { root.then(resizeTree("shift", ResizeOp.SHIFT)); root.then(Commands.literal("xpy") - .executes((CommandContext context) -> autoSelect(context.getSource(), false))); + .executes(ModdedCommandTree.localized((CommandContext context) -> autoSelect(context.getSource(), false)))); root.then(Commands.literal("x+y") - .executes((CommandContext context) -> autoSelect(context.getSource(), false))); + .executes(ModdedCommandTree.localized((CommandContext context) -> autoSelect(context.getSource(), false)))); root.then(Commands.literal("xay") - .executes((CommandContext context) -> autoSelect(context.getSource(), true))); + .executes(ModdedCommandTree.localized((CommandContext context) -> autoSelect(context.getSource(), true)))); root.then(Commands.literal("x&y") - .executes((CommandContext context) -> autoSelect(context.getSource(), true))); + .executes(ModdedCommandTree.localized((CommandContext context) -> autoSelect(context.getSource(), true)))); root.then(positionTree("position1", true)); root.then(positionTree("p1", true)); @@ -146,27 +146,27 @@ public final class ModdedObjectCommands { root.then(Commands.literal("analyze") .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(OBJECT_KEYS) - .executes((CommandContext context) -> analyze(context.getSource(), StringArgumentType.getString(context, "key"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> analyze(context.getSource(), StringArgumentType.getString(context, "key")))))); root.then(Commands.literal("shrink") .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(OBJECT_KEYS) - .executes((CommandContext context) -> shrink(context.getSource(), StringArgumentType.getString(context, "key"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> shrink(context.getSource(), StringArgumentType.getString(context, "key")))))); root.then(Commands.literal("undo") - .executes((CommandContext context) -> undo(context.getSource(), 1)) + .executes(ModdedCommandTree.localized((CommandContext context) -> undo(context.getSource(), 1))) .then(Commands.argument("amount", IntegerArgumentType.integer(1, 32)) - .executes((CommandContext context) -> undo(context.getSource(), IntegerArgumentType.getInteger(context, "amount"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> undo(context.getSource(), IntegerArgumentType.getInteger(context, "amount")))))); root.then(Commands.literal("u") - .executes((CommandContext context) -> undo(context.getSource(), 1)) + .executes(ModdedCommandTree.localized((CommandContext context) -> undo(context.getSource(), 1))) .then(Commands.argument("amount", IntegerArgumentType.integer(1, 32)) - .executes((CommandContext context) -> undo(context.getSource(), IntegerArgumentType.getInteger(context, "amount"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> undo(context.getSource(), IntegerArgumentType.getInteger(context, "amount")))))); root.then(bukkitOnly("we", "WorldEdit selection import requires the Bukkit plugin with WorldEdit installed.")); root.then(bukkitOnly("studio", "The object studio world requires the Bukkit studio toolchain; it is not available on modded servers.")); root.then(bukkitOnly("convert", "Schematic conversion (.schem -> .iob) requires the Bukkit plugin.")); root.then(Commands.literal("plausibilize") .then(Commands.argument("args", StringArgumentType.greedyString()).suggests(OBJECT_KEYS) - .executes((CommandContext context) -> plausibilize(context.getSource(), StringArgumentType.getString(context, "args"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> plausibilize(context.getSource(), StringArgumentType.getString(context, "args")))))); return root; } @@ -174,12 +174,12 @@ public final class ModdedObjectCommands { private static LiteralArgumentBuilder pasteTree() { LiteralArgumentBuilder paste = Commands.literal("paste"); paste.then(Commands.argument("key", StringArgumentType.greedyString()).suggests(OBJECT_KEYS) - .executes((CommandContext context) -> paste(context.getSource(), StringArgumentType.getString(context, "key"), 0, null))); + .executes(ModdedCommandTree.localized((CommandContext context) -> paste(context.getSource(), StringArgumentType.getString(context, "key"), 0, null)))); paste.then(Commands.literal("rotate") .then(Commands.argument("degrees", IntegerArgumentType.integer(-270, 270)).suggests(ROTATIONS) .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(OBJECT_KEYS) - .executes((CommandContext context) -> paste(context.getSource(), StringArgumentType.getString(context, "key"), - IntegerArgumentType.getInteger(context, "degrees"), null))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> paste(context.getSource(), StringArgumentType.getString(context, "key"), + IntegerArgumentType.getInteger(context, "degrees"), null)))))); paste.then(Commands.literal("at") .then(Commands.argument("x", IntegerArgumentType.integer()) .then(Commands.argument("y", IntegerArgumentType.integer()) @@ -187,35 +187,35 @@ public final class ModdedObjectCommands { .then(Commands.literal("rotate") .then(Commands.argument("degrees", IntegerArgumentType.integer(-270, 270)).suggests(ROTATIONS) .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(OBJECT_KEYS) - .executes((CommandContext context) -> paste(context.getSource(), StringArgumentType.getString(context, "key"), + .executes(ModdedCommandTree.localized((CommandContext context) -> paste(context.getSource(), StringArgumentType.getString(context, "key"), IntegerArgumentType.getInteger(context, "degrees"), - new BlockPos(IntegerArgumentType.getInteger(context, "x"), IntegerArgumentType.getInteger(context, "y"), IntegerArgumentType.getInteger(context, "z"))))))) + new BlockPos(IntegerArgumentType.getInteger(context, "x"), IntegerArgumentType.getInteger(context, "y"), IntegerArgumentType.getInteger(context, "z")))))))) .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(OBJECT_KEYS) - .executes((CommandContext context) -> paste(context.getSource(), StringArgumentType.getString(context, "key"), 0, - new BlockPos(IntegerArgumentType.getInteger(context, "x"), IntegerArgumentType.getInteger(context, "y"), IntegerArgumentType.getInteger(context, "z"))))))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> paste(context.getSource(), StringArgumentType.getString(context, "key"), 0, + new BlockPos(IntegerArgumentType.getInteger(context, "x"), IntegerArgumentType.getInteger(context, "y"), IntegerArgumentType.getInteger(context, "z")))))))))); return paste; } private static LiteralArgumentBuilder resizeTree(String name, ResizeOp op) { return Commands.literal(name) - .executes((CommandContext context) -> resize(context.getSource(), 1, op)) + .executes(ModdedCommandTree.localized((CommandContext context) -> resize(context.getSource(), 1, op))) .then(Commands.argument("amount", IntegerArgumentType.integer(1, 256)) - .executes((CommandContext context) -> resize(context.getSource(), IntegerArgumentType.getInteger(context, "amount"), op))); + .executes(ModdedCommandTree.localized((CommandContext context) -> resize(context.getSource(), IntegerArgumentType.getInteger(context, "amount"), op)))); } private static LiteralArgumentBuilder positionTree(String name, boolean first) { return Commands.literal(name) - .executes((CommandContext context) -> position(context.getSource(), first, false)) + .executes(ModdedCommandTree.localized((CommandContext context) -> position(context.getSource(), first, false))) .then(Commands.literal("look") - .executes((CommandContext context) -> position(context.getSource(), first, true))); + .executes(ModdedCommandTree.localized((CommandContext context) -> position(context.getSource(), first, true)))); } private static LiteralArgumentBuilder bukkitOnly(String name, String message) { return Commands.literal(name) - .executes((CommandContext context) -> { + .executes(ModdedCommandTree.localized((CommandContext context) -> { IrisModdedCommands.fail(context.getSource(), message); return 0; - }); + })); } public static int giveWand(CommandSourceStack source) { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPackCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPackCommands.java index 10a7dd589..085298089 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPackCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedPackCommands.java @@ -51,47 +51,47 @@ public final class ModdedPackCommands { public static LiteralArgumentBuilder tree(String name) { LiteralArgumentBuilder root = Commands.literal(name).requires(GATE); - root.executes((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name)); + root.executes(ModdedCommandTree.localized((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name))); root.then(Commands.literal("validate") - .executes((CommandContext context) -> validate(context.getSource(), null)) + .executes(ModdedCommandTree.localized((CommandContext context) -> validate(context.getSource(), null))) .then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> validate(context.getSource(), StringArgumentType.getString(context, "pack"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> validate(context.getSource(), StringArgumentType.getString(context, "pack")))))); root.then(Commands.literal("v") - .executes((CommandContext context) -> validate(context.getSource(), null)) + .executes(ModdedCommandTree.localized((CommandContext context) -> validate(context.getSource(), null))) .then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> validate(context.getSource(), StringArgumentType.getString(context, "pack"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> validate(context.getSource(), StringArgumentType.getString(context, "pack")))))); root.then(Commands.literal("cleanup") .then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> cleanup(context.getSource(), StringArgumentType.getString(context, "pack"), false)) + .executes(ModdedCommandTree.localized((CommandContext context) -> cleanup(context.getSource(), StringArgumentType.getString(context, "pack"), false))) .then(Commands.literal("apply") - .executes((CommandContext context) -> cleanup(context.getSource(), StringArgumentType.getString(context, "pack"), true))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> cleanup(context.getSource(), StringArgumentType.getString(context, "pack"), true)))))); root.then(Commands.literal("c") .then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> cleanup(context.getSource(), StringArgumentType.getString(context, "pack"), false)) + .executes(ModdedCommandTree.localized((CommandContext context) -> cleanup(context.getSource(), StringArgumentType.getString(context, "pack"), false))) .then(Commands.literal("apply") - .executes((CommandContext context) -> cleanup(context.getSource(), StringArgumentType.getString(context, "pack"), true))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> cleanup(context.getSource(), StringArgumentType.getString(context, "pack"), true)))))); root.then(Commands.literal("restore") .then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> restore(context.getSource(), StringArgumentType.getString(context, "pack"), false)) + .executes(ModdedCommandTree.localized((CommandContext context) -> restore(context.getSource(), StringArgumentType.getString(context, "pack"), false))) .then(Commands.literal("apply") - .executes((CommandContext context) -> restore(context.getSource(), StringArgumentType.getString(context, "pack"), true))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> restore(context.getSource(), StringArgumentType.getString(context, "pack"), true)))))); root.then(Commands.literal("r") .then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> restore(context.getSource(), StringArgumentType.getString(context, "pack"), false)) + .executes(ModdedCommandTree.localized((CommandContext context) -> restore(context.getSource(), StringArgumentType.getString(context, "pack"), false))) .then(Commands.literal("apply") - .executes((CommandContext context) -> restore(context.getSource(), StringArgumentType.getString(context, "pack"), true))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> restore(context.getSource(), StringArgumentType.getString(context, "pack"), true)))))); root.then(Commands.literal("status") - .executes((CommandContext context) -> status(context.getSource(), null)) + .executes(ModdedCommandTree.localized((CommandContext context) -> status(context.getSource(), null))) .then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> status(context.getSource(), StringArgumentType.getString(context, "pack"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> status(context.getSource(), StringArgumentType.getString(context, "pack")))))); root.then(Commands.literal("s") - .executes((CommandContext context) -> status(context.getSource(), null)) + .executes(ModdedCommandTree.localized((CommandContext context) -> status(context.getSource(), null))) .then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> status(context.getSource(), StringArgumentType.getString(context, "pack"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> status(context.getSource(), StringArgumentType.getString(context, "pack")))))); return root; } diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStructureCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStructureCommands.java index 116f3ba2f..d135b9621 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStructureCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStructureCommands.java @@ -63,23 +63,23 @@ public final class ModdedStructureCommands { public static LiteralArgumentBuilder tree(String name) { LiteralArgumentBuilder root = Commands.literal(name).requires(GATE); - root.executes((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name)); + root.executes(ModdedCommandTree.localized((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name))); root.then(Commands.literal("list") - .executes((CommandContext context) -> list(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> list(context.getSource())))); root.then(Commands.literal("ls") - .executes((CommandContext context) -> list(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> list(context.getSource())))); root.then(Commands.literal("info") .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(IRIS_STRUCTURE_KEYS) - .executes((CommandContext context) -> info(context.getSource(), StringArgumentType.getString(context, "key"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> info(context.getSource(), StringArgumentType.getString(context, "key")))))); root.then(Commands.literal("place") .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(IRIS_STRUCTURE_KEYS) - .executes((CommandContext context) -> place(context.getSource(), StringArgumentType.getString(context, "key"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> place(context.getSource(), StringArgumentType.getString(context, "key")))))); root.then(Commands.literal("p") .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(IRIS_STRUCTURE_KEYS) - .executes((CommandContext context) -> place(context.getSource(), StringArgumentType.getString(context, "key"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> place(context.getSource(), StringArgumentType.getString(context, "key")))))); root.then(message("import", "Structure import rebuilds vanilla & datapack structures as editable Iris resources through Bukkit/NMS template managers; run /iris structure import on a Bukkit server against this pack, then copy the pack folder over.")); root.then(message("import-all", "Structure import rebuilds vanilla & datapack structures as editable Iris resources through Bukkit/NMS template managers; run /iris structure import on a Bukkit server against this pack, then copy the pack folder over.")); @@ -96,23 +96,23 @@ public final class ModdedStructureCommands { private static LiteralArgumentBuilder verifyTree(String name) { return Commands.literal(name) - .executes((CommandContext context) -> IrisModdedCommands.verifyStructures(context.getSource(), null)) + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.verifyStructures(context.getSource(), null))) .then(Commands.argument("key", StringArgumentType.greedyString()).suggests(ALL_STRUCTURE_KEYS) - .executes((CommandContext context) -> IrisModdedCommands.verifyStructures( - context.getSource(), StringArgumentType.getString(context, "key")))); + .executes(ModdedCommandTree.localized((CommandContext context) -> IrisModdedCommands.verifyStructures( + context.getSource(), StringArgumentType.getString(context, "key"))))); } private static LiteralArgumentBuilder message(String name, String text) { return Commands.literal(name) - .executes((CommandContext context) -> { + .executes(ModdedCommandTree.localized((CommandContext context) -> { IrisModdedCommands.fail(context.getSource(), text); return 0; - }) + })) .then(Commands.argument("args", StringArgumentType.greedyString()) - .executes((CommandContext context) -> { + .executes(ModdedCommandTree.localized((CommandContext context) -> { IrisModdedCommands.fail(context.getSource(), text); return 0; - })); + }))); } private static IrisData dataFor(CommandSourceStack source) { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStudioCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStudioCommands.java index 2acd6744c..5f52089d4 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStudioCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedStudioCommands.java @@ -118,7 +118,7 @@ public final class ModdedStudioCommands { public static LiteralArgumentBuilder tree(String name) { LiteralArgumentBuilder root = Commands.literal(name).requires(GATE); - root.executes((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name)); + root.executes(ModdedCommandTree.localized((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name))); root.then(createTree("create")); root.then(createTree("+")); @@ -127,27 +127,27 @@ public final class ModdedStudioCommands { root.then(packageTree("pkg")); root.then(Commands.literal("version") - .executes((CommandContext context) -> version(context.getSource(), null)) + .executes(ModdedCommandTree.localized((CommandContext context) -> version(context.getSource(), null))) .then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> version(context.getSource(), StringArgumentType.getString(context, "pack"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> version(context.getSource(), StringArgumentType.getString(context, "pack")))))); root.then(Commands.literal("regions") - .executes((CommandContext context) -> regions(context.getSource(), 500)) + .executes(ModdedCommandTree.localized((CommandContext context) -> regions(context.getSource(), 500))) .then(Commands.argument("radius", IntegerArgumentType.integer(8, 1000)) - .executes((CommandContext context) -> regions(context.getSource(), IntegerArgumentType.getInteger(context, "radius"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> regions(context.getSource(), IntegerArgumentType.getInteger(context, "radius")))))); root.then(openTree("open")); root.then(openTree("o")); root.then(Commands.literal("close") - .executes((CommandContext context) -> close(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> close(context.getSource())))); root.then(Commands.literal("x") - .executes((CommandContext context) -> close(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> close(context.getSource())))); root.then(Commands.literal("tpstudio") - .executes((CommandContext context) -> tpStudio(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> tpStudio(context.getSource())))); root.then(Commands.literal("stp") - .executes((CommandContext context) -> tpStudio(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> tpStudio(context.getSource())))); root.then(Commands.literal("status") - .executes((CommandContext context) -> status(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> status(context.getSource())))); root.then(workspaceTree("vscode", true)); root.then(workspaceTree("vsc", true)); root.then(workspaceTree("update", false)); @@ -172,26 +172,26 @@ public final class ModdedStudioCommands { private static LiteralArgumentBuilder createTree(String name) { return Commands.literal(name) - .executes((CommandContext context) -> - create(context.getSource(), "studio", DEFAULT_TEMPLATE)) + .executes(ModdedCommandTree.localized((CommandContext context) -> + create(context.getSource(), "studio", DEFAULT_TEMPLATE))) .then(Commands.argument("name", StringArgumentType.word()) - .executes((CommandContext context) -> - create(context.getSource(), StringArgumentType.getString(context, "name"), DEFAULT_TEMPLATE)) + .executes(ModdedCommandTree.localized((CommandContext context) -> + create(context.getSource(), StringArgumentType.getString(context, "name"), DEFAULT_TEMPLATE))) .then(Commands.argument("template", StringArgumentType.word()) .suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> + .executes(ModdedCommandTree.localized((CommandContext context) -> create(context.getSource(), StringArgumentType.getString(context, "name"), - StringArgumentType.getString(context, "template"))))); + StringArgumentType.getString(context, "template")))))); } private static LiteralArgumentBuilder packageTree(String name) { return Commands.literal(name) - .executes((CommandContext context) -> pkg(context.getSource(), null)) + .executes(ModdedCommandTree.localized((CommandContext context) -> pkg(context.getSource(), null))) .then(Commands.argument("pack", StringArgumentType.word()) .suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> - pkg(context.getSource(), StringArgumentType.getString(context, "pack")))); + .executes(ModdedCommandTree.localized((CommandContext context) -> + pkg(context.getSource(), StringArgumentType.getString(context, "pack"))))); } public static void clear() { @@ -201,11 +201,11 @@ public final class ModdedStudioCommands { private static LiteralArgumentBuilder openTree(String name) { return Commands.literal(name) - .executes((CommandContext context) -> openHelp(context.getSource())) + .executes(ModdedCommandTree.localized((CommandContext context) -> openHelp(context.getSource()))) .then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> open(context.getSource(), StringArgumentType.getString(context, "pack"), 1337L)) + .executes(ModdedCommandTree.localized((CommandContext context) -> open(context.getSource(), StringArgumentType.getString(context, "pack"), 1337L))) .then(Commands.argument("seed", LongArgumentType.longArg()) - .executes((CommandContext context) -> open(context.getSource(), StringArgumentType.getString(context, "pack"), LongArgumentType.getLong(context, "seed"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> open(context.getSource(), StringArgumentType.getString(context, "pack"), LongArgumentType.getLong(context, "seed")))))); } private static int openHelp(CommandSourceStack source) { @@ -215,21 +215,21 @@ public final class ModdedStudioCommands { private static LiteralArgumentBuilder noiseTree(String name) { return Commands.literal(name) - .executes((CommandContext context) -> noise(context.getSource(), null, 12345L)) + .executes(ModdedCommandTree.localized((CommandContext context) -> noise(context.getSource(), null, 12345L))) .then(Commands.argument("generator", StringArgumentType.word()).suggests(GENERATOR_KEYS) - .executes((CommandContext context) -> noise(context.getSource(), StringArgumentType.getString(context, "generator"), 12345L)) + .executes(ModdedCommandTree.localized((CommandContext context) -> noise(context.getSource(), StringArgumentType.getString(context, "generator"), 12345L))) .then(Commands.argument("seed", LongArgumentType.longArg()) - .executes((CommandContext context) -> noise(context.getSource(), StringArgumentType.getString(context, "generator"), LongArgumentType.getLong(context, "seed"))))); + .executes(ModdedCommandTree.localized((CommandContext context) -> noise(context.getSource(), StringArgumentType.getString(context, "generator"), LongArgumentType.getLong(context, "seed")))))); } private static LiteralArgumentBuilder mapTree(String name) { return Commands.literal(name) - .executes((CommandContext context) -> map(context.getSource())); + .executes(ModdedCommandTree.localized((CommandContext context) -> map(context.getSource()))); } private static LiteralArgumentBuilder imageMapTree(String name) { return Commands.literal(name) - .executes((CommandContext context) -> imageMap(context.getSource())); + .executes(ModdedCommandTree.localized((CommandContext context) -> imageMap(context.getSource()))); } private static int noise(CommandSourceStack source, String generatorKey, long seed) { @@ -306,9 +306,9 @@ public final class ModdedStudioCommands { private static LiteralArgumentBuilder workspaceTree(String name, boolean open) { return Commands.literal(name) - .executes((CommandContext context) -> workspace(context.getSource(), null, open)) + .executes(ModdedCommandTree.localized((CommandContext context) -> workspace(context.getSource(), null, open))) .then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> workspace(context.getSource(), StringArgumentType.getString(context, "pack"), open))); + .executes(ModdedCommandTree.localized((CommandContext context) -> workspace(context.getSource(), StringArgumentType.getString(context, "pack"), open)))); } private static int workspace(CommandSourceStack source, String pack, boolean open) { @@ -355,15 +355,15 @@ public final class ModdedStudioCommands { private static LiteralArgumentBuilder message(String name, String text) { return Commands.literal(name) - .executes((CommandContext context) -> { + .executes(ModdedCommandTree.localized((CommandContext context) -> { IrisModdedCommands.fail(context.getSource(), text); return 0; - }) + })) .then(Commands.argument("args", StringArgumentType.greedyString()) - .executes((CommandContext context) -> { + .executes(ModdedCommandTree.localized((CommandContext context) -> { IrisModdedCommands.fail(context.getSource(), text); return 0; - })); + }))); } private static File resolvePack(CommandSourceStack source, String pack) { @@ -905,6 +905,7 @@ public final class ModdedStudioCommands { lootKeys.addAll(dimension.getLoot().getTables()); spawnerKeys.addAll(dimension.getEntitySpawners()); collectStructureKeys(structureKeys, dimension.getStructures()); + objectKeys.addAll(PackExportClosure.collectStaticObjectKeys(dimension.getStaticObjects())); for (String regionKey : regionKeys) { IrisRegion region = dm.getRegionLoader().load(regionKey); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWhatCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWhatCommands.java index e87c12958..e4e138f66 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWhatCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWhatCommands.java @@ -92,30 +92,30 @@ public final class ModdedWhatCommands { public static LiteralArgumentBuilder tree() { return Commands.literal("what").requires(GATE) - .executes((CommandContext context) -> - inspectHere(context.getSource())) + .executes(ModdedCommandTree.localized((CommandContext context) -> + inspectHere(context.getSource()))) .then(Commands.literal("here") - .executes((CommandContext context) -> - inspectHere(context.getSource()))) + .executes(ModdedCommandTree.localized((CommandContext context) -> + inspectHere(context.getSource())))) .then(Commands.literal("biome") - .executes((CommandContext context) -> - inspectBiome(context.getSource()))) + .executes(ModdedCommandTree.localized((CommandContext context) -> + inspectBiome(context.getSource())))) .then(Commands.literal("region") - .executes((CommandContext context) -> - inspectRegion(context.getSource()))) + .executes(ModdedCommandTree.localized((CommandContext context) -> + inspectRegion(context.getSource())))) .then(Commands.literal("block") - .executes((CommandContext context) -> - inspectBlock(context.getSource()))) + .executes(ModdedCommandTree.localized((CommandContext context) -> + inspectBlock(context.getSource())))) .then(Commands.literal("hand") - .executes((CommandContext context) -> - inspectHand(context.getSource()))) + .executes(ModdedCommandTree.localized((CommandContext context) -> + inspectHand(context.getSource())))) .then(Commands.literal("markers") .then(Commands.argument("marker", StringArgumentType.greedyString()) .suggests(MARKER_TYPES) - .executes((CommandContext context) -> + .executes(ModdedCommandTree.localized((CommandContext context) -> inspectMarkers( context.getSource(), - StringArgumentType.getString(context, "marker"))))); + StringArgumentType.getString(context, "marker")))))); } static void clear() { diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java index b2bf115a1..a4bc4c9fc 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java @@ -67,14 +67,14 @@ public final class ModdedWorldCommands { public static LiteralArgumentBuilder tree(String name) { LiteralArgumentBuilder root = Commands.literal(name).requires(GATE); - root.executes((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name)); + root.executes(ModdedCommandTree.localized((CommandContext context) -> ModdedCommandHelp.send(context.getSource(), name))); root.then(Commands.literal("status") - .executes((CommandContext context) -> status(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> status(context.getSource())))); root.then(Commands.literal("list") - .executes((CommandContext context) -> list(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> list(context.getSource())))); root.then(Commands.literal("ls") - .executes((CommandContext context) -> list(context.getSource()))); + .executes(ModdedCommandTree.localized((CommandContext context) -> list(context.getSource())))); root.then(enableTree("enable")); root.then(enableTree("create")); @@ -93,53 +93,53 @@ public final class ModdedWorldCommands { return Commands.literal(name) .then(Commands.argument("dimension", IdentifierArgument.id()) .then(Commands.argument("pack", StringArgumentType.string()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> enable(context.getSource(), + .executes(ModdedCommandTree.localized((CommandContext context) -> enable(context.getSource(), dimensionArgument(context), StringArgumentType.getString(context, "pack"), - null)) + null))) .then(Commands.argument("seed", StringArgumentType.word()) - .executes((CommandContext context) -> enable(context.getSource(), + .executes(ModdedCommandTree.localized((CommandContext context) -> enable(context.getSource(), dimensionArgument(context), StringArgumentType.getString(context, "pack"), - StringArgumentType.getString(context, "seed")))))); + StringArgumentType.getString(context, "seed"))))))); } private static LiteralArgumentBuilder disableTree() { return Commands.literal("disable") .then(Commands.argument("dimension", IdentifierArgument.id()).suggests(LOADED_DIMENSIONS) - .executes((CommandContext context) -> disable(context.getSource(), dimensionArgument(context), false))); + .executes(ModdedCommandTree.localized((CommandContext context) -> disable(context.getSource(), dimensionArgument(context), false)))); } private static LiteralArgumentBuilder deleteTree(String name) { return Commands.literal(name) .then(Commands.argument("dimension", IdentifierArgument.id()).suggests(LOADED_DIMENSIONS) - .executes((CommandContext context) -> disable(context.getSource(), dimensionArgument(context), true))); + .executes(ModdedCommandTree.localized((CommandContext context) -> disable(context.getSource(), dimensionArgument(context), true)))); } private static LiteralArgumentBuilder replaceOverworldTree() { return Commands.literal("replace-overworld") .then(Commands.argument("pack", StringArgumentType.string()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> replaceOverworld(context.getSource(), + .executes(ModdedCommandTree.localized((CommandContext context) -> replaceOverworld(context.getSource(), StringArgumentType.getString(context, "pack"), - null)) + null))) .then(Commands.argument("seed", StringArgumentType.word()) - .executes((CommandContext context) -> replaceOverworld(context.getSource(), + .executes(ModdedCommandTree.localized((CommandContext context) -> replaceOverworld(context.getSource(), StringArgumentType.getString(context, "pack"), - StringArgumentType.getString(context, "seed"))))); + StringArgumentType.getString(context, "seed")))))); } private static LiteralArgumentBuilder mainWorldTree() { return Commands.literal("mainworld") .then(Commands.literal("off") - .executes((CommandContext context) -> clearMainWorld(context.getSource()))) + .executes(ModdedCommandTree.localized((CommandContext context) -> clearMainWorld(context.getSource())))) .then(Commands.argument("pack", StringArgumentType.string()).suggests(IrisModdedCommands.PACK_NAMES) - .executes((CommandContext context) -> mainWorld(context.getSource(), + .executes(ModdedCommandTree.localized((CommandContext context) -> mainWorld(context.getSource(), StringArgumentType.getString(context, "pack"), - null)) + null))) .then(Commands.argument("seed", StringArgumentType.word()) - .executes((CommandContext context) -> mainWorld(context.getSource(), + .executes(ModdedCommandTree.localized((CommandContext context) -> mainWorld(context.getSource(), StringArgumentType.getString(context, "pack"), - StringArgumentType.getString(context, "seed"))))); + StringArgumentType.getString(context, "seed")))))); } public static int createWorld(CommandSourceStack source, String name, String pack, long seed) { diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/IrisModdedCommandParityTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/IrisModdedCommandParityTest.java index 05c66d837..0b08fddde 100644 --- a/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/IrisModdedCommandParityTest.java +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/IrisModdedCommandParityTest.java @@ -30,6 +30,10 @@ public class IrisModdedCommandParityTest { IrisModdedCommands.register(dispatcher); CommandNode iris = child(dispatcher.getRoot(), "iris"); + CommandNode language = child(iris, "language"); + assertNotNull(language.getCommand()); + child(child(language, "self"), "locale"); + child(child(language, "server"), "locale"); CommandNode what = child(iris, "what"); child(what, "here"); child(what, "biome"); diff --git a/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModdedLanguageCommandsTest.java b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModdedLanguageCommandsTest.java new file mode 100644 index 000000000..976c02d24 --- /dev/null +++ b/adapters/modded-common/src/test/java/art/arcane/iris/modded/command/ModdedLanguageCommandsTest.java @@ -0,0 +1,125 @@ +package art.arcane.iris.modded.command; + +import art.arcane.iris.core.localization.IrisLanguage; +import art.arcane.volmlib.util.director.help.DirectorHelpMessages; +import art.arcane.volmlib.util.localization.PluginLanguageService; +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.SharedConstants; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.server.Bootstrap; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerPlayer; +import org.junit.Test; +import org.mockito.MockedStatic; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +public class ModdedLanguageCommandsTest { + @Test + public void personalFailureReportsTheCommittedEnglishFallback() throws Exception { + assertPersonalCompletion("de_DE", "en_US", "Iris: de_DE unavailable; using English (en_US)."); + } + + @Test + public void personalResetReportsTheInheritedLanguage() throws Exception { + assertPersonalCompletion("reset", "fr_FR", "Language updated: fr_FR"); + } + + @Test + public void personalSuccessReportsTheSelectedLanguage() throws Exception { + assertPersonalCompletion("de_DE", "de_DE", "Language updated: de_DE"); + } + + @Test + public void pickerRendersTheNativeHelpFrameAndLanguageActions() throws Exception { + SharedConstants.tryDetectVersion(); + Bootstrap.bootStrap(); + CommandSourceStack source = mock(CommandSourceStack.class, RETURNS_DEEP_STUBS); + when(source.getPlayer()).thenReturn(null); + List rendered = new ArrayList<>(); + doAnswer(invocation -> { + Supplier message = invocation.getArgument(0); + rendered.add(message.get()); + return null; + }).when(source).sendSuccess(any(), eq(false)); + + try (MockedStatic language = mockStatic(IrisLanguage.class)) { + language.when(IrisLanguage::activeLocale).thenReturn("en_US"); + language.when(IrisLanguage::availableLocales).thenReturn(Set.of("de_DE")); + language.when(() -> IrisLanguage.plain(DirectorHelpMessages.BACK)).thenReturn("Back"); + CommandDispatcher dispatcher = new CommandDispatcher<>(); + dispatcher.register(Commands.literal("iris").then(ModdedLanguageCommands.tree())); + + assertEquals(1, dispatcher.execute("iris language", source)); + } + + assertEquals(6, rendered.size()); + assertEquals(ModdedCommandFeedback.banner("/iris language"), rendered.getFirst()); + assertEquals(new ClickEvent.RunCommand("/iris"), rendered.get(1).getStyle().getClickEvent()); + assertEquals("Iris language: en_US", rendered.get(2).getString()); + Component locale = rendered.get(3); + assertTrue(locale.getString().contains("[me]")); + assertFalse(locale.getString().contains("[server]")); + assertEquals(new ClickEvent.RunCommand("/iris language self de_DE"), + locale.getSiblings().getFirst().getStyle().getClickEvent()); + assertEquals(new ClickEvent.RunCommand("/iris language self reset"), rendered.get(4).getStyle().getClickEvent()); + assertEquals(ModdedCommandFeedback.footer(), rendered.getLast()); + } + + private void assertPersonalCompletion(String requested, String selected, String expected) throws Exception { + SharedConstants.tryDetectVersion(); + Bootstrap.bootStrap(); + CommandSourceStack source = mock(CommandSourceStack.class, RETURNS_DEEP_STUBS); + ServerPlayer player = mock(ServerPlayer.class); + UUID playerId = UUID.randomUUID(); + when(source.getPlayer()).thenReturn(player); + when(player.getUUID()).thenReturn(playerId); + MinecraftServer server = source.getServer(); + PluginLanguageService service = mock(PluginLanguageService.class); + CompletableFuture selection = new CompletableFuture<>(); + when(service.selectPlayer(playerId, requested)).thenReturn(selection); + when(service.clearPlayer(playerId)).thenReturn(selection); + when(service.effectiveLocale(playerId)).thenReturn(selected); + List rendered = new ArrayList<>(); + doAnswer(invocation -> { + Supplier message = invocation.getArgument(0); + rendered.add(message.get()); + return null; + }).when(source).sendSuccess(any(), eq(false)); + doAnswer(invocation -> { + Runnable task = invocation.getArgument(0); + task.run(); + return null; + }).when(server).execute(any(Runnable.class)); + + try (MockedStatic language = mockStatic(IrisLanguage.class)) { + language.when(IrisLanguage::selections).thenReturn(service); + CommandDispatcher dispatcher = new CommandDispatcher<>(); + dispatcher.register(Commands.literal("iris").then(ModdedLanguageCommands.tree())); + assertEquals(1, dispatcher.execute("iris language self " + requested, source)); + assertTrue(rendered.isEmpty()); + selection.complete(null); + } + + assertEquals(List.of(expected), rendered.stream().map(Component::getString).toList()); + } +} diff --git a/core/src/main/java/art/arcane/iris/core/localization/DirectorCommandMessages.java b/core/src/main/java/art/arcane/iris/core/localization/DirectorCommandMessages.java index 08a2f8f6d..d70ab8b82 100644 --- a/core/src/main/java/art/arcane/iris/core/localization/DirectorCommandMessages.java +++ b/core/src/main/java/art/arcane/iris/core/localization/DirectorCommandMessages.java @@ -6,6 +6,8 @@ import art.arcane.volmlib.util.localization.TextKey; import java.util.List; public final class DirectorCommandMessages { + public static final TextKey DEBUG_DUMP_DESCRIPTION = TextKey.of("iris.director.commandiris.director.debugdump", "Create and optionally upload a diagnostic report"); + public static final TextKey DEBUG_DUMP_UPLOAD = TextKey.of("iris.director.commandiris.param.debugdump_upload", "Upload the report to mclo.gs"); public static final TextKey COMMAND_DATAPACK_DIRECTOR_DOWNLOAD_MANAGE_EXTERNAL_DATAPACK_IMPORTS_MODRINTH = TextKey.of( "iris.director.commanddatapack.director.download_manage_external_datapack_imports_modrinth", "Download & manage external datapack imports" @@ -864,6 +866,8 @@ public final class DirectorCommandMessages { ); private static final List KEYS = List.of( + DEBUG_DUMP_DESCRIPTION, + DEBUG_DUMP_UPLOAD, COMMAND_DATAPACK_DIRECTOR_DOWNLOAD_MANAGE_EXTERNAL_DATAPACK_IMPORTS_MODRINTH, COMMAND_DATAPACK_DIRECTOR_DOWNLOAD_UPDATE_EVERY_DATAPACK_LISTED_PACK_DIMENSION_S_DATAPACKIMPORTS_INSTALL_IT_INTO, COMMAND_DATAPACK_PARAM_RESTART_SERVER_WHEN_NEW_DATAPACKS_ARE_INSTALLED_REQUIRED_NEW_STRUCTURES_REGISTER_GENERATE, diff --git a/core/src/main/resources/languages/de_DE.json b/core/src/main/resources/languages/de_DE.json index 60e762b83..f38cd3a64 100644 --- a/core/src/main/resources/languages/de_DE.json +++ b/core/src/main/resources/languages/de_DE.json @@ -1,6 +1,8 @@ { "locale": "de_DE", "messages": { + "iris.director.commandiris.director.debugdump": "Einen Diagnosebericht erstellen und optional hochladen", + "iris.director.commandiris.param.debugdump_upload": "Den Bericht zu mclo.gs hochladen", "director.help.navigation.parent.hover": "Zur übergeordneten Befehlsgruppe zurückkehren", "director.help.navigation.back": "Zurück", "director.help.no_subcommands": "Auf dieser Seite gibt es keine Unterbefehle.", diff --git a/core/src/main/resources/languages/es_ES.json b/core/src/main/resources/languages/es_ES.json index 1594a39d9..64a335be6 100644 --- a/core/src/main/resources/languages/es_ES.json +++ b/core/src/main/resources/languages/es_ES.json @@ -1,6 +1,8 @@ { "locale": "es_ES", "messages": { + "iris.director.commandiris.director.debugdump": "Crear y subir opcionalmente un informe de diagnóstico", + "iris.director.commandiris.param.debugdump_upload": "Subir el informe a mclo.gs", "director.help.navigation.parent.hover": "Volver al grupo de comandos superior", "director.help.navigation.back": "Atrás", "director.help.no_subcommands": "No hay subcomandos en esta página.", diff --git a/core/src/main/resources/languages/fi_FI.json b/core/src/main/resources/languages/fi_FI.json index b26961ff5..b89cc7f4a 100644 --- a/core/src/main/resources/languages/fi_FI.json +++ b/core/src/main/resources/languages/fi_FI.json @@ -1,6 +1,8 @@ { "locale": "fi_FI", "messages": { + "iris.director.commandiris.director.debugdump": "Luo diagnostiikkaraportti ja lähetä se halutessasi", + "iris.director.commandiris.param.debugdump_upload": "Lähetä raportti mclo.gs-palveluun", "director.help.navigation.parent.hover": "Palaa ylempään komentoryhmään", "director.help.navigation.back": "Takaisin", "director.help.no_subcommands": "Tällä sivulla ei ole alikomentoja.", diff --git a/core/src/main/resources/languages/fr_FR.json b/core/src/main/resources/languages/fr_FR.json index b017acdb3..1e037a4d3 100644 --- a/core/src/main/resources/languages/fr_FR.json +++ b/core/src/main/resources/languages/fr_FR.json @@ -1,6 +1,8 @@ { "locale": "fr_FR", "messages": { + "iris.director.commandiris.director.debugdump": "Créer un rapport de diagnostic et le publier si souhaité", + "iris.director.commandiris.param.debugdump_upload": "Publier le rapport sur mclo.gs", "director.help.navigation.parent.hover": "Revenir au groupe de commandes parent", "director.help.navigation.back": "Retour", "director.help.no_subcommands": "Aucune sous-commande sur cette page.", diff --git a/core/src/main/resources/languages/he_IL.json b/core/src/main/resources/languages/he_IL.json index cf3124630..76ba569d4 100644 --- a/core/src/main/resources/languages/he_IL.json +++ b/core/src/main/resources/languages/he_IL.json @@ -1,6 +1,8 @@ { "locale": "he_IL", "messages": { + "iris.director.commandiris.director.debugdump": "יצירת דוח אבחון והעלאתו לפי בחירה", + "iris.director.commandiris.param.debugdump_upload": "העלאת הדוח אל mclo.gs", "director.help.navigation.parent.hover": "חזרה לקבוצת פקודות האב", "director.help.navigation.back": "חזרה", "director.help.no_subcommands": "אין פקודות משנה בעמוד זה.", diff --git a/core/src/main/resources/languages/it_IT.json b/core/src/main/resources/languages/it_IT.json index aec0c8fec..03a665cfe 100644 --- a/core/src/main/resources/languages/it_IT.json +++ b/core/src/main/resources/languages/it_IT.json @@ -1,6 +1,8 @@ { "locale": "it_IT", "messages": { + "iris.director.commandiris.director.debugdump": "Crea un rapporto diagnostico e caricalo facoltativamente", + "iris.director.commandiris.param.debugdump_upload": "Carica il rapporto su mclo.gs", "director.help.navigation.parent.hover": "Torna al gruppo di comandi principale", "director.help.navigation.back": "Indietro", "director.help.no_subcommands": "Nessun sottocomando in questa pagina.", diff --git a/core/src/main/resources/languages/ja-JP.json b/core/src/main/resources/languages/ja-JP.json index 75ac9c0cb..af41ffefc 100644 --- a/core/src/main/resources/languages/ja-JP.json +++ b/core/src/main/resources/languages/ja-JP.json @@ -1,6 +1,8 @@ { "locale": "ja-JP", "messages": { + "iris.director.commandiris.director.debugdump": "診断レポートを作成し、必要に応じてアップロードする", + "iris.director.commandiris.param.debugdump_upload": "レポートを mclo.gs にアップロードする", "director.help.navigation.parent.hover": "親コマンドグループに戻る", "director.help.navigation.back": "戻る", "director.help.no_subcommands": "このページにサブコマンドはありません。", diff --git a/core/src/main/resources/languages/ko_KR.json b/core/src/main/resources/languages/ko_KR.json index f8601297a..1f3901acf 100644 --- a/core/src/main/resources/languages/ko_KR.json +++ b/core/src/main/resources/languages/ko_KR.json @@ -1,6 +1,8 @@ { "locale": "ko_KR", "messages": { + "iris.director.commandiris.director.debugdump": "진단 보고서를 생성하고 선택적으로 업로드", + "iris.director.commandiris.param.debugdump_upload": "보고서를 mclo.gs에 업로드", "director.help.navigation.parent.hover": "상위 명령어 그룹으로 돌아가기", "director.help.navigation.back": "뒤로", "director.help.no_subcommands": "이 페이지에는 하위 명령어가 없습니다.", diff --git a/core/src/main/resources/languages/lt_LT.json b/core/src/main/resources/languages/lt_LT.json index a7940d8bf..3ed7b0a77 100644 --- a/core/src/main/resources/languages/lt_LT.json +++ b/core/src/main/resources/languages/lt_LT.json @@ -1,6 +1,8 @@ { "locale": "lt_LT", "messages": { + "iris.director.commandiris.director.debugdump": "Sukurti diagnostikos ataskaitą ir pasirinktinai ją įkelti", + "iris.director.commandiris.param.debugdump_upload": "Įkelti ataskaitą į mclo.gs", "director.help.navigation.parent.hover": "Grįžti į pirminę komandų grupę", "director.help.navigation.back": "Atgal", "director.help.no_subcommands": "Šiame puslapyje nėra antrinių komandų.", diff --git a/core/src/main/resources/languages/nl_NL.json b/core/src/main/resources/languages/nl_NL.json index 18034346c..c406a7c1c 100644 --- a/core/src/main/resources/languages/nl_NL.json +++ b/core/src/main/resources/languages/nl_NL.json @@ -1,6 +1,8 @@ { "locale": "nl_NL", "messages": { + "iris.director.commandiris.director.debugdump": "Maak een diagnostisch rapport en upload het desgewenst", + "iris.director.commandiris.param.debugdump_upload": "Upload het rapport naar mclo.gs", "director.help.navigation.parent.hover": "Terug naar de bovenliggende opdrachtgroep", "director.help.navigation.back": "Terug", "director.help.no_subcommands": "Geen subopdrachten op deze pagina.", diff --git a/core/src/main/resources/languages/pl_PL.json b/core/src/main/resources/languages/pl_PL.json index 8b30423eb..a1f85c37d 100644 --- a/core/src/main/resources/languages/pl_PL.json +++ b/core/src/main/resources/languages/pl_PL.json @@ -1,6 +1,8 @@ { "locale": "pl_PL", "messages": { + "iris.director.commandiris.director.debugdump": "Utwórz raport diagnostyczny i opcjonalnie go prześlij", + "iris.director.commandiris.param.debugdump_upload": "Prześlij raport do mclo.gs", "director.help.navigation.parent.hover": "Wróć do nadrzędnej grupy poleceń", "director.help.navigation.back": "Wstecz", "director.help.no_subcommands": "Na tej stronie nie ma podpoleceń.", diff --git a/core/src/main/resources/languages/pt_PT.json b/core/src/main/resources/languages/pt_PT.json index e6cf58cf5..452eb26de 100644 --- a/core/src/main/resources/languages/pt_PT.json +++ b/core/src/main/resources/languages/pt_PT.json @@ -1,6 +1,8 @@ { "locale": "pt_PT", "messages": { + "iris.director.commandiris.director.debugdump": "Criar um relatório de diagnóstico e carregá-lo opcionalmente", + "iris.director.commandiris.param.debugdump_upload": "Carregar o relatório para mclo.gs", "director.help.navigation.parent.hover": "Voltar ao grupo de comandos principal", "director.help.navigation.back": "Voltar", "director.help.no_subcommands": "Não existem subcomandos nesta página.", diff --git a/core/src/main/resources/languages/ru_RU.json b/core/src/main/resources/languages/ru_RU.json index a56d969a4..baf7d6e29 100644 --- a/core/src/main/resources/languages/ru_RU.json +++ b/core/src/main/resources/languages/ru_RU.json @@ -1,6 +1,8 @@ { "locale": "ru_RU", "messages": { + "iris.director.commandiris.director.debugdump": "Создать диагностический отчёт и при желании загрузить его", + "iris.director.commandiris.param.debugdump_upload": "Загрузить отчёт на mclo.gs", "director.help.navigation.parent.hover": "Вернуться к родительской группе команд", "director.help.navigation.back": "Назад", "director.help.no_subcommands": "На этой странице нет подкоманд.", diff --git a/core/src/main/resources/languages/tr_TR.json b/core/src/main/resources/languages/tr_TR.json index 8d264d1e3..6372a0306 100644 --- a/core/src/main/resources/languages/tr_TR.json +++ b/core/src/main/resources/languages/tr_TR.json @@ -1,6 +1,8 @@ { "locale": "tr_TR", "messages": { + "iris.director.commandiris.director.debugdump": "Tanılama raporu oluştur ve isteğe bağlı olarak yükle", + "iris.director.commandiris.param.debugdump_upload": "Raporu mclo.gs üzerine yükle", "director.help.navigation.parent.hover": "Üst komut grubuna dön", "director.help.navigation.back": "Geri", "director.help.no_subcommands": "Bu sayfada alt komut yok.", diff --git a/core/src/main/resources/languages/vi_VI.json b/core/src/main/resources/languages/vi_VI.json index dfa29fa2c..7bbd0eb4e 100644 --- a/core/src/main/resources/languages/vi_VI.json +++ b/core/src/main/resources/languages/vi_VI.json @@ -1,6 +1,8 @@ { "locale": "vi_VI", "messages": { + "iris.director.commandiris.director.debugdump": "Tạo báo cáo chẩn đoán và tải lên nếu muốn", + "iris.director.commandiris.param.debugdump_upload": "Tải báo cáo lên mclo.gs", "director.help.navigation.parent.hover": "Quay lại nhóm lệnh cha", "director.help.navigation.back": "Quay lại", "director.help.no_subcommands": "Không có lệnh con trên trang này.", diff --git a/core/src/main/resources/languages/zh_CN.json b/core/src/main/resources/languages/zh_CN.json index 72588fc7c..b540c697a 100644 --- a/core/src/main/resources/languages/zh_CN.json +++ b/core/src/main/resources/languages/zh_CN.json @@ -1,6 +1,8 @@ { "locale": "zh_CN", "messages": { + "iris.director.commandiris.director.debugdump": "创建诊断报告并选择是否上传", + "iris.director.commandiris.param.debugdump_upload": "将报告上传到 mclo.gs", "director.help.navigation.parent.hover": "返回上级命令组", "director.help.navigation.back": "返回", "director.help.no_subcommands": "此页面没有子命令。", diff --git a/core/src/main/resources/languages/zh_TW.json b/core/src/main/resources/languages/zh_TW.json index e27df451c..22f8554cc 100644 --- a/core/src/main/resources/languages/zh_TW.json +++ b/core/src/main/resources/languages/zh_TW.json @@ -1,6 +1,8 @@ { "locale": "zh_TW", "messages": { + "iris.director.commandiris.director.debugdump": "建立診斷報告並選擇是否上傳", + "iris.director.commandiris.param.debugdump_upload": "將報告上傳至 mclo.gs", "director.help.navigation.parent.hover": "返回上層命令群組", "director.help.navigation.back": "返回", "director.help.no_subcommands": "此頁面沒有子命令。",