From fc25acdea274f633f0a3963fb7386851e5cfda74 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Sat, 14 Feb 2026 23:55:01 -0500 Subject: [PATCH] CMD --- build.gradle.kts | 2 +- core/src/main/java/art/arcane/iris/Iris.java | 67 +- .../arcane/iris/core/service/CommandSVC.java | 247 ++++++-- .../iris/util/common/decree/DecreeNode.java | 17 - .../util/common/decree/DecreeParameter.java | 28 - .../iris/util/common/decree/DecreeSystem.java | 96 +-- .../decree/virtual/VirtualDecreeCommand.java | 596 ------------------ .../iris/util/common/plugin/VolmitSender.java | 92 ++- 8 files changed, 310 insertions(+), 835 deletions(-) delete mode 100644 core/src/main/java/art/arcane/iris/util/common/decree/DecreeNode.java delete mode 100644 core/src/main/java/art/arcane/iris/util/common/decree/DecreeParameter.java delete mode 100644 core/src/main/java/art/arcane/iris/util/common/decree/virtual/VirtualDecreeCommand.java diff --git a/build.gradle.kts b/build.gradle.kts index 9158c5c78..f9c9b4615 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,7 +34,7 @@ plugins { } group = "art.arcane" -version = "3.9.1-1.20.1-1.21.11" +version = "4.0.0-1.20.1-1.21.11-Dev1" apply() diff --git a/core/src/main/java/art/arcane/iris/Iris.java b/core/src/main/java/art/arcane/iris/Iris.java index fd9cf5d21..1044c6f69 100644 --- a/core/src/main/java/art/arcane/iris/Iris.java +++ b/core/src/main/java/art/arcane/iris/Iris.java @@ -43,7 +43,7 @@ import art.arcane.volmlib.util.collection.KMap; import art.arcane.volmlib.util.exceptions.IrisException; import art.arcane.iris.util.format.C; import art.arcane.volmlib.util.function.NastyRunnable; -import art.arcane.volmlib.util.io.FileWatcher; +import art.arcane.volmlib.util.hotload.ConfigHotloadEngine; import art.arcane.volmlib.util.io.IO; import art.arcane.volmlib.util.io.InstanceState; import art.arcane.volmlib.util.io.JarScanner; @@ -89,10 +89,11 @@ public class Iris extends VolmitPlugin implements Listener { public static Bindings.Adventure audiences; public static MultiverseCoreLink linkMultiverseCore; public static IrisCompat compat; - public static FileWatcher configWatcher; + public static ConfigHotloadEngine configHotloadEngine; public static ChunkTickets tickets; private static VolmitSender sender; private static Thread shutdownHook; + private static File settingsFile; static { try { @@ -446,7 +447,14 @@ public class Iris extends VolmitPlugin implements Listener { IrisSafeguard.splash(); tickets = new ChunkTickets(); linkMultiverseCore = new MultiverseCoreLink(); - configWatcher = new FileWatcher(getDataFile("settings.json")); + settingsFile = getDataFile("settings.json"); + configHotloadEngine = new ConfigHotloadEngine( + Iris::isSettingsFile, + Iris::knownSettingsFiles, + Iris::readSettingsContent, + Iris::normalizeSettingsContent + ); + configHotloadEngine.configure(3_000L, List.of(settingsFile), List.of()); services.values().forEach(IrisService::onEnable); services.values().forEach(this::registerListener); addShutdownHook(); @@ -556,6 +564,10 @@ public class Iris extends VolmitPlugin implements Listener { public void onDisable() { if (IrisSafeguard.isForceShutdown()) return; services.values().forEach(IrisService::onDisable); + if (configHotloadEngine != null) { + configHotloadEngine.clear(); + configHotloadEngine = null; + } Bukkit.getScheduler().cancelTasks(this); HandlerList.unregisterAll((Plugin) this); postShutdown.forEach(Runnable::run); @@ -586,12 +598,51 @@ public class Iris extends VolmitPlugin implements Listener { } private void checkConfigHotload() { - if (configWatcher.checkModified()) { - IrisSettings.invalidate(); - IrisSettings.get(); - configWatcher.checkModified(); - Iris.info("Hotloaded settings.json "); + if (configHotloadEngine == null) { + return; } + + for (File file : configHotloadEngine.pollTouchedFiles()) { + configHotloadEngine.processFileChange(file, ignored -> { + IrisSettings.invalidate(); + IrisSettings.get(); + return true; + }, ignored -> Iris.info("Hotloaded settings.json ")); + } + } + + private static boolean isSettingsFile(File file) { + if (file == null || settingsFile == null) { + return false; + } + return settingsFile.getAbsoluteFile().equals(file.getAbsoluteFile()); + } + + private static List knownSettingsFiles() { + if (settingsFile == null) { + return List.of(); + } + return List.of(settingsFile); + } + + private static String readSettingsContent(File file) { + if (file == null || !file.exists() || !file.isFile()) { + return null; + } + + try { + return IO.readAll(file); + } catch (Throwable ignored) { + return null; + } + } + + private static String normalizeSettingsContent(String text) { + if (text == null) { + return null; + } + + return text.replace("\r\n", "\n").trim(); } private void tickQueue() { diff --git a/core/src/main/java/art/arcane/iris/core/service/CommandSVC.java b/core/src/main/java/art/arcane/iris/core/service/CommandSVC.java index eee523970..84a8c7200 100644 --- a/core/src/main/java/art/arcane/iris/core/service/CommandSVC.java +++ b/core/src/main/java/art/arcane/iris/core/service/CommandSVC.java @@ -19,40 +19,63 @@ package art.arcane.iris.core.service; import art.arcane.iris.Iris; +import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.commands.CommandIris; import art.arcane.iris.core.tools.IrisToolbelt; import art.arcane.iris.engine.data.cache.AtomicCache; -import art.arcane.volmlib.util.collection.KMap; import art.arcane.iris.util.decree.DecreeContext; +import art.arcane.iris.util.decree.DecreeContextHandler; import art.arcane.iris.util.decree.DecreeSystem; -import art.arcane.iris.util.decree.virtual.VirtualDecreeCommand; import art.arcane.iris.util.format.C; import art.arcane.iris.util.plugin.IrisService; import art.arcane.iris.util.plugin.VolmitSender; import art.arcane.iris.util.scheduling.J; +import art.arcane.volmlib.util.director.compat.DirectorDecreeEngineFactory; +import art.arcane.volmlib.util.director.context.DirectorContextRegistry; +import art.arcane.volmlib.util.director.runtime.DirectorExecutionMode; +import art.arcane.volmlib.util.director.runtime.DirectorExecutionResult; +import art.arcane.volmlib.util.director.runtime.DirectorInvocation; +import art.arcane.volmlib.util.director.runtime.DirectorInvocationHook; +import art.arcane.volmlib.util.director.runtime.DirectorRuntimeEngine; +import art.arcane.volmlib.util.director.runtime.DirectorRuntimeNode; +import art.arcane.volmlib.util.director.runtime.DirectorSender; +import art.arcane.volmlib.util.director.visual.DirectorVisualCommand; +import art.arcane.volmlib.util.math.RNG; +import org.bukkit.Sound; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.command.TabCompleter; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.player.PlayerCommandPreprocessEvent; -import org.bukkit.event.server.ServerCommandEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.Locale; -import java.util.concurrent.CompletableFuture; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; -public class CommandSVC implements IrisService, DecreeSystem { - private final KMap> futures = new KMap<>(); - private final transient AtomicCache commandCache = new AtomicCache<>(); - private CompletableFuture consoleFuture = null; +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 transient AtomicCache directorCache = new AtomicCache<>(); + private final transient AtomicCache helpCache = new AtomicCache<>(); @Override public void onEnable() { - Iris.instance.getCommand("iris").setExecutor(this); - J.a(() -> { - DecreeContext.touch(Iris.getSender()); - try { - getRoot().cacheAll(); - } finally { - DecreeContext.remove(); - } - }); + PluginCommand command = Iris.instance.getCommand(ROOT_COMMAND); + if (command == null) { + Iris.warn("Failed to find command '" + ROOT_COMMAND + "'"); + return; + } + + command.setExecutor(this); + command.setTabCompleter(this); + J.a(this::getDirector); } @Override @@ -64,44 +87,184 @@ public class CommandSVC implements IrisService, DecreeSystem { public void on(PlayerCommandPreprocessEvent e) { String msg = e.getMessage().startsWith("/") ? e.getMessage().substring(1) : e.getMessage(); - if (msg.startsWith("irisdecree ")) { - String[] args = msg.split("\\Q \\E"); - CompletableFuture future = futures.get(args[1]); - - if (future != null) { - future.complete(args[2]); - e.setCancelled(true); - return; - } - } - if ((msg.startsWith("locate ") || msg.startsWith("locatebiome ")) && IrisToolbelt.isIrisWorld(e.getPlayer().getWorld())) { new VolmitSender(e.getPlayer()).sendMessage(C.RED + "Locating biomes & objects is disabled in Iris Worlds. Use /iris studio goto "); e.setCancelled(true); } } - @EventHandler - public void on(ServerCommandEvent e) { - if (consoleFuture != null && !consoleFuture.isCancelled() && !consoleFuture.isDone()) { - if (!e.getCommand().contains(" ")) { - String pick = e.getCommand().trim().toLowerCase(Locale.ROOT); - consoleFuture.complete(pick); - e.setCancelled(true); + public DirectorRuntimeEngine getDirector() { + return directorCache.aquireNastyPrint(() -> DirectorDecreeEngineFactory.create( + new CommandIris(), + null, + buildDirectorContexts(), + this::dispatchDirector, + this, + DecreeSystem.handlers + )); + } + + private DirectorContextRegistry buildDirectorContexts() { + DirectorContextRegistry contexts = new DirectorContextRegistry(); + + for (Map.Entry, DecreeContextHandler> entry : DecreeContextHandler.contextHandlers.entrySet()) { + registerContextHandler(contexts, entry.getKey(), entry.getValue()); + } + + return contexts; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private void registerContextHandler(DirectorContextRegistry contexts, Class type, DecreeContextHandler handler) { + contexts.register((Class) type, (invocation, map) -> { + if (invocation.getSender() instanceof BukkitDirectorSender sender) { + return ((DecreeContextHandler) handler).handle(new VolmitSender(sender.sender())); } + + return null; + }); + } + + private void dispatchDirector(DirectorExecutionMode mode, Runnable runnable) { + if (mode == DirectorExecutionMode.SYNC) { + J.s(runnable); + } else { + runnable.run(); } } @Override - public VirtualDecreeCommand getRoot() { - return commandCache.aquireNastyPrint(() -> VirtualDecreeCommand.createRoot(new CommandIris())); + public void beforeInvoke(DirectorInvocation invocation, DirectorRuntimeNode node) { + if (invocation.getSender() instanceof BukkitDirectorSender sender) { + DecreeContext.touch(new VolmitSender(sender.sender())); + } } - public void post(String password, CompletableFuture future) { - futures.put(password, future); + @Override + public void afterInvoke(DirectorInvocation invocation, DirectorRuntimeNode node) { + DecreeContext.remove(); } - public void postConsole(CompletableFuture future) { - consoleFuture = future; + @Nullable + @Override + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { + if (!command.getName().equalsIgnoreCase(ROOT_COMMAND)) { + return List.of(); + } + + List v = 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)); + } + + return v; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (!command.getName().equalsIgnoreCase(ROOT_COMMAND)) { + return false; + } + + if (!sender.hasPermission(ROOT_PERMISSION)) { + sender.sendMessage("You lack the Permission '" + ROOT_PERMISSION + "'"); + return true; + } + + J.aBukkit(() -> executeCommand(sender, label, args)); + return true; + } + + private void executeCommand(CommandSender sender, String label, String[] args) { + if (sendHelpIfRequested(sender, args)) { + playSuccessSound(sender); + return; + } + + DirectorExecutionResult result = runDirector(sender, label, args); + + if (result.isSuccess()) { + playSuccessSound(sender); + return; + } + + playFailureSound(sender); + if (result.getMessage() == null || result.getMessage().trim().isEmpty()) { + new VolmitSender(sender).sendMessage(C.RED + "Unknown Iris Command"); + } + } + + private boolean sendHelpIfRequested(CommandSender sender, String[] args) { + Optional request = DirectorVisualCommand.resolveHelp(getHelpRoot(), Arrays.asList(args)); + if (request.isEmpty()) { + return false; + } + + VolmitSender volmitSender = new VolmitSender(sender); + volmitSender.sendDecreeHelp(request.get().command(), request.get().page()); + return true; + } + + private DirectorVisualCommand getHelpRoot() { + return helpCache.aquireNastyPrint(() -> DirectorVisualCommand.createRoot(getDirector())); + } + + private DirectorExecutionResult runDirector(CommandSender sender, String label, String[] args) { + try { + 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()); + return DirectorExecutionResult.notHandled(); + } + } + + private List runDirectorTab(CommandSender sender, String alias, String[] args) { + try { + 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()); + return List.of(); + } + } + + private void playFailureSound(CommandSender sender) { + if (!IrisSettings.get().getGeneral().isCommandSounds()) { + return; + } + + if (sender instanceof Player player) { + player.playSound(player.getLocation(), Sound.BLOCK_AMETHYST_CLUSTER_BREAK, 0.77f, 0.25f); + player.playSound(player.getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 0.2f, 0.45f); + } + } + + private void playSuccessSound(CommandSender sender) { + if (!IrisSettings.get().getGeneral().isCommandSounds()) { + return; + } + + if (sender instanceof Player player) { + player.playSound(player.getLocation(), Sound.BLOCK_AMETHYST_CLUSTER_BREAK, 0.77f, 1.65f); + player.playSound(player.getLocation(), Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 0.125f, 2.99f); + } + } + + private record BukkitDirectorSender(CommandSender sender) implements DirectorSender { + @Override + public String getName() { + return sender.getName(); + } + + @Override + public boolean isPlayer() { + return sender instanceof Player; + } + + @Override + public void sendMessage(String message) { + if (message != null && !message.trim().isEmpty()) { + sender.sendMessage(message); + } + } } } diff --git a/core/src/main/java/art/arcane/iris/util/common/decree/DecreeNode.java b/core/src/main/java/art/arcane/iris/util/common/decree/DecreeNode.java deleted file mode 100644 index d86c4b5ca..000000000 --- a/core/src/main/java/art/arcane/iris/util/common/decree/DecreeNode.java +++ /dev/null @@ -1,17 +0,0 @@ -package art.arcane.iris.util.decree; - -import art.arcane.volmlib.util.decree.DecreeNodeBase; - -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; - -public class DecreeNode extends DecreeNodeBase { - public DecreeNode(Object instance, Method method) { - super(instance, method); - } - - @Override - protected DecreeParameter createParameter(Parameter parameter) { - return new DecreeParameter(parameter); - } -} diff --git a/core/src/main/java/art/arcane/iris/util/common/decree/DecreeParameter.java b/core/src/main/java/art/arcane/iris/util/common/decree/DecreeParameter.java deleted file mode 100644 index d32110f20..000000000 --- a/core/src/main/java/art/arcane/iris/util/common/decree/DecreeParameter.java +++ /dev/null @@ -1,28 +0,0 @@ -package art.arcane.iris.util.decree; - -import art.arcane.volmlib.util.decree.DecreeParameterBase; -import art.arcane.volmlib.util.decree.specialhandlers.NoParameterHandler; -import art.arcane.iris.util.decree.specialhandlers.DummyHandler; - -import java.lang.reflect.Parameter; - -public class DecreeParameter extends DecreeParameterBase { - public DecreeParameter(Parameter parameter) { - super(parameter); - } - - @Override - protected boolean useSystemHandler(Class customHandler) { - return customHandler.equals(NoParameterHandler.class) || customHandler.equals(DummyHandler.class); - } - - @Override - protected art.arcane.volmlib.util.decree.DecreeParameterHandler getSystemHandler(Class type) { - return DecreeSystem.getHandler(type); - } - - @Override - public DecreeParameterHandler getHandler() { - return (DecreeParameterHandler) super.getHandler(); - } -} diff --git a/core/src/main/java/art/arcane/iris/util/common/decree/DecreeSystem.java b/core/src/main/java/art/arcane/iris/util/common/decree/DecreeSystem.java index b1666e66e..7f95eb956 100644 --- a/core/src/main/java/art/arcane/iris/util/common/decree/DecreeSystem.java +++ b/core/src/main/java/art/arcane/iris/util/common/decree/DecreeSystem.java @@ -20,33 +20,11 @@ package art.arcane.iris.util.decree; import art.arcane.volmlib.util.decree.DecreeSystemSupport; import art.arcane.iris.Iris; -import art.arcane.iris.core.IrisSettings; import art.arcane.volmlib.util.collection.KList; -import art.arcane.iris.util.decree.virtual.VirtualDecreeCommand; -import art.arcane.iris.util.format.C; -import art.arcane.volmlib.util.math.RNG; -import art.arcane.iris.util.plugin.VolmitSender; -import art.arcane.iris.util.scheduling.J; -import org.bukkit.Sound; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabCompleter; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +public final class DecreeSystem { + public static final KList> handlers = Iris.initialize("art.arcane.iris.util.decree.handlers", null).convert((i) -> (DecreeParameterHandler) i); -import java.util.List; - -public interface DecreeSystem extends CommandExecutor, TabCompleter { - KList> handlers = Iris.initialize("art.arcane.iris.util.decree.handlers", null).convert((i) -> (DecreeParameterHandler) i); - - static KList enhanceArgs(String[] args) { - return new KList<>(DecreeSystemSupport.enhanceArgs(args)); - } - - static KList enhanceArgs(String[] args, boolean trim) { - return new KList<>(DecreeSystemSupport.enhanceArgs(args, trim)); + private DecreeSystem() { } /** @@ -55,7 +33,7 @@ public interface DecreeSystem extends CommandExecutor, TabCompleter { * @param type The type to handle * @return The corresponding {@link DecreeParameterHandler}, or null */ - static DecreeParameterHandler getHandler(Class type) { + public static DecreeParameterHandler getHandler(Class type) { DecreeParameterHandler handler = DecreeSystemSupport.getHandler(handlers, type, (h, t) -> h.supports(t)); if (handler != null) { return handler; @@ -64,70 +42,4 @@ public interface DecreeSystem extends CommandExecutor, TabCompleter { Iris.error("Unhandled type in Decree Parameter: " + type.getName() + ". This is bad!"); return null; } - - /** - * The root class to start command searching from - */ - VirtualDecreeCommand getRoot(); - - default boolean call(VolmitSender sender, String[] args) { - DecreeContext.touch(sender); - try { - return getRoot().invoke(sender, enhanceArgs(args)); - } finally { - DecreeContext.remove(); - } - } - - @Nullable - @Override - default List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { - DecreeContext.touch(new VolmitSender(sender)); - try { - KList enhanced = new KList<>(args); - KList v = getRoot().tabComplete(enhanced, enhanced.toString(" ")); - v.removeDuplicates(); - - if (sender instanceof Player) { - if (IrisSettings.get().getGeneral().isCommandSounds()) { - ((Player) sender).playSound(((Player) sender).getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.25f, RNG.r.f(0.125f, 1.95f)); - } - } - - return v; - } finally { - DecreeContext.remove(); - } - } - - @Override - default boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (!sender.hasPermission("iris.all")) { - sender.sendMessage("You lack the Permission 'iris.all'"); - return true; - } - - J.aBukkit(() -> { - var volmit = new VolmitSender(sender); - if (!call(volmit, args)) { - - if (IrisSettings.get().getGeneral().isCommandSounds()) { - if (sender instanceof Player) { - ((Player) sender).playSound(((Player) sender).getLocation(), Sound.BLOCK_AMETHYST_CLUSTER_BREAK, 0.77f, 0.25f); - ((Player) sender).playSound(((Player) sender).getLocation(), Sound.BLOCK_BEACON_DEACTIVATE, 0.2f, 0.45f); - } - } - - volmit.sendMessage(C.RED + "Unknown Iris Command"); - } else { - if (IrisSettings.get().getGeneral().isCommandSounds()) { - if (sender instanceof Player) { - ((Player) sender).playSound(((Player) sender).getLocation(), Sound.BLOCK_AMETHYST_CLUSTER_BREAK, 0.77f, 1.65f); - ((Player) sender).playSound(((Player) sender).getLocation(), Sound.BLOCK_RESPAWN_ANCHOR_CHARGE, 0.125f, 2.99f); - } - } - } - }); - return true; - } } diff --git a/core/src/main/java/art/arcane/iris/util/common/decree/virtual/VirtualDecreeCommand.java b/core/src/main/java/art/arcane/iris/util/common/decree/virtual/VirtualDecreeCommand.java deleted file mode 100644 index 133f1d370..000000000 --- a/core/src/main/java/art/arcane/iris/util/common/decree/virtual/VirtualDecreeCommand.java +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package art.arcane.iris.util.decree.virtual; - -import art.arcane.iris.Iris; -import art.arcane.iris.core.IrisSettings; -import art.arcane.volmlib.util.collection.KList; -import art.arcane.volmlib.util.collection.KMap; -import art.arcane.volmlib.util.collection.KSet; -import art.arcane.iris.util.decree.*; -import art.arcane.volmlib.util.decree.DecreeOrigin; -import art.arcane.volmlib.util.decree.annotations.Decree; -import art.arcane.volmlib.util.decree.exceptions.DecreeParsingException; -import art.arcane.iris.util.format.C; -import art.arcane.volmlib.util.format.Form; -import art.arcane.iris.util.plugin.CommandDummy; -import art.arcane.iris.util.plugin.VolmitSender; -import art.arcane.volmlib.util.scheduling.ChronoLatch; -import art.arcane.iris.util.scheduling.J; -import lombok.Data; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Objects; -import java.util.stream.Collectors; - -@Data -public class VirtualDecreeCommand { - private final Class type; - private final VirtualDecreeCommand parent; - private final KList nodes; - private final DecreeNode node; - String[] gradients = new String[]{ - "", - "", - "", - "", - "", - "" - }; - private ChronoLatch cl = new ChronoLatch(1000); - - private VirtualDecreeCommand(Class type, VirtualDecreeCommand parent, KList nodes, DecreeNode node) { - this.parent = parent; - this.type = type; - this.nodes = nodes; - this.node = node; - } - - public static VirtualDecreeCommand createRoot(Object v) throws Throwable { - return createRoot(null, v); - } - - public static VirtualDecreeCommand createRoot(VirtualDecreeCommand parent, Object v) throws Throwable { - VirtualDecreeCommand c = new VirtualDecreeCommand(v.getClass(), parent, new KList<>(), null); - - for (Field i : v.getClass().getDeclaredFields()) { - if (Modifier.isStatic(i.getModifiers()) || Modifier.isFinal(i.getModifiers()) || Modifier.isTransient(i.getModifiers()) || Modifier.isVolatile(i.getModifiers())) { - continue; - } - - if (!i.getType().isAnnotationPresent(Decree.class)) { - continue; - } - - i.setAccessible(true); - Object childRoot = i.get(v); - - if (childRoot == null) { - childRoot = i.getType().getConstructor().newInstance(); - i.set(v, childRoot); - } - - c.getNodes().add(createRoot(c, childRoot)); - } - - for (Method i : v.getClass().getDeclaredMethods()) { - if (Modifier.isStatic(i.getModifiers()) || Modifier.isFinal(i.getModifiers()) || Modifier.isPrivate(i.getModifiers())) { - continue; - } - - if (!i.isAnnotationPresent(Decree.class)) { - continue; - } - - c.getNodes().add(new VirtualDecreeCommand(v.getClass(), c, new KList<>(), new DecreeNode(v, i))); - } - - return c; - } - - public void cacheAll() { - VolmitSender sender = new VolmitSender(new CommandDummy()); - - if (isNode()) { - sender.sendDecreeHelpNode(this); - } - - for (VirtualDecreeCommand j : nodes) { - j.cacheAll(); - } - } - - public String getPath() { - KList n = new KList<>(); - VirtualDecreeCommand cursor = this; - - while (cursor.getParent() != null) { - cursor = cursor.getParent(); - n.add(cursor.getName()); - } - - return "/" + n.reverse().qadd(getName()).toString(" "); - } - - public String getParentPath() { - return getParent().getPath(); - } - - public String getName() { - return isNode() ? getNode().getName() : getType().getDeclaredAnnotation(Decree.class).name(); - } - - private boolean isStudio() { - return isNode() ? getNode().getDecree().studio() : getType().getDeclaredAnnotation(Decree.class).studio(); - } - - public String getDescription() { - return isNode() ? getNode().getDescription() : getType().getDeclaredAnnotation(Decree.class).description(); - } - - public KList getNames() { - if (isNode()) { - return getNode().getNames(); - } - - Decree dc = getType().getDeclaredAnnotation(Decree.class); - KList d = new KList<>(); - d.add(dc.name()); - for (String i : dc.aliases()) { - if (i.isEmpty()) { - continue; - } - - d.add(i); - } - - d.removeDuplicates(); - - return d; - } - - public boolean isNode() { - return node != null; - } - - public KList tabComplete(KList args, String raw) { - KList skip = new KList<>(); - KList tabs = new KList<>(); - invokeTabComplete(args, skip, tabs, raw); - return tabs; - } - - private boolean invokeTabComplete(KList args, KList skip, KList tabs, String raw) { - if (isStudio() && !IrisSettings.get().getStudio().isStudio()) { - return false; - } - - if (isNode()) { - tab(args, tabs); - skip.add(hashCode()); - return false; - } - - if (args.isEmpty()) { - tab(args, tabs); - return true; - } - - String head = args.get(0); - - if (args.size() > 1 || head.endsWith(" ")) { - VirtualDecreeCommand match = matchNode(head, skip); - - if (match != null) { - args.pop(); - return match.invokeTabComplete(args, skip, tabs, raw); - } - - skip.add(hashCode()); - } else { - tab(args, tabs); - } - - return false; - } - - private void tab(KList args, KList tabs) { - String last = null; - KList ignore = new KList<>(); - Runnable la = () -> { - - }; - for (String a : args) { - la.run(); - last = a; - la = () -> { - if (isNode()) { - String sea = a.contains("=") ? a.split("\\Q=\\E")[0] : a; - sea = sea.trim(); - - searching: - for (DecreeParameter i : getNode().getParameters()) { - for (String m : i.getNames()) { - if (m.equalsIgnoreCase(sea) || m.toLowerCase().contains(sea.toLowerCase()) || sea.toLowerCase().contains(m.toLowerCase())) { - ignore.add(i); - continue searching; - } - } - } - } - }; - } - - if (last != null) { - if (isNode()) { - for (DecreeParameter i : getNode().getParameters()) { - if (ignore.contains(i)) { - continue; - } - - int g = 0; - - if (last.contains("=")) { - String[] vv = last.trim().split("\\Q=\\E"); - String vx = vv.length == 2 ? vv[1] : ""; - for (String f : i.getHandler().getPossibilities(vx).convert((v) -> i.getHandler().toStringForce(v))) { - g++; - tabs.add(i.getName() + "=" + f); - } - } else { - for (String f : i.getHandler().getPossibilities("").convert((v) -> i.getHandler().toStringForce(v))) { - g++; - tabs.add(i.getName() + "=" + f); - } - } - - if (g == 0) { - tabs.add(i.getName() + "="); - } - } - } else { - for (VirtualDecreeCommand i : getNodes()) { - String m = i.getName(); - if (m.equalsIgnoreCase(last) || m.toLowerCase().contains(last.toLowerCase()) || last.toLowerCase().contains(m.toLowerCase())) { - tabs.addAll(i.getNames()); - } - } - } - } - } - - /** - * Maps the input a player typed to the parameters of this command - * - * @param sender The sender - * @param in The input - * @return A map of all the parameter names and their values - */ - private KMap map(VolmitSender sender, KList in) { - KMap data = new KMap<>(); - KSet nowhich = new KSet<>(); - - KList unknownInputs = new KList<>(in.stream().filter(s -> !s.contains("=")).collect(Collectors.toList())); - KList knownInputs = new KList<>(in.stream().filter(s -> s.contains("=")).collect(Collectors.toList())); - - //Loop known inputs - for (int x = 0; x < knownInputs.size(); x++) { - String stringParam = knownInputs.get(x); - int original = in.indexOf(stringParam); - - String[] v = stringParam.split("\\Q=\\E"); - String key = v[0]; - String value = v[1]; - DecreeParameter param = null; - - //Find decree parameter from string param - for (DecreeParameter j : getNode().getParameters()) { - for (String k : j.getNames()) { - if (k.equalsIgnoreCase(key)) { - param = j; - break; - } - } - } - - //If it failed, see if we can find it by checking if the names contain the param - if (param == null) { - for (DecreeParameter j : getNode().getParameters()) { - for (String k : j.getNames()) { - if (k.toLowerCase().contains(key.toLowerCase()) || key.toLowerCase().contains(k.toLowerCase())) { - param = j; - break; - } - } - } - } - - //Still failed to find, error them - if (param == null) { - Iris.debug("Can't find parameter key for " + key + "=" + value + " in " + getPath()); - sender.sendMessage(C.YELLOW + "Unknown Parameter: " + key); - unknownInputs.add(value); //Add the value to the unknowns and see if we can assume it later - continue; - } - - key = param.getName(); - - try { - data.put(key, param.getHandler().parse(value, nowhich.contains(original))); //Parse and put - } catch (DecreeParsingException e) { - Iris.debug("Can't parse parameter value for " + key + "=" + value + " in " + getPath() + " using handler " + param.getHandler().getClass().getSimpleName()); - sender.sendMessage(C.RED + "Cannot convert \"" + value + "\" into a " + param.getType().getSimpleName()); - e.printStackTrace(); - return null; - } - } - - //Make a list of decree params that haven't been identified - KList decreeParameters = new KList<>(getNode().getParameters().stream().filter(param -> !data.contains(param.getName())).collect(Collectors.toList())); - - //Loop Unknown inputs - for (int x = 0; x < unknownInputs.size(); x++) { - String stringParam = unknownInputs.get(x); - int original = in.indexOf(stringParam); - try { - DecreeParameter par = decreeParameters.get(x); - - try { - data.put(par.getName(), par.getHandler().parse(stringParam, nowhich.contains(original))); - } catch (DecreeParsingException e) { - Iris.debug("Can't parse parameter value for " + par.getName() + "=" + stringParam + " in " + getPath() + " using handler " + par.getHandler().getClass().getSimpleName()); - sender.sendMessage(C.RED + "Cannot convert \"" + stringParam + "\" into a " + par.getType().getSimpleName()); - e.printStackTrace(); - return null; - } - } catch (IndexOutOfBoundsException e) { - sender.sendMessage(C.YELLOW + "Unknown Parameter: " + stringParam + " (" + Form.getNumberSuffixThStRd(x + 1) + " argument)"); - } - } - - return data; - } - - public boolean invoke(VolmitSender sender, KList realArgs) { - return invoke(sender, realArgs, new KList<>()); - } - - public boolean invoke(VolmitSender sender, KList args, KList skip) { - if (isStudio() && !IrisSettings.get().getStudio().isStudio()) { - sender.sendMessage(C.RED + "To use Iris Studio Commands, please enable studio in Iris/settings.json (settings auto-reload)"); - return false; - } - - DecreeOrigin origin = type.getDeclaredAnnotation(Decree.class).origin(); - if (!origin.validFor(sender.isPlayer())) { - sender.sendMessage(C.RED + "This command has to be sent from another origin: " + C.GOLD + origin); - return false; - } - - Iris.debug("@ " + getPath() + " with " + args.toString(", ")); - if (isNode()) { - Iris.debug("Invoke " + getPath() + "(" + args.toString(",") + ") at "); - if (invokeNode(sender, map(sender, args))) { - return true; - } - - skip.add(hashCode()); - return false; - } - - if (args.isEmpty()) { - sender.sendDecreeHelp(this); - - return true; - } else if (args.size() == 1) { - for (String i : args) { - if (i.startsWith("help=")) { - sender.sendDecreeHelp(this, Integer.parseInt(i.split("\\Q=\\E")[1]) - 1); - return true; - } - } - } - - String head = args.get(0); - VirtualDecreeCommand match = matchNode(head, skip); - - if (match != null) { - args.pop(); - return match.invoke(sender, args, skip); - } - - skip.add(hashCode()); - - return false; - } - - private boolean invokeNode(VolmitSender sender, KMap map) { - if (map == null) { - return false; - } - - Object[] params = new Object[getNode().getMethod().getParameterCount()]; - int vm = 0; - for (DecreeParameter i : getNode().getParameters()) { - Object value = map.get(i.getName()); - - try { - if (value == null && i.hasDefault()) { - value = i.getDefaultValue(); - } - } catch (DecreeParsingException e) { - Iris.debug("Can't parse parameter value for " + i.getName() + "=" + i.getParam().defaultValue() + " in " + getPath() + " using handler " + i.getHandler().getClass().getSimpleName()); - sender.sendMessage(C.RED + "Cannot convert \"" + i.getParam().defaultValue() + "\" into a " + i.getType().getSimpleName()); - return false; - } - - if (sender.isPlayer() && i.isContextual() && value == null) { - Iris.debug("Contextual!"); - DecreeContextHandler ch = DecreeContextHandler.contextHandlers.get(i.getType()); - - if (ch != null) { - value = ch.handle(sender); - - if (value != null) { - Iris.debug("Parameter \"" + i.getName() + "\" derived a value of \"" + i.getHandler().toStringForce(value) + "\" from " + ch.getClass().getSimpleName()); - } else { - Iris.debug("Parameter \"" + i.getName() + "\" could not derive a value from \"" + ch.getClass().getSimpleName()); - } - } else { - Iris.debug("Parameter \"" + i.getName() + "\" is contextual but has no context handler for \"" + i.getType().getCanonicalName() + "\""); - } - } - - if (i.hasDefault() && value == null) { - try { - Iris.debug("Parameter \"" + i.getName() + "\" is using default value \"" + i.getParam().defaultValue() + "\""); - value = i.getDefaultValue(); - } catch (Throwable e) { - e.printStackTrace(); - } - } - - if (i.isRequired() && value == null) { - sender.sendMessage(C.RED + "Missing argument \"" + i.getName() + "\" (" + i.getType().getSimpleName() + ") as the " + Form.getNumberSuffixThStRd(vm + 1) + " argument."); - sender.sendDecreeHelpNode(this); - return false; - } - - params[vm] = value; - vm++; - } - - DecreeContext.touch(sender); - try { - Runnable rx = () -> { - DecreeContext.touch(sender); - try { - getNode().getMethod().setAccessible(true); - getNode().getMethod().invoke(getNode().getInstance(), params); - } catch (Throwable e) { - e.printStackTrace(); - throw new RuntimeException("Failed to execute "); // TODO: - } finally { - DecreeContext.remove(); - } - }; - - if (getNode().isSync()) { - J.s(rx); - } else { - rx.run(); - } - } finally { - DecreeContext.remove(); - } - - return true; - } - - public KList matchAllNodes(String in) { - KList g = new KList<>(); - - if (in.trim().isEmpty()) { - g.addAll(nodes); - return g; - } - - for (VirtualDecreeCommand i : nodes) { - if (i.matches(in)) { - g.add(i); - } - } - - for (VirtualDecreeCommand i : nodes) { - if (i.deepMatches(in)) { - g.add(i); - } - } - - g.removeDuplicates(); - return g; - } - - public VirtualDecreeCommand matchNode(String in, KList skip) { - if (in.trim().isEmpty()) { - return null; - } - - for (VirtualDecreeCommand i : nodes) { - if (skip.contains(i.hashCode())) { - continue; - } - - if (i.matches(in)) { - return i; - } - } - - for (VirtualDecreeCommand i : nodes) { - if (skip.contains(i.hashCode())) { - continue; - } - - if (i.deepMatches(in)) { - return i; - } - } - - return null; - } - - public boolean deepMatches(String in) { - KList a = getNames(); - - for (String i : a) { - if (i.toLowerCase().contains(in.toLowerCase()) || in.toLowerCase().contains(i.toLowerCase())) { - return true; - } - } - - return false; - } - - @Override - public int hashCode() { - return Objects.hash(getName(), getDescription(), getType(), getPath()); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof VirtualDecreeCommand)) { - return false; - } - return this.hashCode() == obj.hashCode(); - } - - public boolean matches(String in) { - KList a = getNames(); - - for (String i : a) { - if (i.equalsIgnoreCase(in)) { - return true; - } - } - - return false; - } -} diff --git a/core/src/main/java/art/arcane/iris/util/common/plugin/VolmitSender.java b/core/src/main/java/art/arcane/iris/util/common/plugin/VolmitSender.java index 7f9121d26..91427855a 100644 --- a/core/src/main/java/art/arcane/iris/util/common/plugin/VolmitSender.java +++ b/core/src/main/java/art/arcane/iris/util/common/plugin/VolmitSender.java @@ -22,8 +22,8 @@ import art.arcane.iris.Iris; import art.arcane.iris.core.IrisSettings; import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KMap; -import art.arcane.iris.util.decree.DecreeParameter; -import art.arcane.iris.util.decree.virtual.VirtualDecreeCommand; +import art.arcane.volmlib.util.director.visual.DirectorVisualCommand; +import art.arcane.volmlib.util.director.visual.DirectorVisualCommand.DirectorVisualParameter; import art.arcane.iris.util.format.C; import art.arcane.volmlib.util.format.Form; import art.arcane.volmlib.util.math.M; @@ -409,19 +409,19 @@ public class VolmitSender implements CommandSender { return s.spigot(); } - private String pickRandoms(int max, VirtualDecreeCommand i) { + private String pickRandoms(int max, DirectorVisualCommand i) { KList m = new KList<>(); for (int ix = 0; ix < max; ix++) { m.add((i.isNode() ? (i.getNode().getParameters().isNotEmpty()) - ? "<#aebef2>✦ <#5ef288>" + ? "<#c2f7d2>✦ <#5ef288>" + i.getParentPath() - + " <#42ecf5>" + + " <#32bfad>" + i.getName() + " " + i.getNode().getParameters().shuffleCopy(RNG.r).convert((f) -> (f.isRequired() || RNG.r.b(0.5) ? "<#f2e15e>" + f.getNames().getRandom() + "=" - + "<#d665f0>" + f.example() + + "<#5ef288>" + f.example() : "")) .toString(" ") : "" @@ -443,7 +443,7 @@ public class VolmitSender implements CommandSender { if (name.trim().isEmpty()) { sendMessageRaw("" + sf + s + "" + s + se); } else { - sendMessageRaw("" + sf + s + si + " " + name + " " + so + s + se); + sendMessageRaw("" + sf + s + si + " " + name + " " + so + s + se); } } @@ -451,31 +451,29 @@ public class VolmitSender implements CommandSender { sendHeader(name, 44); } - public void sendDecreeHelp(VirtualDecreeCommand v) { + public void sendDecreeHelp(DirectorVisualCommand v) { sendDecreeHelp(v, 0); } - public void sendDecreeHelp(VirtualDecreeCommand v, int page) { + public void sendDecreeHelp(DirectorVisualCommand v, int page) { if (!isPlayer()) { - for (VirtualDecreeCommand i : v.getNodes()) { + for (DirectorVisualCommand i : v.getNodes()) { sendDecreeHelpNode(i); } return; } - int m = v.getNodes().size(); - sendMessageRaw("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); if (v.getNodes().isNotEmpty()) { sendHeader(v.getPath() + (page > 0 ? (" {" + (page + 1) + "}") : "")); if (isPlayer() && v.getParent() != null) { - sendMessageRaw("Click to go back to <#3299bf>" + Form.capitalize(v.getParent().getName()) + " Help" + "'><#f58571>〈 Back"); + sendMessageRaw("Click to go back to <#32bfad>" + Form.capitalize(v.getParent().getName()) + " Help" + "'><#6fe98f>〈 Back"); } AtomicBoolean next = new AtomicBoolean(false); - for (VirtualDecreeCommand i : paginate(v.getNodes(), 17, page, next)) { + for (DirectorVisualCommand i : paginate(v.getNodes(), 17, page, next)) { sendDecreeHelpNode(i); } @@ -483,13 +481,13 @@ public class VolmitSender implements CommandSender { int l = 75 - (page > 0 ? 10 : 0) - (next.get() ? 10 : 0); if (page > 0) { - s += "Click to go back to page " + page + "'>〈 Page " + page + " "; + s += "Click to go back to page " + page + "'>〈 Page " + page + " "; } s += "" + Form.repeat(" ", l) + ""; if (next.get()) { - s += " Click to go to back to page " + (page + 2) + "'>Page " + (page + 2) + " ❭"; + s += " Click to go to back to page " + (page + 2) + "'>Page " + (page + 2) + " ❭"; } sendMessageRaw(s); @@ -498,15 +496,13 @@ public class VolmitSender implements CommandSender { } } - public void sendDecreeHelpNode(VirtualDecreeCommand i) { + public void sendDecreeHelpNode(DirectorVisualCommand i) { if (isPlayer() || s instanceof CommandDummy) { sendMessageRaw(helpCache.computeIfAbsent(i.getPath(), (k) -> { String newline = "\n"; - /// Command - // Contains main command & aliases - String realText = i.getPath() + " >" + "<#46826a>⇀ " + i.getName(); - String hoverTitle = i.getNames().copy().reverse().convert((f) -> "<#42ecf5>" + f).toString(", "); + String realText = i.getPath() + " >" + "<#46826a>⇀ " + i.getName(); + String hoverTitle = i.getNames().copy().reverse().convert((f) -> "<#5ef288>" + f).toString(", "); String description = "<#3fe05a>✎ <#6ad97d>" + i.getDescription(); String usage = "<#bbe03f>✒ <#a8e0a2>"; String onClick; @@ -526,18 +522,16 @@ public class VolmitSender implements CommandSender { String suggestion = ""; String suggestions = ""; if (i.isNode() && i.getNode().getParameters().isNotEmpty()) { - suggestion += newline + "<#aebef2>✦ <#5ef288>" + i.getParentPath() + " <#42ecf5>" + i.getName() + " " - + i.getNode().getParameters().convert((f) -> "<#d665f0>" + f.example()).toString(" "); + suggestion += newline + "<#c2f7d2>✦ <#5ef288>" + i.getParentPath() + " <#32bfad>" + i.getName() + " " + + i.getNode().getParameters().convert((f) -> "<#5ef288>" + f.example()).toString(" "); suggestions += newline + "" + pickRandoms(Math.min(i.getNode().getParameters().size() + 1, 5), i); } - /// Params StringBuilder nodes = new StringBuilder(); if (i.isNode()) { - for (DecreeParameter p : i.getNode().getParameters()) { - - String nTitle = "" + p.getName(); - String nHoverTitle = p.getNames().convert((ff) -> "<#d665f0>" + ff).toString(", "); + for (DirectorVisualParameter p : i.getNode().getParameters()) { + String nTitle = "" + p.getName(); + String nHoverTitle = p.getNames().convert((ff) -> "<#5ef288>" + ff).toString(", "); String nDescription = "<#3fe05a>✎ <#6ad97d>" + p.getDescription(); String nUsage; String fullTitle; @@ -550,12 +544,12 @@ public class VolmitSender implements CommandSender { nUsage = "<#db4321>⚠ <#faa796>This parameter is required."; } else if (p.hasDefault()) { fullTitle = "<#4f4f4f>⊰" + nTitle + "<#4f4f4f>⊱"; - nUsage = "<#2181db>✔ <#78dcf0>Defaults to \"" + p.getParam().defaultValue() + "\" if undefined."; + nUsage = "<#3fbe6f>✔ <#9de5b6>Defaults to \"" + p.getParam().defaultValue() + "\" if undefined."; } else { fullTitle = "<#4f4f4f>⊰" + nTitle + "<#4f4f4f>⊱"; - nUsage = "<#a73abd>✔ <#78dcf0>This parameter is optional."; + nUsage = "<#3fbe6f>✔ <#9de5b6>This parameter is optional."; } - String type = "<#cc00ff>✢ <#ff33cc>This parameter is of type " + p.getType().getSimpleName() + "."; + String type = "<#4fbf7f>✢ <#8ad9af>This parameter is of type " + p.getType().getSimpleName() + "."; nodes .append(""); } } else { - nodes = new StringBuilder(" - Category of Commands"); + nodes = new StringBuilder(" - Category of Commands"); } - /// Wrapper - String wrapper = - "" + - "" + - "" + - " " + - nodes; - - return wrapper; + return "" + + "" + + "" + + " " + + nodes; })); } else { sendMessage(i.getPath());