mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 21:40:05 +00:00
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.
This commit is contained in:
39 files changed
+654
-226
No files matched your search
@@ -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<String> 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();
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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<Deque<LanguageAudience.Scope>> languageScopes = ThreadLocal.withInitial(ArrayDeque::new);
|
||||
|
||||
private final transient AtomicCache<DirectorRuntimeEngine> directorCache = new AtomicCache<>();
|
||||
private final transient ThreadLocal<CommandSender> 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<Class<?>, 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<LanguageAudience.Scope> 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<String> tabCompleteRoot(CommandSender sender, String alias, String[] args) {
|
||||
List<String> suggestions = runDirectorTab(sender, alias, args);
|
||||
List<String> 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<DirectorHelpPage> 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<DirectorHelpPage> 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<String> 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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
+56
@@ -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);
|
||||
}
|
||||
}
|
||||
+8
-5
@@ -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
|
||||
|
||||
Reference in new issue
Block a user