From b77cc273485d34fbc08e158ab993bdd660d52c79 Mon Sep 17 00:00:00 2001 From: TonytheMacaroni Date: Sun, 9 Nov 2025 09:57:26 -0500 Subject: [PATCH 1/5] Transition commands from ACF to Cloud --- .../dev/magicspells/gradle/MSJavaPlugin.java | 1 - core/build.gradle | 10 +- .../com/nisovin/magicspells/MagicSpells.java | 142 +-- .../java/com/nisovin/magicspells/Perm.java | 13 +- .../java/com/nisovin/magicspells/Spell.java | 19 +- .../conditions/HasItemCondition.java | 2 +- .../conditions/HoldingCondition.java | 2 +- .../conditions/OffhandCondition.java | 2 +- .../conditions/SignTextCondition.java | 2 +- .../conditions/WearingCondition.java | 2 +- .../magicspells/commands/CastCommands.java | 255 +++++ .../commands/CommandHelpFilter.java | 41 - .../magicspells/commands/DebugCommand.java | 73 ++ .../magicspells/commands/HelpCommand.java | 57 ++ .../magicspells/commands/HelpPermission.java | 16 - .../magicspells/commands/MagicCommand.java | 901 ------------------ .../magicspells/commands/MagicCommands.java | 160 ++++ .../commands/MagicItemCommand.java | 111 +++ .../magicspells/commands/MagicXpCommand.java | 59 ++ .../magicspells/commands/ManaCommands.java | 245 +++++ .../commands/ProfileReportCommand.java | 32 + .../magicspells/commands/ReloadCommands.java | 118 +++ .../commands/ResetCooldownCommand.java | 140 +++ .../magicspells/commands/TaskInfoCommand.java | 48 + .../magicspells/commands/UtilCommands.java | 93 ++ .../commands/VariableCommands.java | 158 +++ .../exceptions/GenericCommandException.java | 13 + .../InvalidCommandArgumentException.java | 13 + .../commands/parsers/AngleParser.java | 85 ++ .../commands/parsers/OwnedSpellParser.java | 102 ++ .../commands/parsers/RotationParser.java | 102 ++ .../parsers/SpellCastArgumentsParser.java | 130 +++ .../commands/parsers/SpellParser.java | 76 ++ .../commands/parsers/VarargsParser.java | 88 ++ .../com/nisovin/magicspells/mana/ManaBar.java | 70 +- .../nisovin/magicspells/mana/ManaRank.java | 49 +- .../nisovin/magicspells/mana/ManaSystem.java | 161 ++-- .../nisovin/magicspells/spells/BowSpell.java | 6 +- .../magicspells/spells/CommandSpell.java | 5 - .../nisovin/magicspells/spells/MenuSpell.java | 13 +- .../magicspells/spells/PlayerMenuSpell.java | 10 +- .../spells/command/AdminTeachSpell.java | 29 +- .../magicspells/spells/command/BindSpell.java | 31 +- .../spells/command/ForgetSpell.java | 82 +- .../magicspells/spells/command/HelpSpell.java | 24 +- .../spells/command/ImbueSpell.java | 29 +- .../spells/command/ItemSerializeSpell.java | 6 - .../spells/command/KeybindSpell.java | 28 +- .../magicspells/spells/command/ListSpell.java | 56 +- .../spells/command/ScrollSpell.java | 51 +- .../spells/command/SpellbookSpell.java | 33 +- .../spells/command/SublistSpell.java | 52 +- .../spells/command/TeachSpell.java | 55 +- .../magicspells/spells/command/TomeSpell.java | 33 +- .../spells/command/UnbindSpell.java | 39 +- .../spells/instant/ConjureBookSpell.java | 2 +- .../spells/instant/ConjureFireworkSpell.java | 2 +- .../spells/instant/ConjureSpell.java | 6 +- .../spells/instant/EnderchestSpell.java | 27 +- .../spells/instant/RecallSpell.java | 29 +- .../spells/targeted/CaptureSpell.java | 4 +- .../spells/targeted/SummonSpell.java | 18 +- .../nisovin/magicspells/util/Rotation.java | 85 ++ .../com/nisovin/magicspells/util/TxtUtil.java | 8 + .../com/nisovin/magicspells/util/Util.java | 104 +- .../util/config/ConfigDataUtil.java | 49 +- .../util/itemreader/LoreHandler.java | 4 +- .../util/itemreader/NameHandler.java | 2 +- .../util/itemreader/WrittenBookHandler.java | 6 +- .../util/magicitems/MagicItemDataParser.java | 10 +- core/src/main/resources/general.yml | 2 +- core/src/main/resources/mana.yml | 16 +- 72 files changed, 3122 insertions(+), 1425 deletions(-) create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/CastCommands.java delete mode 100644 core/src/main/java/com/nisovin/magicspells/commands/CommandHelpFilter.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/DebugCommand.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/HelpCommand.java delete mode 100644 core/src/main/java/com/nisovin/magicspells/commands/HelpPermission.java delete mode 100644 core/src/main/java/com/nisovin/magicspells/commands/MagicCommand.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/MagicCommands.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/MagicItemCommand.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/MagicXpCommand.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/ManaCommands.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/ProfileReportCommand.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/ReloadCommands.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/ResetCooldownCommand.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/TaskInfoCommand.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/UtilCommands.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/VariableCommands.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/exceptions/GenericCommandException.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/exceptions/InvalidCommandArgumentException.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/parsers/AngleParser.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/parsers/OwnedSpellParser.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/parsers/RotationParser.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/parsers/SpellCastArgumentsParser.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/parsers/SpellParser.java create mode 100644 core/src/main/java/com/nisovin/magicspells/commands/parsers/VarargsParser.java create mode 100644 core/src/main/java/com/nisovin/magicspells/util/Rotation.java diff --git a/buildSrc/src/main/java/dev/magicspells/gradle/MSJavaPlugin.java b/buildSrc/src/main/java/dev/magicspells/gradle/MSJavaPlugin.java index cf70d5165..5e50667e4 100644 --- a/buildSrc/src/main/java/dev/magicspells/gradle/MSJavaPlugin.java +++ b/buildSrc/src/main/java/dev/magicspells/gradle/MSJavaPlugin.java @@ -27,7 +27,6 @@ public void apply(Project target) { "https://repo.dmulloy2.net/nexus/repository/public/", "https://repo.md-5.net/content/repositories/releases/", "https://repo.papermc.io/repository/maven-public/", - "https://repo.aikar.co/content/groups/aikar/", "https://oss.sonatype.org/content/repositories/central", "https://oss.sonatype.org/content/repositories/snapshots", "https://hub.spigotmc.org/nexus/content/repositories/snapshots/", diff --git a/core/build.gradle b/core/build.gradle index 90efd393b..81345e849 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -7,7 +7,9 @@ dependencies { shadow("org.apache.commons:commons-math4-core:4.0-beta1") shadow("com.github.ben-manes.caffeine:caffeine:3.2.1") shadow("com.github.Chronoken:EffectLib:753da6c") - shadow("co.aikar:acf-paper:0.5.1-SNAPSHOT") + shadow("org.incendo:cloud-paper:2.0.0-beta.13") + shadow("org.incendo:cloud-minecraft-extras:2.0.0-beta.13") + shadow("org.incendo:cloud-processors-requirements:1.0.0-rc.1") shadow("org.bstats:bstats-bukkit:3.0.2") shadow("com.github.ezylang:EvalEx:0dcb042") @@ -53,8 +55,10 @@ shadowJar { relocate("com.github.benmanes.caffeine", "com.nisovin.magicspells.shaded.com.github.benmanes.caffeine") relocate("org.apache.commons.math4", "com.nisovin.magicspells.shaded.org.apache.commons.math4") relocate("de.slikey.effectlib", "com.nisovin.magicspells.shaded.effectlib") - relocate("co.aikar.commands", "com.nisovin.magicspells.shaded.acf") - relocate("co.aikar.locales", "com.nisovin.magicspells.shaded.locales") + relocate("org.incendo.cloud", "com.nisovin.magicspells.shaded.cloud") + relocate("io.leangen.geantyref", "com.nisovin.magicspells.shaded.geantyref") + relocate("xyz.jpenilla.reflectionremapper", "com.nisovin.magicspells.shaded.reflectionremapper") + relocate("net.fabricmc.mappingio", "com.nisovin.magicspells.shaded.mappingio") relocate("org.bstats", "com.nisovin.magicspells.shaded.bstats") relocate("de.slikey.exp4j", "com.nisovin.magicspells.shaded.exp4j") relocate("com.ezylang.evalex", "com.nisovin.magicspells.shaded.evalex") diff --git a/core/src/main/java/com/nisovin/magicspells/MagicSpells.java b/core/src/main/java/com/nisovin/magicspells/MagicSpells.java index 5126b5846..f989bae73 100644 --- a/core/src/main/java/com/nisovin/magicspells/MagicSpells.java +++ b/core/src/main/java/com/nisovin/magicspells/MagicSpells.java @@ -1,5 +1,7 @@ package com.nisovin.magicspells; +import org.jetbrains.annotations.NotNull; + import java.io.*; import java.util.*; @@ -25,6 +27,8 @@ import com.google.common.collect.SetMultimap; import com.google.common.collect.LinkedHashMultimap; +import net.kyori.adventure.text.format.NamedTextColor; + import de.slikey.effectlib.EffectManager; import org.bstats.bukkit.Metrics; @@ -32,10 +36,6 @@ import org.bstats.charts.AdvancedPie; import org.bstats.charts.DrilldownPie; -import org.jetbrains.annotations.NotNull; - -import co.aikar.commands.PaperCommandManager; - import org.bukkit.*; import org.bukkit.event.Event; import org.bukkit.entity.Entity; @@ -53,6 +53,11 @@ import org.bukkit.permissions.PermissionDefault; import org.bukkit.configuration.ConfigurationSection; +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import org.incendo.cloud.paper.PaperCommandManager; +import org.incendo.cloud.execution.ExecutionCoordinator; + import me.clip.placeholderapi.PlaceholderAPI; import com.nisovin.magicspells.util.*; @@ -64,8 +69,8 @@ import com.nisovin.magicspells.mana.ManaHandler; import com.nisovin.magicspells.variables.Variable; import com.nisovin.magicspells.spells.PassiveSpell; -import com.nisovin.magicspells.commands.MagicCommand; import com.nisovin.magicspells.util.compat.EventUtil; +import com.nisovin.magicspells.commands.MagicCommands; import com.nisovin.magicspells.storage.StorageHandler; import com.nisovin.magicspells.util.prompt.PromptType; import com.nisovin.magicspells.util.compat.CompatBasics; @@ -73,7 +78,6 @@ import com.nisovin.magicspells.spelleffects.SpellEffect; import com.nisovin.magicspells.util.magicitems.MagicItem; import com.nisovin.magicspells.castmodifiers.ModifierSet; -import com.nisovin.magicspells.commands.CommandHelpFilter; import com.nisovin.magicspells.util.magicitems.MagicItems; import com.nisovin.magicspells.util.recipes.CustomRecipes; import com.nisovin.magicspells.util.ai.CustomGoalsManager; @@ -141,7 +145,7 @@ public class MagicSpells extends JavaPlugin { private NoMagicZoneManager zoneManager; private CleanserManager cleanserManager; private CustomGoalsManager customGoalsManager; - private PaperCommandManager commandManager; + private PaperCommandManager commandManager; private ExperienceBarManager expBarManager; private ExpressionDictionary expressionDictionary; @@ -212,7 +216,8 @@ public class MagicSpells extends JavaPlugin { private long lastReloadTime = 0; - private ChatColor textColor; + private NamedTextColor textColor; + private String textFormat; private double losRaySize; private boolean losIgnorePassableBlocks; @@ -238,45 +243,8 @@ public class MagicSpells extends JavaPlugin { @Override public void onEnable() { load(); - - Metrics metrics = new Metrics(this, 892); - - metrics.addCustomChart(new DrilldownPie("spells", () -> { - Map> map = new HashMap<>(); - if (spells == null) return map; - - for (Spell spell : spells.values()) { - String name = spell.getClass().getName(); - if (!name.startsWith("com.nisovin.magicspells.spells")) continue; - name = name.replace("com.nisovin.magicspells.spells.", ""); - - String[] typeSplit = name.split("\\.", 2); - String formalPackage = typeSplit[0].substring(0, 1).toUpperCase() + typeSplit[0].substring(1); - - String spellPackage = (typeSplit.length == 1 ? "General" : formalPackage) + " Spells"; - String spellClass = typeSplit.length == 1 ? typeSplit[0] : typeSplit[1]; - - map.computeIfAbsent(spellPackage, key -> new HashMap<>()); - map.get(spellPackage).compute(spellClass, (k, v) -> (v == null ? 0 : v) + 1); - } - return map; - })); - metrics.addCustomChart(new AdvancedPie("passive_listeners", () -> { - IntMap map = new IntMap<>(); - if (spells == null) return map; - - for (Spell spell : spells.values()) { - if (!spell.getClass().getName().startsWith("com.nisovin.magicspells.spells")) continue; - if (!(spell instanceof PassiveSpell passiveSpell)) continue; - - for (PassiveListener listener : passiveSpell.getPassiveListeners()) { - String name = listener.getClass().getSimpleName(); - map.increment(name.substring(0, name.lastIndexOf("Listener"))); - } - } - return map; - })); - metrics.addCustomChart(new SimplePie("reload_time", () -> "<" + (lastReloadTime - lastReloadTime % 500 + 500) + " ms")); + initMetrics(); + initCommands(); } public void load() { @@ -287,8 +255,6 @@ public void load() { effectManager = new EffectManager(this); effectManager.enableDebug(debug); - commandManager = new PaperCommandManager(plugin); - // Create storage stuff spells = new HashMap<>(); spellNames = new HashMap<>(); @@ -334,7 +300,6 @@ public void load() { enableErrorLogging = config.getBoolean(path + "enable-error-logging", true); errorLogLimit = config.getInt(path + "error-log-limit", -1); enableProfiling = config.getBoolean(path + "enable-profiling", false); - textColor = ChatColor.getByChar(config.getString(path + "text-color", ChatColor.DARK_AQUA.getChar() + "")); broadcastRange = config.getInt(path + "broadcast-range", 20); effectlibInstanceLimit = config.getInt(path + "effectlib-instance-limit", 20000); @@ -405,6 +370,9 @@ public void load() { } } + textColor = Util.getLegacyColor(config.getString("text-color", null), NamedTextColor.DARK_AQUA); + textFormat = config.getString("text-format", "<" + textColor + ">"); + soundFailOnCooldown = config.getString(path + "sound-on-cooldown", null); soundFailMissingReagents = config.getString(path + "sound-missing-reagents", null); @@ -570,12 +538,6 @@ public void load() { magicLogger = new MagicLogger(this); } - // Register commands - commandManager.enableUnstableAPI("help"); - commandManager.registerCommand(new MagicCommand()); - commandManager.setValidNamePredicate(string -> true); - CommandHelpFilter.mapPerms(); - // Setup profiling if (enableProfiling) { profilingTotalTime = new HashMap<>(); @@ -588,6 +550,55 @@ public void load() { Bukkit.getScheduler().runTaskLater(this, this::loadExternalData, 1); } + private void initMetrics() { + Metrics metrics = new Metrics(this, 892); + + metrics.addCustomChart(new DrilldownPie("spells", () -> { + Map> map = new HashMap<>(); + if (spells == null) return map; + + for (Spell spell : spells.values()) { + String name = spell.getClass().getName(); + if (!name.startsWith("com.nisovin.magicspells.spells")) continue; + name = name.replace("com.nisovin.magicspells.spells.", ""); + + String[] typeSplit = name.split("\\.", 2); + String formalPackage = typeSplit[0].substring(0, 1).toUpperCase() + typeSplit[0].substring(1); + + String spellPackage = (typeSplit.length == 1 ? "General" : formalPackage) + " Spells"; + String spellClass = typeSplit.length == 1 ? typeSplit[0] : typeSplit[1]; + + map.computeIfAbsent(spellPackage, key -> new HashMap<>()); + map.get(spellPackage).compute(spellClass, (k, v) -> (v == null ? 0 : v) + 1); + } + return map; + })); + metrics.addCustomChart(new AdvancedPie("passive_listeners", () -> { + IntMap map = new IntMap<>(); + if (spells == null) return map; + + for (Spell spell : spells.values()) { + if (!spell.getClass().getName().startsWith("com.nisovin.magicspells.spells")) continue; + if (!(spell instanceof PassiveSpell passiveSpell)) continue; + + for (PassiveListener listener : passiveSpell.getPassiveListeners()) { + String name = listener.getClass().getSimpleName(); + map.increment(name.substring(0, name.lastIndexOf("Listener"))); + } + } + return map; + })); + metrics.addCustomChart(new SimplePie("reload_time", () -> "<" + (lastReloadTime - lastReloadTime % 500 + 500) + " ms")); + } + + private void initCommands() { + commandManager = PaperCommandManager.builder() + .executionCoordinator(ExecutionCoordinator.simpleCoordinator()) + .buildOnEnable(this); + + MagicCommands.register(commandManager); + } + private void initializeSpells() { log("Initializing spells..."); @@ -1077,10 +1088,19 @@ public static Spellbook getSpellbook(Player player) { return plugin.spellbooks.computeIfAbsent(player.getUniqueId(), uuid -> new Spellbook(player)); } + @Deprecated(forRemoval = true) public static ChatColor getTextColor() { + return ChatColor.valueOf(plugin.textColor.toString().toUpperCase()); + } + + public static NamedTextColor getLegacyTextColor() { return plugin.textColor; } + public static String getTextFormat() { + return plugin.textFormat; + } + /** * Gets a list of blocks that are considered transparent * @return set of block types @@ -1246,6 +1266,10 @@ public static int getDebugLevelOriginal() { return plugin.debugLevelOriginal; } + public static int getDebugLevel() { + return plugin.debugLevel; + } + public static int getErrorLogLimit() { return plugin.errorLogLimit; } @@ -1584,7 +1608,7 @@ public static void sendMessage(String message, LivingEntity recipient, SpellData message = doReplacements(message, recipient, data, replacements); - recipient.sendMessage(Util.getMiniMessage(getTextColor() + message)); + recipient.sendMessage(Util.getMessageText(message)); } private static final Pattern chatVarMatchPattern = Pattern.compile("%var:(\\w+)(?::(\\d+))?%", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); @@ -1898,13 +1922,13 @@ public static boolean requireReplacement(String message) { public static String getTargetName(Entity target) { if (target instanceof Player) return target.getName(); - if (target.customName() != null) return Util.getStrictStringFromComponent(target.customName()); + if (target.customName() != null) return Util.getStrictString(target.customName()); EntityType type = target.getType(); String name = plugin.entityNames.get(type); if (name != null) return name; - return Util.getStrictStringFromComponent(target.name()); + return Util.getStrictString(target.name()); } public static void registerEvents(final Listener listener) { diff --git a/core/src/main/java/com/nisovin/magicspells/Perm.java b/core/src/main/java/com/nisovin/magicspells/Perm.java index ff27a335c..443a70fe5 100644 --- a/core/src/main/java/com/nisovin/magicspells/Perm.java +++ b/core/src/main/java/com/nisovin/magicspells/Perm.java @@ -1,8 +1,12 @@ package com.nisovin.magicspells; +import org.jetbrains.annotations.NotNull; + import org.bukkit.permissions.Permissible; -public enum Perm { +import org.incendo.cloud.permission.Permission; + +public enum Perm implements Permission { SILENT("magicspells.silent"), NO_REAGENTS("magicspells.noreagents"), @@ -99,5 +103,10 @@ public boolean has(Permissible permissible, Spell spell) { if (requiresNode() && !permissible.hasPermission(getNode(spell))) return false; return true; } - + + @Override + public @NotNull String permissionString() { + return node; + } + } diff --git a/core/src/main/java/com/nisovin/magicspells/Spell.java b/core/src/main/java/com/nisovin/magicspells/Spell.java index d3f478f71..227a2e88b 100644 --- a/core/src/main/java/com/nisovin/magicspells/Spell.java +++ b/core/src/main/java/com/nisovin/magicspells/Spell.java @@ -1,5 +1,7 @@ package com.nisovin.magicspells; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.format.TextDecorationAndState; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -16,6 +18,8 @@ import com.google.common.collect.*; import com.google.common.base.Functions; +import org.incendo.cloud.suggestion.SuggestionProvider; + import org.bukkit.*; import org.bukkit.entity.*; import org.bukkit.util.Vector; @@ -47,6 +51,7 @@ import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.block.fluid.FluidData; import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.command.brigadier.CommandSourceStack; import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.events.*; @@ -287,7 +292,7 @@ public Spell(MagicConfig config, String spellName) { if (spellIcon != null && !spellIcon.getType().isAir()) { if (!magicItem.getMagicItemData().hasAttribute(MagicItemData.MagicItemAttribute.NAME)) { ItemMeta iconMeta = spellIcon.getItemMeta(); - iconMeta.displayName(Component.text(MagicSpells.getTextColor() + name)); + iconMeta.itemName(Util.getMiniMessage(name).applyFallbackStyle(MagicSpells.getLegacyTextColor())); spellIcon.setItemMeta(iconMeta); } } @@ -902,6 +907,10 @@ protected ConfigData getConfigDataComponent(String key, Component def return ConfigDataUtil.getComponent(config.getMainConfig(), internalKey + key, def); } + protected ConfigData getConfigDataItemComponent(String key, Component def) { + return ConfigDataUtil.getItemComponent(config.getMainConfig(), internalKey + key, def); + } + protected > ConfigData getConfigDataEnum(String key, Class type, T def) { return ConfigDataUtil.getEnum(config.getMainConfig(), internalKey + key, type, def); } @@ -1236,8 +1245,8 @@ protected boolean preCastTimeCheck(LivingEntity livingEntity, String[] args) { return true; } - public List tabComplete(CommandSender sender, String[] args) { - return null; + public SuggestionProvider suggestionProvider() { + return this instanceof SuggestionProvider ? (SuggestionProvider) this : SuggestionProvider.noSuggestions(); } /** @@ -1275,6 +1284,10 @@ public boolean isIgnoringGlobalCooldown() { return ignoreGlobalCooldown; } + public boolean isRequiringCastItemOnCommand() { + return requireCastItemOnCommand; + } + public boolean isValidItemForCastCommand(ItemStack item) { if (!requireCastItemOnCommand || castItems == null) return true; if (item == null && castItems.length == 1 && castItems[0].getType().isAir()) return true; diff --git a/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/HasItemCondition.java b/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/HasItemCondition.java index 27e5c5e68..247c9923b 100644 --- a/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/HasItemCondition.java +++ b/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/HasItemCondition.java @@ -47,7 +47,7 @@ public boolean initialize(@NotNull String var) { if (var.contains("|")) { String[] subVarData = var.split("\\|"); var = subVarData[0]; - name = Util.getMiniMessage(subVarData[1].replace("__", " ")); + name = Util.getItemMiniMessage(subVarData[1].replace("__", " ")); checkName = true; } else checkName = false; diff --git a/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/HoldingCondition.java b/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/HoldingCondition.java index 0e84b537d..6afa0bbbb 100644 --- a/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/HoldingCondition.java +++ b/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/HoldingCondition.java @@ -41,7 +41,7 @@ public boolean initialize(@NotNull String var) { if (varData[i].contains("|")) { String[] subVarData = varData[i].split("\\|"); varData[i] = subVarData[0]; - names[i] = Util.getMiniMessage(subVarData[1].replace("__", " ")); + names[i] = Util.getItemMiniMessage(subVarData[1].replace("__", " ")); checkName[i] = true; } else { names[i] = null; diff --git a/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/OffhandCondition.java b/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/OffhandCondition.java index 67ed290c4..2eab82b14 100644 --- a/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/OffhandCondition.java +++ b/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/OffhandCondition.java @@ -40,7 +40,7 @@ public boolean initialize(@NotNull String var) { if (varData[i].contains("|")) { String[] subVarData = varData[i].split("\\|"); varData[i] = subVarData[0]; - names[i] = Util.getMiniMessage(subVarData[1].replace("__", " ")); + names[i] = Util.getItemMiniMessage(subVarData[1].replace("__", " ")); checkName[i] = true; } else { names[i] = null; diff --git a/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/SignTextCondition.java b/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/SignTextCondition.java index b1717bef9..4b3d26cac 100644 --- a/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/SignTextCondition.java +++ b/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/SignTextCondition.java @@ -75,7 +75,7 @@ public boolean checkSignText(Location targetedLocation) { List lines = ((Sign) block.getState()).getSide(side).lines(); for (int i = 0; i < lines.size(); i++) { - String signLine = Util.getStrictStringFromComponent(lines.get(i)); + String signLine = Util.getStrictString(lines.get(i)); String checkLine = text.size() - 1 >= i ? text.get(i) : ""; if (signLine.equals(checkLine)) continue; return false; diff --git a/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/WearingCondition.java b/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/WearingCondition.java index 9716780ab..cf7268e97 100644 --- a/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/WearingCondition.java +++ b/core/src/main/java/com/nisovin/magicspells/castmodifiers/conditions/WearingCondition.java @@ -41,7 +41,7 @@ public boolean initialize(@NotNull String var) { if (varData[i].contains("|")) { String[] subVarData = varData[i].split("\\|"); varData[i] = subVarData[0]; - names[i] = Util.getMiniMessage(subVarData[1].replace("__", " ")); + names[i] = Util.getItemMiniMessage(subVarData[1].replace("__", " ")); checkName[i] = true; } else { names[i] = null; diff --git a/core/src/main/java/com/nisovin/magicspells/commands/CastCommands.java b/core/src/main/java/com/nisovin/magicspells/commands/CastCommands.java new file mode 100644 index 000000000..5794c331c --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/CastCommands.java @@ -0,0 +1,255 @@ +package com.nisovin.magicspells.commands; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.function.Function; + +import io.leangen.geantyref.TypeToken; + +import net.kyori.adventure.text.Component; + +import org.incendo.cloud.Command; +import org.incendo.cloud.type.Either; +import org.incendo.cloud.key.CloudKey; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.description.Description; +import org.incendo.cloud.parser.flag.CommandFlag; +import org.incendo.cloud.bukkit.parser.WorldParser; +import org.incendo.cloud.paper.PaperCommandManager; +import org.incendo.cloud.parser.standard.FloatParser; +import org.incendo.cloud.parser.standard.EitherParser; +import org.incendo.cloud.paper.parser.KeyedWorldParser; +import org.incendo.cloud.bukkit.data.SingleEntitySelector; +import org.incendo.cloud.bukkit.parser.location.LocationParser; +import org.incendo.cloud.bukkit.parser.selector.SingleEntitySelectorParser; + +import org.bukkit.World; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; + +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import com.nisovin.magicspells.Perm; +import com.nisovin.magicspells.Spell; +import com.nisovin.magicspells.util.Rotation; +import com.nisovin.magicspells.util.SpellData; +import com.nisovin.magicspells.util.CastResult; + +import org.incendo.cloud.component.CommandComponent; + +import com.nisovin.magicspells.util.Util; +import com.nisovin.magicspells.spells.TargetedEntitySpell; +import com.nisovin.magicspells.commands.parsers.SpellParser; +import com.nisovin.magicspells.spells.TargetedLocationSpell; +import com.nisovin.magicspells.commands.parsers.RotationParser; +import com.nisovin.magicspells.commands.parsers.OwnedSpellParser; +import com.nisovin.magicspells.commands.parsers.SpellCastArgumentsParser; +import com.nisovin.magicspells.commands.exceptions.GenericCommandException; +import com.nisovin.magicspells.commands.exceptions.InvalidCommandArgumentException; + +public class CastCommands { + + public static final CloudKey SPELL_CAST_ARGUMENTS_KEY = CloudKey.of("cast arguments", String[].class); + public static final CloudKey TARGET_ENTITY_KEY = CloudKey.of("entity", SingleEntitySelector.class); + public static final CloudKey> WORLD_KEY = CloudKey.of("world", new TypeToken<>() {}); + public static final CloudKey TARGET_LOCATION_KEY = CloudKey.of("location", Location.class); + public static final CloudKey TARGET_ROTATION_KEY = CloudKey.of("rotation", Rotation.class); + public static final CloudKey SPELL_KEY = CloudKey.of("spell", Spell.class); + + public static final CommandFlag POWER_FLAG = CommandFlag.builder("power") + .withComponent(FloatParser.floatParser()) + .withDescription(Description.of("Specify the power of the casted spell.")) + .withPermission(Perm.COMMAND_CAST_POWER) + .withAliases("p") + .build(); + + static void register(@NotNull PaperCommandManager manager) { + Command.Builder base = manager + .commandBuilder("ms", "magicspells") + .literal("cast") + .flag(POWER_FLAG); + + var spellComponent = CommandComponent.builder() + .key(SPELL_KEY) + .parser(SpellParser.spellParser()) + .description(Description.of("The spell to cast.")) + .required() + .build(); + + var castArgumentsComponent = CommandComponent.builder() + .key(SPELL_CAST_ARGUMENTS_KEY) + .parser(SpellCastArgumentsParser.spellCastArgumentsParser()) + .description(Description.of("Arguments to pass to the spell when casted.")) + .optional() + .build(); + + Command castSelfCommand = base + .literal("self") + .required(SPELL_KEY, OwnedSpellParser.ownedSpellParser(), Description.of("The spell to cast.")) + .argument(castArgumentsComponent) + .commandDescription(Description.of("Cast a spell.")) + .permission(Perm.COMMAND_CAST_SELF) + .handler(CastCommands::onCastSelf) + .build(); + + manager.command(castSelfCommand); + + manager.command(manager.commandBuilder("c", "cast") + .commandDescription(Description.of("Cast a spell.")) + .proxies(castSelfCommand) + ); + + Command castAsCommand = base + .literal("as") + .required( + TARGET_ENTITY_KEY, + SingleEntitySelectorParser.singleEntitySelectorParser(), + Description.of("The entity to cast the spell as.") + ) + .argument(spellComponent) + .argument(castArgumentsComponent) + .commandDescription(Description.of("Force an entity to cast a spell.")) + .permission(Perm.COMMAND_CAST_AS) + .handler(CastCommands::onCastAs) + .build(); + + manager.command(castAsCommand); + + // Legacy alias for /ms cast as. + manager.command(manager.commandBuilder("c", "cast") + .literal("forcecast") + .meta(HelpCommand.FILTER_FROM_HELP, true) + .proxies(castAsCommand) + ); + + manager.command(base + .literal("on") + .required( + TARGET_ENTITY_KEY, + SingleEntitySelectorParser.singleEntitySelectorParser(), + Description.of("The entity to cast the spell on.") + ) + .argument(spellComponent) + .argument(castArgumentsComponent) + .commandDescription(Description.of("Cast a spell on an entity.")) + .permission(Perm.COMMAND_CAST_ON) + .handler(CastCommands::onCastOn) + ); + + manager.command(base + .literal("at") + .argument(spellComponent) + .required( + WORLD_KEY, + EitherParser.eitherParser(KeyedWorldParser.keyedWorldParser(), WorldParser.worldParser()), + Description.of("The world the spell will be casted in.") + ) + .required( + TARGET_LOCATION_KEY, + LocationParser.locationParser(), + Description.of("The coordinates the spell will be casted at, formatted as .") + ) + .optional( + TARGET_ROTATION_KEY, + RotationParser.rotationParser(), + Description.of("The orientation the spell will be casted with, formatted as .") + ) + .argument(castArgumentsComponent) + .commandDescription(Description.of("Cast a spell at a location.")) + .permission(Perm.COMMAND_CAST_AT) + .handler(CastCommands::onCastAt) + ); + } + + private static void onCastSelf(CommandContext context) { + String[] arguments = context.getOrDefault(SPELL_CAST_ARGUMENTS_KEY, new String[0]); + float power = context.flags().getValue(POWER_FLAG).orElse(1f); + Spell spell = context.get(SPELL_KEY); + + CommandSourceStack stack = context.sender(); + CommandSender sender = stack.getSender(); + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), sender); + + if (executor instanceof LivingEntity caster) { + spell.hardCast(new SpellData(caster, power, arguments)); + return; + } + + if (executor instanceof ConsoleCommandSender console) { + if (spell.castFromConsole(console, arguments)) sender.sendMessage("Spell casted!"); + else sender.sendMessage("Spell cast failed."); + + return; + } + + throw new GenericCommandException("Cannot cast spell."); + } + + private static void onCastAs(CommandContext context) { + String[] arguments = context.getOrDefault(SPELL_CAST_ARGUMENTS_KEY, new String[0]); + SingleEntitySelector selector = context.get(TARGET_ENTITY_KEY); + float power = context.flags().getValue(POWER_FLAG).orElse(1f); + Spell spell = context.get(SPELL_KEY); + + Entity entity = selector.single(); + if (!(entity instanceof LivingEntity caster)) + throw new InvalidCommandArgumentException("Target is not a living entity"); + + spell.hardCast(new SpellData(caster, power, arguments)); + } + + private static void onCastOn(CommandContext context) { + SingleEntitySelector selector = context.get(TARGET_ENTITY_KEY); + String[] arguments = context.getOrDefault(SPELL_CAST_ARGUMENTS_KEY, new String[0]); + float power = context.flags().getValue(POWER_FLAG).orElse(1f); + Spell spell = context.get(SPELL_KEY); + + CommandSourceStack stack = context.sender(); + CommandSender sender = stack.getSender(); + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), sender); + + LivingEntity caster = executor instanceof LivingEntity le ? le : null; + + Entity entity = selector.single(); + if (!(entity instanceof LivingEntity target)) + throw new InvalidCommandArgumentException("Target is not a living entity"); + + if (!(spell instanceof TargetedEntitySpell targetedEntitySpell)) + throw new InvalidCommandArgumentException("Spell is not a targeted entity spell"); + + CastResult result = targetedEntitySpell.castAtEntity(new SpellData(caster, target, power, arguments)); + if (result.action() == Spell.PostCastAction.ALREADY_HANDLED) + sender.sendMessage(Util.getMessageText(Component.text("Spell cast failed."))); + } + + private static void onCastAt(CommandContext context) { + String[] arguments = context.getOrDefault(SPELL_CAST_ARGUMENTS_KEY, new String[0]); + float power = context.flags().getValue(POWER_FLAG).orElse(1f); + Spell spell = context.get(SPELL_KEY); + + World world = context.get(WORLD_KEY).primaryOrMapFallback(Function.identity()); + Location location = context.get(TARGET_LOCATION_KEY); + location.setWorld(world); + + Rotation rotation = context.getOrDefault(TARGET_ROTATION_KEY, null); + if (rotation != null) rotation.apply(location); + + CommandSourceStack stack = context.sender(); + CommandSender sender = stack.getSender(); + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), sender); + + LivingEntity caster = executor instanceof LivingEntity le ? le : null; + + if (!(spell instanceof TargetedLocationSpell targetedLocationSpell)) + throw new InvalidCommandArgumentException("Spell is not a targeted location spell"); + + CastResult result = targetedLocationSpell.castAtLocation(new SpellData(caster, location, power, arguments)); + if (result.action() == Spell.PostCastAction.ALREADY_HANDLED) + sender.sendMessage(Util.getMessageText(Component.text("Spell cast failed."))); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/CommandHelpFilter.java b/core/src/main/java/com/nisovin/magicspells/commands/CommandHelpFilter.java deleted file mode 100644 index 6354714ac..000000000 --- a/core/src/main/java/com/nisovin/magicspells/commands/CommandHelpFilter.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.nisovin.magicspells.commands; - -import java.util.Map; -import java.util.HashMap; -import java.util.Iterator; - -import co.aikar.commands.HelpEntry; -import co.aikar.commands.RootCommand; -import co.aikar.commands.CommandHelp; -import co.aikar.commands.RegisteredCommand; - -import org.bukkit.command.CommandSender; - -import com.nisovin.magicspells.MagicSpells; - -public class CommandHelpFilter { - - private static final Map magicPerms = new HashMap<>(); - - public static void mapPerms() { - for (RootCommand rootCommand : MagicSpells.getCommandManager().getRegisteredRootCommands()) { - for (RegisteredCommand command : rootCommand.getSubCommands().values()) { - HelpPermission annotation = command.getAnnotation(HelpPermission.class); - if (annotation == null) continue; - magicPerms.put(command.getCommand(), annotation.permission().getNode()); - } - } - } - - public static void filter(CommandSender sender, CommandHelp help) { - // Filter help entries by permissions. - Iterator iterator = help.getHelpEntries().iterator(); - while (iterator.hasNext()) { - String perm = magicPerms.get(iterator.next().getCommand()); - if (perm == null) continue; - if (sender.hasPermission(perm)) continue; - iterator.remove(); - } - } - -} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/DebugCommand.java b/core/src/main/java/com/nisovin/magicspells/commands/DebugCommand.java new file mode 100644 index 000000000..2427a23fa --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/DebugCommand.java @@ -0,0 +1,73 @@ +package com.nisovin.magicspells.commands; + +import org.bukkit.command.CommandSender; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.format.NamedTextColor; + +import org.incendo.cloud.key.CloudKey; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.description.Description; +import org.incendo.cloud.paper.PaperCommandManager; +import org.incendo.cloud.parser.standard.IntegerParser; + +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import com.nisovin.magicspells.Perm; +import com.nisovin.magicspells.MagicSpells; +import com.nisovin.magicspells.util.Util; + +public class DebugCommand { + + private static final CloudKey LEVEL_KEY = CloudKey.of("level", Integer.class); + + public static void register(PaperCommandManager manager) { + manager.command(manager.commandBuilder("ms", "magicspells") + .literal("debug") + .commandDescription(Description.of("Toggle debug mode, or set current debug level.")) + .optional(LEVEL_KEY, IntegerParser.integerParser(), Description.of("Enable debug, and set debug level to this value.")) + .permission(Perm.COMMAND_DEBUG) + .handler(DebugCommand::debug) + ); + } + + private static void debug(CommandContext context) { + Integer level = context.getOrDefault(LEVEL_KEY, null); + if (level == null) { + MagicSpells.setDebugLevel(MagicSpells.getDebugLevelOriginal()); + MagicSpells.setDebug(!MagicSpells.isDebug()); + sendOutput(context); + return; + } + + MagicSpells.setDebugLevel(level); + MagicSpells.setDebug(true); + sendOutput(context); + } + + private static void sendOutput(CommandContext context) { + boolean debug = MagicSpells.isDebug(); + int level = MagicSpells.getDebugLevel(); + + ComponentLike status; + if (debug) { + status = Component.text() + .append(Component.text("enabled", NamedTextColor.GREEN)) + .append(Component.text(" (level: ")) + .append(Component.text(level, NamedTextColor.GREEN)) + .append(Component.text(")")); + } else { + status = Component.text("disabled", NamedTextColor.RED); + } + + CommandSender sender = context.sender().getSender(); + sender.sendMessage(Util.getMessageText( + Component.text() + .append(Component.text("Debug is now ")) + .append(status) + .append(Component.text(".")) + )); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/HelpCommand.java b/core/src/main/java/com/nisovin/magicspells/commands/HelpCommand.java new file mode 100644 index 000000000..cece75320 --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/HelpCommand.java @@ -0,0 +1,57 @@ +package com.nisovin.magicspells.commands; + +import org.incendo.cloud.Command; +import org.incendo.cloud.key.CloudKey; +import org.incendo.cloud.component.DefaultValue; +import org.incendo.cloud.description.Description; +import org.incendo.cloud.paper.PaperCommandManager; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.minecraft.extras.MinecraftHelp; + +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import com.nisovin.magicspells.Perm; + +public class HelpCommand { + + public static CloudKey FILTER_FROM_HELP = CloudKey.of("__filter_from_help__", Boolean.class); + + public static void register(PaperCommandManager manager) { + MinecraftHelp minecraftHelp = MinecraftHelp.builder() + .commandManager(manager) + .audienceProvider(CommandSourceStack::getSender) + .commandPrefix("/magicspells help") + .commandFilter(command -> !command.commandMeta().contains(FILTER_FROM_HELP)) + .build(); + + Command.Builder base = manager.commandBuilder("ms", "magicspells"); + + manager.command(base + .meta(FILTER_FROM_HELP, true) + .permission(Perm.COMMAND_HELP) + .handler(context -> minecraftHelp.queryCommands("", context.sender())) + ); + + Command helpCommand = base + .literal("help") + .optional( + "command", + StringParser.greedyStringParser(), + DefaultValue.constant(""), + Description.of("Shows syntax and usage for the queried command.") + ) + .commandDescription(Description.of("Display command help.")) + .permission(Perm.COMMAND_HELP) + .handler(context -> minecraftHelp.queryCommands(context.get("command"), context.sender())) + .build(); + + manager.command(helpCommand); + + manager.command(base + .literal("?") + .meta(FILTER_FROM_HELP, true) + .proxies(helpCommand) + ); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/HelpPermission.java b/core/src/main/java/com/nisovin/magicspells/commands/HelpPermission.java deleted file mode 100644 index c25eae2c7..000000000 --- a/core/src/main/java/com/nisovin/magicspells/commands/HelpPermission.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.nisovin.magicspells.commands; - -import com.nisovin.magicspells.Perm; - -import java.lang.annotation.Target; -import java.lang.annotation.Retention; -import java.lang.annotation.ElementType; -import java.lang.annotation.RetentionPolicy; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface HelpPermission { - - Perm permission(); - -} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/MagicCommand.java b/core/src/main/java/com/nisovin/magicspells/commands/MagicCommand.java deleted file mode 100644 index b5ce14938..000000000 --- a/core/src/main/java/com/nisovin/magicspells/commands/MagicCommand.java +++ /dev/null @@ -1,901 +0,0 @@ -package com.nisovin.magicspells.commands; - -import java.util.*; -import java.io.File; -import java.io.IOException; -import java.util.regex.Pattern; - -import org.jetbrains.annotations.NotNull; - -import org.bukkit.*; -import org.bukkit.entity.Mob; -import org.bukkit.block.Block; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.util.RayTraceResult; -import org.bukkit.entity.LivingEntity; -import org.bukkit.inventory.ItemStack; -import org.bukkit.scheduler.BukkitTask; -import org.bukkit.command.CommandSender; -import org.bukkit.inventory.EntityEquipment; -import org.bukkit.command.ConsoleCommandSender; -import org.bukkit.configuration.file.YamlConfiguration; - -import co.aikar.commands.*; -import co.aikar.commands.annotation.*; -import co.aikar.commands.annotation.Optional; -import co.aikar.commands.bukkit.contexts.OnlinePlayer; - -import com.destroystokyo.paper.entity.ai.Goal; -import com.destroystokyo.paper.profile.ProfileProperty; - -import com.nisovin.magicspells.Perm; -import com.nisovin.magicspells.Spell; -import com.nisovin.magicspells.util.*; -import com.nisovin.magicspells.Spellbook; -import com.nisovin.magicspells.MagicSpells; -import com.nisovin.magicspells.variables.Variable; -import com.nisovin.magicspells.Spell.PostCastAction; -import com.nisovin.magicspells.mana.ManaChangeReason; -import com.nisovin.magicspells.handlers.MagicXpHandler; -import com.nisovin.magicspells.util.magicitems.MagicItem; -import com.nisovin.magicspells.util.magicitems.MagicItems; -import com.nisovin.magicspells.spells.TargetedEntitySpell; -import com.nisovin.magicspells.events.SpellbookReloadEvent; -import com.nisovin.magicspells.spells.TargetedLocationSpell; -import com.nisovin.magicspells.variables.variabletypes.GlobalVariable; -import com.nisovin.magicspells.variables.variabletypes.GlobalStringVariable; - -@CommandAlias("ms|magicspells") -public class MagicCommand extends BaseCommand { - - private static final File PLUGIN_FOLDER = MagicSpells.getInstance().getDataFolder(); - - private static final Pattern QUOTATIONS_PATTERN = Pattern.compile("^[\"']|[\"']$"); - - public MagicCommand() { - PaperCommandManager commandManager = MagicSpells.getCommandManager(); - - // Creating conditions. - commandManager.getCommandConditions().addCondition("mana_is_enabled", context -> { - if (!MagicSpells.isManaSystemEnabled()) throw new ConditionFailedException("The Mana system is not enabled."); - }); - - // Create command completions. - commandManager.getCommandCompletions().registerAsyncCompletion("spells", context -> - TxtUtil.tabCompleteSpellName(context.getSender()) - ); - commandManager.getCommandCompletions().registerAsyncCompletion("variables", context -> - MagicSpells.getVariableManager().getVariables().keySet() - ); - commandManager.getCommandCompletions().registerAsyncCompletion("magic_items", context -> - MagicItems.getMagicItemKeys() - ); - commandManager.getCommandCompletions().registerAsyncCompletion("looking_at", context -> { - Player player = context.getPlayer(); - if (player == null) return Collections.emptySet(); - - String config = context.getConfig(); - if (config == null || config.isEmpty()) return Collections.emptySet(); - - RayTraceResult result = player.rayTraceBlocks(6, FluidCollisionMode.SOURCE_ONLY); - if (result == null) return Collections.emptySet(); - - Block block = result.getHitBlock(); - if (block == null) return Collections.emptySet(); - - String value = switch (config.toLowerCase()) { - case "x" -> String.valueOf(block.getX()); - case "y" -> String.valueOf(block.getY()); - case "z" -> String.valueOf(block.getZ()); - case "pitch" -> String.valueOf(player.getLocation().getPitch()); - case "yaw" -> String.valueOf(player.getLocation().getYaw()); - default -> ""; - }; - return Set.of(value); - }); - commandManager.getCommandCompletions().registerAsyncCompletion("spell_target", context -> { - CommandSender sender = context.getSender(); - Player player = context.getPlayer(); - if (player == null) return TxtUtil.tabCompletePlayerName(sender); - RayTraceResult result = player.rayTraceEntities(6); - - Set targets = new HashSet<>(); - // Add the targeted entity's uuid/username first. - if (result != null && result.getHitEntity() instanceof LivingEntity entity) { - targets.add(entity instanceof Player pl ? pl.getName() : entity.getUniqueId().toString()); - } - targets.addAll(TxtUtil.tabCompletePlayerName(sender)); - return targets; - }); - commandManager.getCommandCompletions().registerAsyncCompletion("target_uuid", context -> { - Player player = context.getPlayer(); - if (player == null) return Collections.emptySet(); - - RayTraceResult result = player.rayTraceEntities(6); - if (result == null || !(result.getHitEntity() instanceof LivingEntity entity)) - return Collections.emptySet(); - return Set.of(entity.getUniqueId().toString()); - }); - commandManager.getCommandCompletions().registerAsyncCompletion("cast_data", context -> { - String[] args = (String[]) context.getContextValue(String.class.arrayType()); - CommandSender sender = context.getSender(); - if (args.length == 1) return TxtUtil.tabCompleteSpellName(sender); - - Spell spell = getSpell(args[0]); - if (spell == null) return null; - if (sender instanceof Player player) { - if (!MagicSpells.getSpellbook(player).hasSpell(spell) || !spell.canCastByCommand()) { - return null; - } - } else if (!(sender instanceof ConsoleCommandSender)) return null; - - List ret = new ArrayList<>(); - if (args.length == 2 && Perm.COMMAND_CAST_POWER.has(sender)) ret.add("-p:"); - List completion = spell.tabComplete(sender, Arrays.copyOfRange(args, 1, args.length)); - if (completion != null) ret.addAll(completion); - return ret; - }); - } - - private static Spell getSpell(String name) { - return MagicSpells.getSpellByName(QUOTATIONS_PATTERN.matcher(name).replaceAll("")); - } - - private static Spell getSpell(CommandIssuer issuer, String name) { - Spell spell = getSpell(name); - if (spell == null) issuer.sendMessage(MagicSpells.getTextColor() + "No matching spell found: '" + name + "'"); - return spell; - } - - private static Player getPlayerFromIssuer(CommandIssuer issuer) { - if (issuer.isPlayer()) return issuer.getIssuer(); - issuer.sendMessage(MagicSpells.getTextColor() + "You must be a player in order to perform this command."); - return null; - } - - private static LivingEntity getEntity(String input) { - if (input == null || input.isEmpty()) return null; - // Try an ID. - UUID uuid = null; - try { - uuid = UUID.fromString(input); - } catch (IllegalArgumentException ignored) {} - - if (uuid != null) { - Entity entity = Bukkit.getEntity(uuid); - return entity instanceof LivingEntity ? (LivingEntity) entity : null; - } - // Try to find a player instead. - Player player = Bukkit.getPlayer(input); - return player != null && player.isOnline() ? player : null; - } - - private static String[] getCustomArgs(String[] args, int fromIndex) { - if (args.length <= fromIndex) return null; - List spellArgs = new ArrayList<>(); - for (String string : Arrays.copyOfRange(args, fromIndex, args.length)) { - // Skip custom flags. - if (string.startsWith("-p:")) continue; - spellArgs.add(string); - } - return spellArgs.toArray(new String[0]); - } - - private static boolean hasPowerArg(String[] args, int fromIndex) { - if (args.length <= fromIndex) return false; - for (String string : args) { - if (!string.startsWith("-p:")) continue; - return true; - } - return false; - } - - private static float getPowerFromArgs(String[] args, int fromIndex) { - if (args.length <= fromIndex) return 1F; - for (String string : args) { - if (!string.startsWith("-p:")) continue; - return ACFUtil.parseFloat(string.substring(3), 1F); - } - return 1F; - } - - private static boolean noPermission(CommandSender sender, Perm perm) { - return noPermission(sender, perm, "You do not have permission to perform this command."); - } - - private static boolean noPermission(CommandSender sender, Perm perm, String error) { - if (perm.has(sender)) return false; - sender.sendMessage(Util.getMiniMessage("&4Error: " + error)); - return true; - } - - @HelpCommand - @Syntax("[command]") - @Description("Display command help.") - public void doHelp(CommandSender sender, CommandHelp help) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(sender, Perm.COMMAND_HELP)) return; - CommandHelpFilter.filter(sender, help); - help.showHelp(); - } - - @Subcommand("reload") - @CommandCompletion("@players @nothing") - @Syntax("[player]") - @Description("Reloads MagicSpells. If player is specified, then it reloads their spellbook.") - @HelpPermission(permission = Perm.COMMAND_RELOAD) - public static void onReload(CommandIssuer issuer, String[] args) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_RELOAD)) return; - - MagicSpells plugin = MagicSpells.getInstance(); - if (args.length == 0) { - plugin.unload(); - plugin.load(); - issuer.sendMessage(MagicSpells.getTextColor() + "MagicSpells plugin reloaded."); - return; - } - - if (noPermission(issuer.getIssuer(), Perm.COMMAND_RELOAD_SPELLBOOK)) return; - Player player = ACFBukkitUtil.findPlayerSmart(issuer, args[0]); - if (player == null) return; - - Map spellbooks = MagicSpells.getSpellbooks(); - UUID uuid = player.getUniqueId(); - - // Remove old spellbook - Spellbook spellbook = spellbooks.get(uuid); - if (spellbook != null) spellbook.destroy(); - - // Create new spellbook - spellbook = new Spellbook(player); - spellbooks.put(uuid, spellbook); - Bukkit.getPluginManager().callEvent(new SpellbookReloadEvent(player, spellbook)); - - issuer.sendMessage(MagicSpells.getTextColor() + "Spellbook for player '" + TxtUtil.getPossessiveName(player.getName()) + "' has been reloaded."); - - } - - @Subcommand("reloadeffectlib") - @Description("Reloads EffectLib, the shaded version inside MagicSpells.") - @HelpPermission(permission = Perm.COMMAND_RELOAD_EFFECTLIB) - public static void onReloadEffectLib(CommandIssuer issuer) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_RELOAD_EFFECTLIB)) return; - MagicSpells.resetEffectlib(); - issuer.sendMessage(MagicSpells.getTextColor() + "Effectlib reloaded."); - } - - @Subcommand("taskinfo") - @Description("Displays information about tasks.") - @HelpPermission(permission = Perm.COMMAND_TASKINFO) - public static void onTask(CommandIssuer issuer) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_TASKINFO)) return; - - List msTasks = new ArrayList<>(); - for (BukkitTask task : Bukkit.getScheduler().getPendingTasks()) { - if (task == null) continue; - if (!task.getOwner().equals(MagicSpells.getInstance())) continue; - msTasks.add(task); - } - - issuer.sendMessage(MagicSpells.getTextColor() + "EffectLib effect instances - " + MagicSpells.getEffectManager().getEffects().size()); - issuer.sendMessage(MagicSpells.getTextColor() + "MagicSpells tasks - " + msTasks.size()); - } - - @Subcommand("resetcd") - @CommandCompletion("*|@players *|@spells @nothing") - @Syntax("[player/*] [spell/*]") - @Description("Reset cooldown of all players or a player for a spell or all spells.") - @HelpPermission(permission = Perm.COMMAND_RESET_COOLDOWN) - public static void onResetCD(CommandIssuer issuer, String[] args) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_RESET_COOLDOWN)) return; - args = Util.splitParams(args); - Player player = null; - Spell spell = null; - - if (args.length > 0 && !args[0].isEmpty()) { - if (!args[0].equals("*")) { - player = ACFBukkitUtil.findPlayerSmart(issuer, args[0]); - if (player == null) return; - } - - if (args.length > 1 && !args[1].equals("*")) { - spell = getSpell(issuer, String.join(" ", Arrays.copyOfRange(args, 1, args.length))); - if (spell == null) return; - } - } - - if (player == null && args[0].isEmpty() && issuer.isPlayer()) player = issuer.getIssuer(); - - Set spells = new HashSet<>(); - if (spell == null) spells.addAll(MagicSpells.getSpells().values()); - else spells.add(spell); - for (Spell s : spells) { - if (player == null) s.getCooldowns().clear(); - else s.setCooldown(player, 0, false); - } - issuer.sendMessage(MagicSpells.getTextColor() + "Cooldowns reset" + (player == null ? "" : " for " + player.getName()) + (spell == null ? "" : " for spell " + Util.getLegacyFromMiniMessage(spell.getName()))); - } - - @Subcommand("mana") - @Conditions("mana_is_enabled") - public class ManaCommands extends BaseCommand { - - @Subcommand("show") - @CommandAlias("mana") - @Description("Display your mana.") - @HelpPermission(permission = Perm.COMMAND_MANA_SHOW) - public void onShow(CommandIssuer issuer) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_MANA_SHOW)) return; - Player player = getPlayerFromIssuer(issuer); - if (player == null) return; - MagicSpells.getManaHandler().showMana(player, true); - } - - @Subcommand("reset") - @CommandCompletion("@players @nothing") - @Syntax("[player]") - @Description("Reset mana of yourself or another player.") - @HelpPermission(permission = Perm.COMMAND_MANA_RESET) - public void onReset(CommandIssuer issuer, @Optional OnlinePlayer onlinePlayer) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_MANA_RESET)) return; - Player player = onlinePlayer == null ? getPlayerFromIssuer(issuer) : onlinePlayer.getPlayer(); - if (player == null) return; - MagicSpells.getManaHandler().createManaBar(player); - issuer.sendMessage(MagicSpells.getTextColor() + TxtUtil.getPossessiveName(player.getName()) + " mana was reset."); - } - - @Subcommand("setmax") - @CommandCompletion("@players @nothing") - @Syntax("[player] ") - @Description("Set the max mana of yourself or another player.") - @HelpPermission(permission = Perm.COMMAND_MANA_SET_MAX) - public void onSetMax(CommandIssuer issuer, String[] args) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_MANA_SET_MAX)) return; - if (args.length < 1) throw new InvalidCommandArgument(); - - int amount; - Player player = null; - if (ACFUtil.isInteger(args[0])) amount = Integer.parseInt(args[0]); - else { - if (args.length < 2) throw new InvalidCommandArgument(); - player = ACFBukkitUtil.findPlayerSmart(issuer, args[0]); - if (player == null) throw new InvalidCommandArgument(); - - if (!ACFUtil.isInteger(args[1])) throw new InvalidCommandArgument(); - amount = Integer.parseInt(args[1]); - } - - if (player == null) player = getPlayerFromIssuer(issuer); - if (player == null) return; - - MagicSpells.getManaHandler().setMaxMana(player, amount); - issuer.sendMessage(MagicSpells.getTextColor() + TxtUtil.getPossessiveName(player.getName()) + " max mana set to " + amount + "."); - } - - @Subcommand("add") - @CommandCompletion("@players @nothing") - @Syntax("[player] ") - @Description("Add mana to yourself or another player.") - @HelpPermission(permission = Perm.COMMAND_MANA_ADD) - public void onAdd(CommandIssuer issuer, String[] args) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_MANA_ADD)) return; - if (args.length < 1) throw new InvalidCommandArgument(); - - int amount; - Player player = null; - if (ACFUtil.isInteger(args[0])) amount = Integer.parseInt(args[0]); - else { - if (args.length < 2) throw new InvalidCommandArgument(); - player = ACFBukkitUtil.findPlayerSmart(issuer, args[0]); - if (player == null) throw new InvalidCommandArgument(); - - if (!ACFUtil.isInteger(args[1])) throw new InvalidCommandArgument(); - amount = Integer.parseInt(args[1]); - } - - if (player == null) player = getPlayerFromIssuer(issuer); - if (player == null) return; - - MagicSpells.getManaHandler().addMana(player, amount, ManaChangeReason.OTHER); - issuer.sendMessage(MagicSpells.getTextColor() + TxtUtil.getPossessiveName(player.getName()) + " mana was modified by " + amount + "."); - } - - @Subcommand("set") - @CommandCompletion("@players @nothing") - @Syntax("[player] ") - @Description("Set your or another player's mana to a new value.") - @HelpPermission(permission = Perm.COMMAND_MANA_SET) - public void onSet(CommandIssuer issuer, String[] args) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_MANA_SET)) return; - if (args.length < 1) throw new InvalidCommandArgument(); - - int amount; - Player player = null; - if (ACFUtil.isInteger(args[0])) amount = Integer.parseInt(args[0]); - else { - if (args.length < 2) throw new InvalidCommandArgument(); - player = ACFBukkitUtil.findPlayerSmart(issuer, args[0]); - if (player == null) throw new InvalidCommandArgument(); - - if (!ACFUtil.isInteger(args[1])) throw new InvalidCommandArgument(); - amount = Integer.parseInt(args[1]); - } - - if (player == null) player = getPlayerFromIssuer(issuer); - if (player == null) return; - - MagicSpells.getManaHandler().setMana(player, amount, ManaChangeReason.OTHER); - issuer.sendMessage(MagicSpells.getTextColor() + TxtUtil.getPossessiveName(player.getName()) + " mana was set to " + amount + "."); - } - - @Subcommand("updaterank") - @CommandCompletion("@players @nothing") - @Syntax("[player]") - @Description("Update your or another player's mana rank.") - @HelpPermission(permission = Perm.COMMAND_MANA_UPDATE_RANK) - public void onUpdateManaRank(CommandIssuer issuer, @Optional OnlinePlayer onlinePlayer) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_MANA_UPDATE_RANK)) return; - Player player = onlinePlayer == null ? getPlayerFromIssuer(issuer) : onlinePlayer.getPlayer(); - if (player == null) return; - boolean updated = MagicSpells.getManaHandler().updateManaRankIfNecessary(player); - MagicSpells.getManaHandler().showMana(player); - String status = updated ? "updated" : "already correct"; - issuer.sendMessage(MagicSpells.getTextColor() + TxtUtil.getPossessiveName(player.getName()) + " mana rank was " + status + "."); - } - - } - - @Subcommand("variable") - public class VariableCommands extends BaseCommand { - - @Subcommand("show") - @CommandCompletion("@variables @players @nothing") - @Syntax(" [player]") - @Description("Display value of a variable.") - @HelpPermission(permission = Perm.COMMAND_VARIABLE_SHOW) - public void onShowVariable(CommandIssuer issuer, String[] args) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_VARIABLE_SHOW)) return; - if (args.length == 0) throw new InvalidCommandArgument(); - - Variable variable = MagicSpells.getVariableManager().getVariable(args[0]); - if (variable == null) throw new ConditionFailedException("No matching variable found: '" + args[0] + "'"); - - String name = null; - if (!(variable instanceof GlobalVariable || variable instanceof GlobalStringVariable)) { - Player player = args.length == 1 ? getPlayerFromIssuer(issuer) : ACFBukkitUtil.findPlayerSmart(issuer, args[1]); - if (player == null) return; - - name = player.getName(); - } - - String message = name == null ? "Variable" : TxtUtil.getPossessiveName(name) + " variable"; - issuer.sendMessage(MagicSpells.getTextColor() + message + " value for " + args[0] + " is: " + variable.getStringValue(name)); - } - - @Subcommand("modify") - @CommandCompletion("@variables @players @nothing") - @Syntax(" ") - @Description("Modify a variable's value.") - @HelpPermission(permission = Perm.COMMAND_VARIABLE_MODIFY) - public void onModifyVariable(CommandIssuer issuer, String[] args) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_VARIABLE_MODIFY)) return; - if (args.length < 3) throw new InvalidCommandArgument(); - - String variableName = args[0]; - Variable variable = MagicSpells.getVariableManager().getVariable(variableName); - if (variable == null) throw new ConditionFailedException("No matching variable found: '" + variableName + "'"); - - Player player = null; - if (ACFBukkitUtil.isValidName(args[1])) { - player = Bukkit.getPlayer(args[1]); - if (player == null || !player.isOnline()) throw new ConditionFailedException("No matching player found: '" + args[1] + "'"); - } - String playerName = player == null ? "-" : player.getName(); - - VariableMod variableMod = new VariableMod(String.join(" ", Arrays.copyOfRange(args, 2, args.length))); - String oldValue = MagicSpells.getVariableManager().getStringValue(variableName, playerName); - MagicSpells.getVariableManager().processVariableMods(variableName, variableMod, player, new SpellData(player)); - - String message = player == null ? "Value" : TxtUtil.getPossessiveName(playerName) + " value"; - issuer.sendMessage(MagicSpells.getTextColor() + message + " of '" + variableName + "' was modified: '" + oldValue + "' to '" + MagicSpells.getVariableManager().getStringValue(variableName, playerName) + "'."); - } - - } - - @Subcommand("magicitem") - @CommandCompletion("@magic_items @players @nothing") - @Syntax(" [amount] [player]") - @Description("Give a user a Magic Item.") - @HelpPermission(permission = Perm.COMMAND_MAGIC_ITEM) - public static void onMagicItem(CommandIssuer issuer, String[] args) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_MAGIC_ITEM)) return; - if (args.length == 0) throw new InvalidCommandArgument(); - MagicItem magicItem = MagicItems.getMagicItemByInternalName(args[0]); - if (magicItem == null) throw new ConditionFailedException("No matching Magic Item found: '" + args[0] + "'"); - - int amount = 1; - Player player = null; - if (args.length > 1) { - if (ACFUtil.isInteger(args[1])) { - amount = Integer.parseInt(args[1]); - } else { - player = ACFBukkitUtil.findPlayerSmart(issuer, args[1]); - if (player == null) throw new InvalidCommandArgument(); - } - - if (args.length > 2) { - player = ACFBukkitUtil.findPlayerSmart(issuer, args[2]); - if (player == null) return; - } - } - - if (player == null) player = getPlayerFromIssuer(issuer); - if (player == null) return; - - ItemStack item = magicItem.getItemStack().clone(); - item.setAmount(amount); - player.getInventory().addItem(item); - issuer.sendMessage(MagicSpells.getTextColor() + player.getName() + " received a magic item (" + args[0] + " x" + amount + ")."); - } - - @Subcommand("util") - public class UtilCommands extends BaseCommand { - - @Subcommand("download") - @Syntax(" ") - @Description("Download a file from a specified URL and save it with the specified name. (The spell file prefix is not automatically added.)") - @HelpPermission(permission = Perm.COMMAND_UTIL_DOWNLOAD) - public void onDownload(CommandIssuer issuer, String[] args) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_UTIL_DOWNLOAD)) return; - if (args.length < 2) throw new InvalidCommandArgument(); - String fileName = args[1] + ".yml"; - File file = new File(PLUGIN_FOLDER, fileName); - if (file.exists()) throw new ConditionFailedException("The file '" + fileName + "' already exists!"); - boolean downloaded = Util.downloadFile(args[0], file); - if (downloaded) issuer.sendMessage(MagicSpells.getTextColor() + "SUCCESS! You will need reload the plugin to load new spells."); - else throw new ConditionFailedException("The file could not be downloaded."); - } - - @Subcommand("update") - @Syntax(" ") - @Description("This behaves the same as the download command, except it can overwrite existing files.") - @HelpPermission(permission = Perm.COMMAND_UTIL_UPDATE) - public void onUpdate(CommandIssuer issuer, String[] args) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_UTIL_UPDATE)) return; - if (args.length < 2) throw new InvalidCommandArgument(); - String fileName = args[1]; - File updateFile = new File(PLUGIN_FOLDER, "update-" + fileName + ".yml"); - boolean downloaded = Util.downloadFile(args[0], updateFile); - if (!downloaded) throw new ConditionFailedException("Update file failed to download."); - // Delete the existing file. - File oldFile = new File(PLUGIN_FOLDER, fileName + ".yml"); - if (oldFile.exists()) { - boolean deleted = oldFile.delete(); - if (!deleted) throw new ConditionFailedException("Old file could not be deleted. Aborting update, please delete the update file: '" + updateFile.getName() + "'"); - issuer.sendMessage(MagicSpells.getTextColor() + "Old file successfully deleted."); - } - // Rename the update to the original file's name. - boolean renamed = updateFile.renameTo(new File(PLUGIN_FOLDER, fileName + ".yml")); - if (!renamed) throw new ConditionFailedException("Failed to rename the update file, update failed"); - issuer.sendMessage(MagicSpells.getTextColor() + "Successfully renamed the update file to '" + fileName + ".yml'. You will need reload the plugin to load new spells."); - } - - @Subcommand("saveskin") - @CommandCompletion("@players @nothing") - @Syntax("[player]") - @Description("Save a player's current skin data to a readable file.") - @HelpPermission(permission = Perm.COMMAND_UTIL_SAVE_SKIN) - public void onSaveSkin(CommandIssuer issuer, @Optional Player player) { - if (!MagicSpells.isLoaded()) return; - - if (noPermission(issuer.getIssuer(), Perm.COMMAND_UTIL_SAVE_SKIN)) return; - if (player == null) player = getPlayerFromIssuer(issuer); - if (player == null) return; - - ProfileProperty latestSkin = player.getPlayerProfile() - .getProperties() - .stream() - .filter(prop -> prop.getName().equals("textures")) - .toList() - .getFirst(); - - YamlConfiguration data = new YamlConfiguration(); - data.set("skin", latestSkin.getValue()); - data.set("signature", latestSkin.getSignature()); - - File folder = new File(PLUGIN_FOLDER, "skins"); - if (!folder.exists()) folder.mkdir(); - try { - data.save(new File(folder, System.currentTimeMillis() + ".yml")); - } catch (IOException e) { - e.printStackTrace(); - return; - } - - issuer.sendMessage(MagicSpells.getTextColor() + TxtUtil.getPossessiveName(player.getName()) + " skin was saved."); - } - - @Subcommand("listgoals") - @CommandCompletion("@target_uuid @nothing") - @Syntax("[uuid]") - @Description("List an entity's mob goals.") - @HelpPermission(permission = Perm.COMMAND_UTIL_LIST_GOALS) - public void onListGoals(CommandIssuer issuer, String uuidString) { - if (noPermission(issuer.getIssuer(), Perm.COMMAND_UTIL_LIST_GOALS)) return; - - UUID uuid; - try { - uuid = UUID.fromString(uuidString); - } catch (IllegalArgumentException e) { - throw new ConditionFailedException("Passed UUID argument is not a valid UUIO."); - } - if (!(Bukkit.getEntity(uuid) instanceof Mob mob)) - throw new ConditionFailedException("UUID did not match an entity of Mob type."); - - Collection> goals = Bukkit.getMobGoals().getAllGoals(mob); - if (goals.isEmpty()) { - issuer.sendMessage(MagicSpells.getTextColor() + "Entity '" + uuid + "' has no mob goals."); - return; - } - - issuer.sendMessage(MagicSpells.getTextColor() + "Mob goals of entity '" + uuid + "':"); - for (Goal<@NotNull Mob> goal : goals) { - String entity = goal.getKey().getEntityClass().getSimpleName(); - String key = goal.getKey().getNamespacedKey().toString(); - issuer.sendMessage(MagicSpells.getTextColor() + " - " + entity + ": " + key + " " + goal.getTypes()); - } - } - - } - - @Subcommand("profilereport") - @Description("Save profile report to a file.") - @HelpPermission(permission = Perm.COMMAND_PROFILE_REPORT) - public static void onProfiler(CommandIssuer issuer) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_PROFILE_REPORT)) return; - MagicSpells.profilingReport(); - issuer.sendMessage(MagicSpells.getTextColor() + "Created profiling report."); - } - - @Subcommand("debug") - @Syntax("[level]") - @Description("Toggle MagicSpells debug mode.") - @HelpPermission(permission = Perm.COMMAND_DEBUG) - public static void onDebug(CommandIssuer issuer, @Optional Integer level) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_DEBUG)) return; - - int levelFinal = MagicSpells.isDebug() || level == null ? MagicSpells.getDebugLevelOriginal() : level; - MagicSpells.setDebugLevel(levelFinal); - MagicSpells.setDebug(!MagicSpells.isDebug()); - - issuer.sendMessage(MagicSpells.getTextColor() + "MagicSpells debug mode " + (MagicSpells.isDebug() ? "enabled (level: " + levelFinal + ")" : "disabled") + "."); - } - - @Subcommand("magicxp") - @CommandAlias("magicxp") - @Description("Display your MagicXp.") - @HelpPermission(permission = Perm.COMMAND_MAGICXP) - public void onShow(CommandIssuer issuer) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_MAGICXP)) return; - Player player = getPlayerFromIssuer(issuer); - if (player == null) return; - MagicXpHandler xpHandler = MagicSpells.getMagicXpHandler(); - if (xpHandler == null) throw new ConditionFailedException("The ManaXp system is not enabled."); - xpHandler.showXpInfo(player); - } - - @Subcommand("cast") - public class CastCommands extends BaseCommand { - - @Subcommand("self") - @CommandAlias("c|cast") - @CommandCompletion("@cast_data") - @Syntax(" [-p:(power)] [spellArgs]") - @Description("Cast a spell. (You can optionally define power: -p:1.0)") - @HelpPermission(permission = Perm.COMMAND_CAST_SELF) - public void onCastSelf(CommandIssuer issuer, String[] args) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_CAST_SELF)) return; - args = Util.splitParams(args); - if (args[0].isEmpty()) throw new InvalidCommandArgument(); - - // This is an abstract way to preserve an old command alias for the "ms cast as" command. ("c forcecast") - if (args[0].equals("forcecast") && args.length > 2) { - onCastAs(issuer, args[1], Arrays.copyOfRange(args, 2, args.length)); - return; - } - - Spell spell = getSpell(issuer, args[0]); - if (spell == null) return; - - // Get spell power if the user has permission. - if (hasPowerArg(args, 1)) { - boolean noPowerPerm = noPermission(issuer.getIssuer(), Perm.COMMAND_CAST_POWER,"You do not have permission to use the power parameter."); - if (noPowerPerm) return; - } - float power = getPowerFromArgs(args, 1); - String[] spellArgs = getCustomArgs(args, 1); - - CommandSender sender = issuer.getIssuer(); - // Console - if (sender instanceof ConsoleCommandSender) { - boolean casted = spell.castFromConsole(issuer.getIssuer(), spellArgs); - if (casted) issuer.sendMessage("Spell casted!"); - else issuer.sendMessage("Spell failed to cast."); - return; - } - // Player - if (sender instanceof Player player) { - if (spell.isHelperSpell() && !Perm.COMMAND_CAST_SELF_HELPER.has(player) || !MagicSpells.getSpellbook(player).hasSpell(spell)) { - MagicSpells.sendMessage(MagicSpells.getTextColor() + MagicSpells.getUnknownSpellMessage(), player, null); - return; - } - - if (!spell.canCastByCommand()) { - MagicSpells.sendMessage(MagicSpells.getTextColor() + spell.getStrCantCastByCommand(), player, null); - return; - } - - if (!spell.isValidItemForCastCommand(player.getInventory().getItemInMainHand())) { - MagicSpells.sendMessage(spell.getStrWrongCastItem(), player, null); - return; - } - - spell.hardCast(new SpellData(player, power, spellArgs)); - return; - } - // LivingEntity - if (sender instanceof LivingEntity livingEntity) { - if (!spell.canCastByCommand()) return; - EntityEquipment equipment = livingEntity.getEquipment(); - if (equipment == null) return; - if (!spell.isValidItemForCastCommand(equipment.getItemInMainHand())) return; - spell.hardCast(new SpellData(livingEntity, power, spellArgs)); - } - } - - @Subcommand("as") - @CommandCompletion("@spell_target @cast_data") - @Syntax(" (-p:[power]) [spellArgs]") - @Description("Force a player to cast a spell. (You can optionally define power: -p:1.0)") - @HelpPermission(permission = Perm.COMMAND_CAST_AS) - public void onCastAs(CommandIssuer issuer, String targetName, String[] args) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_CAST_AS)) return; - - LivingEntity target = getEntity(targetName); - if (target == null) throw new ConditionFailedException("Entity not found."); - - args = Util.splitParams(args); - Spell spell = getSpell(issuer, args[0]); - if (spell == null) return; - - // Get spell power if the user has permission. - if (hasPowerArg(args, 1)) { - boolean noPowerPerm = noPermission(issuer.getIssuer(), Perm.COMMAND_CAST_POWER,"You do not have permission to use the power parameter."); - if (noPowerPerm) return; - } - float power = getPowerFromArgs(args, 1); - String[] spellArgs = getCustomArgs(args, 1); - spell.hardCast(new SpellData(target, power, spellArgs)); - } - - @Subcommand("on") - @CommandCompletion("@spell_target @cast_data") - @Syntax(" (-p:[power]) [spellArgs]") - @Description("Cast a spell on an entity. (You can optionally define power: -p:1.0)") - @HelpPermission(permission = Perm.COMMAND_CAST_ON) - public void onCastOn(CommandIssuer issuer, String targetName, String[] args) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_CAST_ON)) return; - - LivingEntity target = getEntity(targetName); - if (target == null) throw new ConditionFailedException("Entity not found."); - - args = Util.splitParams(args); - Spell spell = getSpell(issuer, args[0]); - if (spell == null) return; - if (!(spell instanceof TargetedEntitySpell newSpell)) { - throw new ConditionFailedException("Spell is not a targeted entity spell."); - } - - // Get spell power if the user has permission. - if (hasPowerArg(args, 1)) { - boolean noPowerPerm = noPermission(issuer.getIssuer(), Perm.COMMAND_CAST_POWER,"You do not have permission to use the power parameter."); - if (noPowerPerm) return; - } - float power = getPowerFromArgs(args, 1); - String[] spellArgs = getCustomArgs(args, 1); - - LivingEntity caster = issuer.getIssuer() instanceof LivingEntity ? issuer.getIssuer() : null; - CastResult result = newSpell.castAtEntity(new SpellData(caster, target, power, spellArgs)); - if (result.action() == PostCastAction.ALREADY_HANDLED) - throw new ConditionFailedException("Spell probably cannot be cast from console."); - } - - @Subcommand("at") - @CommandCompletion("@spells @worlds @looking_at:X @looking_at:Y @looking_at:Z @looking_at:yaw @looking_at:pitch @nothing") - @Syntax(" [world] [yaw] [pitch]") - @Description("Cast a spell at a location.") - @HelpPermission(permission = Perm.COMMAND_CAST_AT) - public void onCastAt(CommandIssuer issuer, String[] args) { - if (!MagicSpells.isLoaded()) return; - if (noPermission(issuer.getIssuer(), Perm.COMMAND_CAST_AT)) return; - - args = Util.splitParams(args); - if (args.length < 4) throw new InvalidCommandArgument(); - Spell spell = getSpell(issuer, args[0]); - if (spell == null) return; - if (!(spell instanceof TargetedLocationSpell newSpell)) { - throw new ConditionFailedException("Spell is not a targeted location spell."); - } - - World world = null; - float yaw = 0, pitch = 0; - int i = 0; - // Has world parameter. - if (!ACFUtil.isDouble(args[1])) { - if (args.length < 5) throw new InvalidCommandArgument(); - world = Bukkit.getWorld(args[1]); - i = 1; - } - // Use issuer's world. - else if (issuer.getIssuer() instanceof LivingEntity) { - Location location = ((LivingEntity) issuer.getIssuer()).getLocation(); - world = location.getWorld(); - // If only coordinates were specified. - if (args.length < 5) { - yaw = location.getYaw(); - pitch = location.getPitch(); - } - } - // This fails if the world wasn't specified and the issuer is console, or if the world was invalid. - if (world == null) throw new ConditionFailedException("No world found."); - - // Parse coordinates. - if (!ACFUtil.isDouble(args[1 + i])) throw new InvalidCommandArgument(); - if (!ACFUtil.isDouble(args[2 + i])) throw new InvalidCommandArgument(); - if (!ACFUtil.isDouble(args[3 + i])) throw new InvalidCommandArgument(); - double x = ACFUtil.parseDouble(args[1 + i]); - double y = ACFUtil.parseDouble(args[2 + i]); - double z = ACFUtil.parseDouble(args[3 + i]); - if (args.length > 4 + i) { - if (!ACFUtil.isFloat(args[4 + i])) throw new InvalidCommandArgument(); - yaw = ACFUtil.parseFloat(args[4 + i]); - } - if (args.length > 5 + i) { - if (!ACFUtil.isFloat(args[5 + i])) throw new InvalidCommandArgument(); - pitch = ACFUtil.parseFloat(args[5 + i]); - } - Location location = new Location(world, x, y, z, yaw, pitch); - - // Handle with or without caster. - SpellData data = new SpellData(issuer.getIssuer() instanceof LivingEntity le ? le : null, location); - CastResult result = newSpell.castAtLocation(data); - - if (result.action() == PostCastAction.ALREADY_HANDLED) - throw new ConditionFailedException("Spell probably cannot be cast from console."); - } - - } - -} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/MagicCommands.java b/core/src/main/java/com/nisovin/magicspells/commands/MagicCommands.java new file mode 100644 index 000000000..f5e939b79 --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/MagicCommands.java @@ -0,0 +1,160 @@ +package com.nisovin.magicspells.commands; + +import java.util.Map; + +import org.incendo.cloud.parser.standard.*; +import org.jetbrains.annotations.NotNull; + +import io.leangen.geantyref.TypeToken; +import io.leangen.geantyref.GenericTypeReflector; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; + +import com.mojang.brigadier.arguments.StringArgumentType; + +import org.incendo.cloud.parser.ArgumentParser; +import org.incendo.cloud.paper.PaperCommandManager; +import org.incendo.cloud.caption.StandardCaptionKeys; +import org.incendo.cloud.services.type.ConsumerService; +import org.incendo.cloud.brigadier.CloudBrigadierManager; +import org.incendo.cloud.exception.ArgumentParseException; +import org.incendo.cloud.brigadier.argument.BrigadierMapping; +import org.incendo.cloud.exception.CommandExecutionException; +import org.incendo.cloud.exception.handling.ExceptionHandler; +import org.incendo.cloud.brigadier.argument.BrigadierMappings; +import org.incendo.cloud.bukkit.internal.BukkitBrigadierMapper; +import org.incendo.cloud.minecraft.extras.caption.RichVariable; +import org.incendo.cloud.brigadier.argument.ArgumentTypeFactory; +import org.incendo.cloud.brigadier.suggestion.TooltipSuggestion; +import org.incendo.cloud.exception.handling.ExceptionController; +import org.incendo.cloud.minecraft.extras.MinecraftExceptionHandler; +import org.incendo.cloud.minecraft.extras.suggestion.ComponentTooltipSuggestion; + +import org.bukkit.command.CommandSender; + +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.MessageComponentSerializer; + +import com.nisovin.magicspells.MagicSpells; +import com.nisovin.magicspells.commands.parsers.*; +import com.nisovin.magicspells.commands.exceptions.GenericCommandException; +import com.nisovin.magicspells.commands.exceptions.InvalidCommandArgumentException; +import com.nisovin.magicspells.util.Util; + +public class MagicCommands { + + @SuppressWarnings({"rawtypes", "unchecked"}) + public static void register(@NotNull PaperCommandManager manager) { + CloudBrigadierManager brigadierManager = manager.brigadierManager(); + brigadierManager.setNativeNumberSuggestions(true); + + brigadierManager.registerMapping(new TypeToken() {}, builder -> builder + .cloudSuggestions() + .toConstant(StringArgumentType.greedyString()) + ); + + brigadierManager.registerMapping(new TypeToken>() {}, builder -> builder + .cloudSuggestions() + .toConstant(StringArgumentType.greedyString()) + ); + + brigadierManager.registerMapping(new TypeToken>() {}, builder -> builder + .cloudSuggestions() + .to(parser -> { + BrigadierMappings mappings = brigadierManager.mappings(); + + ArgumentParser primaryParser = parser.primary().parser(); + Class primaryParserClass = primaryParser.getClass(); + + BrigadierMapping, ?> primaryMapping = mappings.mapping(primaryParserClass); + if (primaryMapping != null && primaryMapping.mapper() != null) + return primaryMapping.mapper().apply(primaryParser); + + Map, ArgumentTypeFactory> defaultArgumentFactories = brigadierManager.defaultArgumentTypeFactories(); + + ArgumentTypeFactory primaryFactory = defaultArgumentFactories.get(GenericTypeReflector.erase(primaryParserClass)); + if (primaryFactory != null) + return primaryFactory.create(); + + ArgumentParser fallbackParser = parser.fallback().parser(); + Class fallbackParserClass = fallbackParser.getClass(); + + BrigadierMapping, ?> fallbackMapping = mappings.mapping(fallbackParserClass); + if (fallbackMapping != null && fallbackMapping.mapper() != null) + return fallbackMapping.mapper().apply(fallbackParser); + + ArgumentTypeFactory fallbackFactory = defaultArgumentFactories.get(GenericTypeReflector.erase(fallbackParserClass)); + if (fallbackFactory != null) + return fallbackFactory.create(); + + return StringArgumentType.word(); + }) + ); + + BukkitBrigadierMapper brigadierMapper = new BukkitBrigadierMapper<>(MagicSpells.getInstance().getLogger(), brigadierManager); + brigadierMapper.mapSimpleNMS(new TypeToken>() {}, "angle"); + brigadierMapper.mapSimpleNMS(new TypeToken>() {}, "rotation"); + + // Less restrictive in terms of valid non-quoted characters compared to the brigadier string type. + brigadierMapper.mapSimpleNMS(new TypeToken() {}, "nbt_path", true); + brigadierMapper.mapSimpleNMS(new TypeToken>() {}, "nbt_path", true); + + MinecraftExceptionHandler.create(CommandSourceStack::getSender) + .defaultHandlers() + .handler(GenericCommandException.class, (formatter, context) -> { + GenericCommandException exception = context.exception(); + return Util.getMessageText(Component.text(exception.getMessage())); + }) + .handler(InvalidCommandArgumentException.class, (formatter, context) -> { + InvalidCommandArgumentException exception = context.exception(); + + return Component.text() + .color(NamedTextColor.RED) + .append(context.context().formatCaption( + formatter, + StandardCaptionKeys.EXCEPTION_INVALID_ARGUMENT, + RichVariable.of("cause", Component.text(exception.getMessage(), NamedTextColor.GRAY)) + )); + }) + .registerTo(manager); + + ExceptionController controller = manager.exceptionController(); + controller.registerHandler(ArgumentParseException.class, ExceptionHandler.unwrappingHandler(GenericCommandException.class)); + controller.registerHandler(CommandExecutionException.class, ExceptionHandler.unwrappingHandler(GenericCommandException.class)); + controller.registerHandler(CommandExecutionException.class, ExceptionHandler.unwrappingHandler(InvalidCommandArgumentException.class)); + + manager.registerCommandPreProcessor(context -> { + if (!MagicSpells.isLoaded()) { + CommandSender sender = context.commandContext().sender().getSender(); + sender.sendMessage(Component.text("MagicSpells is not currently loaded.", NamedTextColor.RED)); + + ConsumerService.interrupt(); + } + }); + + manager.appendSuggestionMapper(suggestion -> { + if (!(suggestion instanceof ComponentTooltipSuggestion componentTooltipSuggestion)) + return suggestion; + + return TooltipSuggestion.suggestion( + suggestion.suggestion(), + MessageComponentSerializer.message().serializeOrNull(componentTooltipSuggestion.tooltip()) + ); + }); + + CastCommands.register(manager); + DebugCommand.register(manager); + HelpCommand.register(manager); + MagicItemCommand.register(manager); + MagicXpCommand.register(manager); + ManaCommands.register(manager); + ProfileReportCommand.register(manager); + ReloadCommands.register(manager); + ResetCooldownCommand.register(manager); + TaskInfoCommand.register(manager); + UtilCommands.register(manager); + VariableCommands.register(manager); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/MagicItemCommand.java b/core/src/main/java/com/nisovin/magicspells/commands/MagicItemCommand.java new file mode 100644 index 000000000..56f9298a7 --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/MagicItemCommand.java @@ -0,0 +1,111 @@ +package com.nisovin.magicspells.commands; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.incendo.cloud.key.CloudKey; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.description.Description; +import org.incendo.cloud.parser.flag.CommandFlag; +import org.incendo.cloud.paper.PaperCommandManager; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.parser.standard.IntegerParser; +import org.incendo.cloud.suggestion.SuggestionProvider; +import org.incendo.cloud.bukkit.data.MultiplePlayerSelector; +import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser; + +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import com.nisovin.magicspells.Perm; +import com.nisovin.magicspells.util.magicitems.MagicItem; +import com.nisovin.magicspells.util.magicitems.MagicItems; +import com.nisovin.magicspells.commands.exceptions.InvalidCommandArgumentException; + +public class MagicItemCommand { + + private static final CloudKey TARGET_PLAYERS_KEY = CloudKey.of("player", MultiplePlayerSelector.class); + private static final CloudKey MAGIC_ITEM_KEY = CloudKey.of("magic item", String.class); + private static final CloudKey AMOUNT_KEY = CloudKey.of("amount", Integer.class); + + private static final CommandFlag DROP_LEFTOVER_FLAG = CommandFlag.builder("drop-leftover") + .withDescription(Description.of("If used, leftover items will be dropped on the ground.")) + .withAliases("d") + .build(); + + static void register(@NotNull PaperCommandManager manager) { + manager.command(manager.commandBuilder("ms", "magicspells") + .literal("magicitem") + .required( + TARGET_PLAYERS_KEY, + MultiplePlayerSelectorParser.multiplePlayerSelectorParser(false), + Description.of("Selector for the player(s) to give the magic item to.") + ) + .required( + MAGIC_ITEM_KEY, + StringParser.stringParser(), + Description.of("The magic item to give."), + SuggestionProvider.suggestingStrings(MagicItems.getMagicItemKeys()) + ) + .optional( + AMOUNT_KEY, + IntegerParser.integerParser(1), + Description.of("The amount of the magic item to give.") + ) + .flag(DROP_LEFTOVER_FLAG) + .commandDescription(Description.of("Give a magic item to players.")) + .permission(Perm.COMMAND_MAGIC_ITEM) + .handler(MagicItemCommand::magicItem) + ); + } + + private static void magicItem(CommandContext context) { + String internalName = context.get(MAGIC_ITEM_KEY); + + MagicItem magicItem = MagicItems.getMagicItemByInternalName(internalName); + if (magicItem == null) + throw new InvalidCommandArgumentException("Invalid magic item '" + internalName + "'"); + + Collection players = context.get(TARGET_PLAYERS_KEY).values(); + boolean dropLeftOver = context.flags().isPresent(DROP_LEFTOVER_FLAG); + Integer amount = context.getOrDefault(AMOUNT_KEY, null); + + Collection items = divideItems(magicItem, amount); + players.forEach(p -> p.give(items, dropLeftOver)); + } + + private static Collection divideItems(MagicItem magicItem, Integer amount) { + ItemStack item = magicItem.getItemStack().clone(); + if (amount == null) return List.of(item); + + int maxStackSize = item.getMaxStackSize(); + if (amount < item.getMaxStackSize()) { + item.setAmount(amount); + return List.of(item); + } + + ItemStack maxSizeStack = item.clone(); + maxSizeStack.setAmount(maxStackSize); + + int fullStackCount = amount / maxStackSize; + int leftover = amount % maxStackSize; + + Collection fullStacks = Collections.nCopies(fullStackCount, maxSizeStack); + if (leftover == 0) return fullStacks; + + List items = new ArrayList<>(fullStackCount + 1); + items.addAll(fullStacks); + + item.setAmount(leftover); + items.add(item); + + return items; + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/MagicXpCommand.java b/core/src/main/java/com/nisovin/magicspells/commands/MagicXpCommand.java new file mode 100644 index 000000000..643bc7029 --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/MagicXpCommand.java @@ -0,0 +1,59 @@ +package com.nisovin.magicspells.commands; + +import java.util.Objects; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; + +import org.incendo.cloud.Command; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.description.Description; +import org.incendo.cloud.paper.PaperCommandManager; + +import org.bukkit.entity.Player; +import org.bukkit.command.CommandSender; + +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import com.nisovin.magicspells.Perm; +import com.nisovin.magicspells.MagicSpells; +import com.nisovin.magicspells.handlers.MagicXpHandler; + +public class MagicXpCommand { + + public static void register(PaperCommandManager manager) { + Command magicXpCommand = manager.commandBuilder("ms", "magicspells") + .literal("magicxp") + .commandDescription(Description.of("Display your magic xp.")) + .permission(Perm.COMMAND_MAGICXP) + .handler(MagicXpCommand::magicXp) + .build(); + + manager.command(magicXpCommand); + + manager.command(manager.commandBuilder("magicxp") + .commandDescription(Description.of("Display your magic xp.")) + .proxies(magicXpCommand) + ); + } + + private static void magicXp(CommandContext context) { + CommandSourceStack stack = context.sender(); + CommandSender sender = stack.getSender(); + + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), sender); + if (!(executor instanceof Player player)) { + sender.sendMessage(Component.text("You must be a player in order to perform this command.", NamedTextColor.RED)); + return; + } + + MagicXpHandler xpHandler = MagicSpells.getMagicXpHandler(); + if (xpHandler == null) { + sender.sendMessage(Component.text("The magic xp system is not enabled.", NamedTextColor.RED)); + return; + } + + xpHandler.showXpInfo(player); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/ManaCommands.java b/core/src/main/java/com/nisovin/magicspells/commands/ManaCommands.java new file mode 100644 index 000000000..ccfe02fdc --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/ManaCommands.java @@ -0,0 +1,245 @@ +package com.nisovin.magicspells.commands; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.CompletableFuture; + +import io.leangen.geantyref.TypeToken; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; + +import org.incendo.cloud.Command; +import org.incendo.cloud.key.CloudKey; +import org.incendo.cloud.component.DefaultValue; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.description.Description; +import org.incendo.cloud.paper.PaperCommandManager; +import org.incendo.cloud.component.CommandComponent; +import org.incendo.cloud.parser.ArgumentParseResult; +import org.incendo.cloud.parser.standard.IntegerParser; +import org.incendo.cloud.processors.requirements.Requirement; +import org.incendo.cloud.processors.requirements.Requirements; +import org.incendo.cloud.processors.requirements.RequirementPostprocessor; +import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser; + +import org.bukkit.entity.Player; +import org.bukkit.command.CommandSender; + +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import com.nisovin.magicspells.Perm; +import com.nisovin.magicspells.MagicSpells; +import com.nisovin.magicspells.mana.ManaHandler; +import com.nisovin.magicspells.mana.ManaChangeReason; +import com.nisovin.magicspells.commands.exceptions.InvalidCommandArgumentException; +import com.nisovin.magicspells.util.Util; + +public class ManaCommands { + + private static final CloudKey> MANA_REQUIREMENT_KEY = CloudKey.of("mana_is_enabled", new TypeToken<>() {}); + private static final CloudKey> PLAYERS_KEY = CloudKey.of("players", new TypeToken<>() {}); + private static final CloudKey AMOUNT_KEY = CloudKey.of("amount", Integer.class); + + static void register(PaperCommandManager manager) { + manager.registerCommandPostProcessor(RequirementPostprocessor.of( + MANA_REQUIREMENT_KEY, + (context, requirement) -> { + CommandSender sender = context.sender().getSender(); + sender.sendMessage(Component.text("The mana system is not enabled.", NamedTextColor.RED)); + } + )); + + CommandComponent.Builder> playersComponent = CommandComponent + .builder( + PLAYERS_KEY, + MultiplePlayerSelectorParser.multiplePlayerSelectorParser() + .mapSuccess( + new TypeToken<>() {}, + (context, selector) -> CompletableFuture.completedFuture(selector.values()) + ) + ); + + CommandComponent.Builder> optionalPlayersComponent = CommandComponent + .builder( + PLAYERS_KEY, + MultiplePlayerSelectorParser.multiplePlayerSelectorParser() + .mapSuccess( + new TypeToken<>() {}, + (context, selector) -> CompletableFuture.completedFuture(selector.values()) + ) + ) + .optional(DefaultValue.failableDynamic(context -> { + CommandSourceStack stack = context.sender(); + + CommandSender sender = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + if (!(sender instanceof Player player)) { + return ArgumentParseResult.failure(new InvalidCommandArgumentException( + "A player must be specified" + )); + } + + return ArgumentParseResult.success(Collections.singleton(player)); + })); + + Command.Builder base = manager.commandBuilder("ms", "magicspells") + .literal("mana") + .meta(MANA_REQUIREMENT_KEY, Requirements.of(new ManaRequirement())); + + manager.command(manager.commandBuilder("mana") + .commandDescription(Description.of("Display your mana.")) + .meta(MANA_REQUIREMENT_KEY, Requirements.of(new ManaRequirement())) + .permission(Perm.COMMAND_MANA_SHOW) + .handler(ManaCommands::show) + ); + + manager.command(base + .literal("show") + .commandDescription(Description.of("Display your mana.")) + .permission(Perm.COMMAND_MANA_SHOW) + .handler(ManaCommands::show) + ); + + manager.command(base + .literal("add") + .argument(playersComponent.description(Description.of("Selector for the player(s) to add the mana to."))) + .required(AMOUNT_KEY, IntegerParser.integerParser(), Description.of("Amount of mana to add.")) + .commandDescription(Description.of("Add to the mana of players.")) + .permission(Perm.COMMAND_MANA_ADD) + .handler(ManaCommands::add) + ); + + manager.command(base + .literal("set") + .argument(playersComponent.description(Description.of("Selector for the player(s) to set the mana for."))) + .required(AMOUNT_KEY, IntegerParser.integerParser(), Description.of("Amount to set the mana to.")) + .commandDescription(Description.of("Set the mana of players.")) + .permission(Perm.COMMAND_MANA_SET) + .handler(ManaCommands::set) + ); + + manager.command(base + .literal("setmax") + .argument(playersComponent.description(Description.of("Selector for the player(s) to set the max mana for."))) + .required(AMOUNT_KEY, IntegerParser.integerParser(), Description.of("Amount to set the max mana to.")) + .commandDescription(Description.of("Set the max mana of players.")) + .permission(Perm.COMMAND_MANA_SET_MAX) + .handler(ManaCommands::setMax) + ); + + manager.command(base + .literal("reset") + .argument(optionalPlayersComponent.description(Description.of("Selector for the player(s) to reset mana for."))) + .commandDescription(Description.of("Reset the mana of players.")) + .permission(Perm.COMMAND_MANA_RESET) + .handler(ManaCommands::reset) + ); + + manager.command(base + .literal("updaterank") + .argument(optionalPlayersComponent.description(Description.of("Selector for the player(s) to update the mana rank for."))) + .commandDescription(Description.of("Update players' mana ranks, if possible.")) + .permission(Perm.COMMAND_MANA_UPDATE_RANK) + .handler(ManaCommands::updateRank) + ); + } + + private static void show(CommandContext context) { + CommandSourceStack stack = context.sender(); + CommandSender sender = stack.getSender(); + + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), sender); + if (!(executor instanceof Player player)) { + sender.sendMessage(Component.text("You must be a player in order to perform this command.", NamedTextColor.RED)); + return; + } + + MagicSpells.getManaHandler().showMana(player, true); + } + + private static void add(CommandContext context) { + Collection players = context.get(PLAYERS_KEY); + int amount = context.get(AMOUNT_KEY); + + ManaHandler handler = MagicSpells.getManaHandler(); + players.forEach(player -> handler.addMana(player, amount, ManaChangeReason.OTHER)); + + context.sender().getSender().sendMessage(Util.getMessageText( + Component.text(getName(players) + "mana was modified by " + amount + ".") + )); + } + + private static void set(CommandContext context) { + Collection players = context.get(PLAYERS_KEY); + int amount = context.get(AMOUNT_KEY); + + ManaHandler handler = MagicSpells.getManaHandler(); + players.forEach(player -> handler.setMana(player, amount, ManaChangeReason.OTHER)); + + context.sender().getSender().sendMessage(Util.getMessageText( + Component.text(getName(players) + "mana was set to " + amount + ".") + )); + } + + private static void setMax(CommandContext context) { + Collection players = context.get(PLAYERS_KEY); + int amount = context.get(AMOUNT_KEY); + + ManaHandler handler = MagicSpells.getManaHandler(); + players.forEach(player -> handler.setMaxMana(player, amount)); + + context.sender().getSender().sendMessage(Util.getMessageText( + Component.text(getName(players) + "max mana was set to " + amount + ".") + )); + } + + private static void reset(CommandContext context) { + Collection players = context.get(PLAYERS_KEY); + + ManaHandler handler = MagicSpells.getManaHandler(); + players.forEach(handler::createManaBar); + + context.sender().getSender().sendMessage(Util.getMessageText( + Component.text(getName(players) + "mana was reset.") + )); + } + + private static void updateRank(CommandContext context) { + Collection players = context.get(PLAYERS_KEY); + + ManaHandler handler = MagicSpells.getManaHandler(); + boolean updated = players.stream().anyMatch(handler::updateManaRankIfNecessary); + + if (!updated) { + context.sender().getSender().sendMessage(Util.getMessageText( + Component.text(players.size() == 1 ? + players.iterator().next().getName() + "'s mana rank was not updated." : + "No players' mana ranks were updated." + ) + )); + + return; + } + + context.sender().getSender().sendMessage(Util.getMessageText( + Component.text(getName(players) + "mana rank was updated.") + )); + } + + private static String getName(Collection players) { + return players.size() == 1 ? players.iterator().next().getName() + "'s " : "The selected players' "; + } + + private static class ManaRequirement implements Requirement { + + @Override + public boolean evaluateRequirement(@NotNull CommandContext commandContext) { + return MagicSpells.isManaSystemEnabled(); + } + + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/ProfileReportCommand.java b/core/src/main/java/com/nisovin/magicspells/commands/ProfileReportCommand.java new file mode 100644 index 000000000..85cfeb5c7 --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/ProfileReportCommand.java @@ -0,0 +1,32 @@ +package com.nisovin.magicspells.commands; + +import net.kyori.adventure.text.Component; + +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.description.Description; +import org.incendo.cloud.paper.PaperCommandManager; + +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import com.nisovin.magicspells.Perm; +import com.nisovin.magicspells.util.Util; +import com.nisovin.magicspells.MagicSpells; + +public class ProfileReportCommand { + + public static void register(PaperCommandManager manager) { + manager.command(manager.commandBuilder("ms", "magicspells") + .literal("profilereport") + .commandDescription(Description.of("Save profile report to a file.")) + .permission(Perm.COMMAND_PROFILE_REPORT) + .handler(ProfileReportCommand::profileReport) + ); + } + + private static void profileReport(CommandContext context) { + MagicSpells.profilingReport(); + + context.sender().getSender().sendMessage(Util.getMessageText(Component.text("Created profiling report."))); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/ReloadCommands.java b/core/src/main/java/com/nisovin/magicspells/commands/ReloadCommands.java new file mode 100644 index 000000000..fc238e0ed --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/ReloadCommands.java @@ -0,0 +1,118 @@ +package com.nisovin.magicspells.commands; + +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.HoverEvent; + +import org.bukkit.entity.Player; + +import org.incendo.cloud.Command; +import org.incendo.cloud.key.CloudKey; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.description.Description; +import org.incendo.cloud.paper.PaperCommandManager; +import org.incendo.cloud.bukkit.data.MultiplePlayerSelector; +import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser; + +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import com.nisovin.magicspells.Perm; +import com.nisovin.magicspells.Spellbook; +import com.nisovin.magicspells.MagicSpells; +import com.nisovin.magicspells.events.SpellbookReloadEvent; +import com.nisovin.magicspells.util.Util; + +public class ReloadCommands { + + private static final CloudKey TARGET_PLAYERS_KEY = CloudKey.of("players", MultiplePlayerSelector.class); + + static void register(@NotNull PaperCommandManager manager) { + Command.Builder base = manager.commandBuilder("ms", "magicspells"); + Command.Builder reload = base.literal("reload"); + + manager.command(reload + .permission(Perm.COMMAND_RELOAD) + .commandDescription(Description.of("Reload MagicSpells.")) + .handler(ReloadCommands::reload) + ); + + manager.command(reload + .required( + TARGET_PLAYERS_KEY, + MultiplePlayerSelectorParser.multiplePlayerSelectorParser(false), + Description.of("Selector for the player(s) to reload the spellbooks for.") + ) + .commandDescription(Description.of("Reload the spellbooks' of the selected players.")) + .permission(Perm.COMMAND_RELOAD_SPELLBOOK) + .handler(ReloadCommands::reloadSpellbook) + ); + + manager.command(base + .literal("reloadeffectlib") + .commandDescription(Description.of("Reload the shaded EffectLib instance inside MagicSpells.")) + .permission(Perm.COMMAND_RELOAD_EFFECTLIB) + .handler(ReloadCommands::reloadEffectLib) + ); + } + + private static void reload(CommandContext context) { + MagicSpells plugin = MagicSpells.getInstance(); + plugin.unload(); + plugin.load(); + + context.sender().getSender().sendMessage(Util.getMessageText(Component.text("MagicSpells plugin reloaded."))); + } + + private static void reloadSpellbook(CommandContext context) { + MultiplePlayerSelector selector = context.get(TARGET_PLAYERS_KEY); + Collection players = selector.values(); + + Map spellBooks = MagicSpells.getSpellbooks(); + players.forEach(player -> { + Spellbook spellbook = spellBooks.computeIfPresent(player.getUniqueId(), (uuid, oldSpellbook) -> { + oldSpellbook.destroy(); + return new Spellbook(player); + }); + + new SpellbookReloadEvent(player, spellbook).callEvent(); + }); + + if (players.size() == 1) { + context.sender().getSender().sendMessage(Util.getMessageText(Component.text( + "The spellbook for player '" + players.iterator().next().getName() + "' has been reloaded." + ))); + + return; + } + + context.sender().getSender().sendMessage(Util.getMessageText( + Component.text() + .append(Component.text("The spellbooks for the ")) + .append( + Component.text() + .content("[selected players]") + .hoverEvent(HoverEvent.showText( + Component.text( + players.stream() + .map(Player::getName) + .sorted() + .collect(Collectors.joining("\n")) + ) + )) + ) + .append(Component.text(" have been reloaded.")) + )); + } + + private static void reloadEffectLib(CommandContext context) { + MagicSpells.resetEffectlib(); + context.sender().getSender().sendMessage(Util.getMessageText(Component.text("EffectLib reloaded."))); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/ResetCooldownCommand.java b/core/src/main/java/com/nisovin/magicspells/commands/ResetCooldownCommand.java new file mode 100644 index 000000000..0197b07e8 --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/ResetCooldownCommand.java @@ -0,0 +1,140 @@ +package com.nisovin.magicspells.commands; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Collection; +import java.util.Collections; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; + +import org.incendo.cloud.Command; +import org.incendo.cloud.key.CloudKey; +import org.incendo.cloud.bukkit.data.Selector; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.description.Description; +import org.incendo.cloud.paper.PaperCommandManager; +import org.incendo.cloud.component.CommandComponent; +import org.incendo.cloud.bukkit.data.MultipleEntitySelector; +import org.incendo.cloud.bukkit.parser.selector.MultipleEntitySelectorParser; + +import org.bukkit.entity.Player; +import org.bukkit.entity.LivingEntity; + +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import com.nisovin.magicspells.Perm; +import com.nisovin.magicspells.Spell; +import com.nisovin.magicspells.util.Util; +import com.nisovin.magicspells.MagicSpells; +import com.nisovin.magicspells.commands.parsers.SpellParser; +import com.nisovin.magicspells.commands.exceptions.InvalidCommandArgumentException; + +public class ResetCooldownCommand { + + private static final CloudKey TARGET_ENTITIES_KEY = CloudKey.of("entities", MultipleEntitySelector.class); + private static final CloudKey SPELL_KEY = CloudKey.of("spell", Spell.class); + + static void register(@NotNull PaperCommandManager manager) { + Command.Builder base = manager.commandBuilder("ms", "magicspells") + .literal("resetcd") + .permission(Perm.COMMAND_RESET_COOLDOWN) + .commandDescription(Description.of("Reset the cooldowns for the selected entities.")) + .handler(ResetCooldownCommand::resetCooldown); + + Command.Builder filtered = base.meta(HelpCommand.FILTER_FROM_HELP, true); + + var selectorComponent = CommandComponent.builder() + .key(TARGET_ENTITIES_KEY) + .parser(MultipleEntitySelectorParser.multipleEntitySelectorParser(false)) + .description(Description.of("Selector for the entities to reset the cooldowns for. Use * for all entities, including unloaded entities and offline players.")) + .build(); + + var spellComponent = CommandComponent.builder() + .key(SPELL_KEY) + .parser(SpellParser.spellParser()) + .description(Description.of("Spell to reset the cooldown for. Use * for all spells.")) + .build(); + + manager.command(filtered + .literal("*") + .literal("*") + ); + + manager.command(filtered + .argument(selectorComponent) + .literal("*") + ); + + manager.command(filtered + .literal("*") + .argument(spellComponent) + ); + + manager.command(base + .argument(selectorComponent) + .argument(spellComponent) + ); + } + + private static void resetCooldown(CommandContext context) { + Collection entities = context.optional(TARGET_ENTITIES_KEY) + .map(Selector::values) + .map(entityCollection -> entityCollection.stream() + .filter(entity -> entity instanceof LivingEntity) + .map(entity -> (LivingEntity) entity) + .toList() + ) + .orElse(null); + + if (entities != null && entities.isEmpty()) + throw new InvalidCommandArgumentException("No targets were living entities"); + + List spells = context.optional(SPELL_KEY) + .map(Collections::singletonList) + .orElse(MagicSpells.getSpellsOrdered()); + + for (Spell spell : spells) { + if (entities == null) { + spell.getCooldowns().clear(); + continue; + } + + for (LivingEntity entity : entities) + spell.setCooldown(entity, 0, false); + } + + ComponentLike cooldown; + if (spells.size() == 1) { + cooldown = Component.text() + .append(Component.text("Reset cooldown on spell '")) + .append(Util.getMiniMessage(spells.getFirst().getName())) + .append(Component.text("'")); + } else { + cooldown = Component.text("Reset cooldowns on all spells"); + } + + ComponentLike target; + if (entities == null) { + target = Component.text(" for all entities."); + } else if (entities.size() == 1) { + LivingEntity entity = entities.iterator().next(); + + target = Component.text() + .append(Component.text(entity instanceof Player ? " for player '" : " for entity '")) + .append(entities.iterator().next().name().hoverEvent(entity)) + .append(Component.text("'.")); + } else { + target = Component.text(" for the selected entities."); + } + + context.sender().getSender().sendMessage(Util.getMessageText( + Component.text() + .append(cooldown) + .append(target) + )); + } + + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/TaskInfoCommand.java b/core/src/main/java/com/nisovin/magicspells/commands/TaskInfoCommand.java new file mode 100644 index 000000000..9719b5692 --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/TaskInfoCommand.java @@ -0,0 +1,48 @@ +package com.nisovin.magicspells.commands; + +import org.bukkit.Bukkit; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; + +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.description.Description; +import org.incendo.cloud.paper.PaperCommandManager; + +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import com.nisovin.magicspells.Perm; +import com.nisovin.magicspells.util.Util; +import com.nisovin.magicspells.MagicSpells; + +public class TaskInfoCommand { + + public static void register(PaperCommandManager manager) { + manager.command(manager.commandBuilder("ms", "magicspells") + .literal("taskinfo") + .commandDescription(Description.of("Display info for the tasks currently scheduled by MagicSpells.")) + .permission(Perm.COMMAND_TASKINFO) + .handler(TaskInfoCommand::taskInfo) + ); + } + + private static void taskInfo(CommandContext context) { + long msTasks = Bukkit.getScheduler().getPendingTasks().stream() + .filter(task -> MagicSpells.getInstance().equals(task.getOwner())) + .count(); + + int effectLibTasks = MagicSpells.getEffectManager().getEffects().size(); + + context.sender().getSender().sendMessage(Util.getMessageText( + Component.text() + .content("Tasks:") + .append(Component.text(" * All - ").append(number(msTasks))) + .append(Component.text(" * EffectLib - ")).append(number(effectLibTasks)) + )); + } + + private static Component number(long number) { + return Component.text(number + "\n", number > 0 ? NamedTextColor.GREEN : NamedTextColor.GRAY); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/UtilCommands.java b/core/src/main/java/com/nisovin/magicspells/commands/UtilCommands.java new file mode 100644 index 000000000..32c2333b7 --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/UtilCommands.java @@ -0,0 +1,93 @@ +package com.nisovin.magicspells.commands; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.Collection; +import java.util.stream.Collectors; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; + +import org.incendo.cloud.Command; +import org.incendo.cloud.key.CloudKey; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.description.Description; +import org.incendo.cloud.paper.PaperCommandManager; +import org.incendo.cloud.bukkit.data.SingleEntitySelector; +import org.incendo.cloud.bukkit.parser.selector.SingleEntitySelectorParser; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Mob; +import org.bukkit.entity.Entity; +import org.bukkit.command.CommandSender; + +import com.destroystokyo.paper.entity.ai.Goal; +import com.destroystokyo.paper.entity.ai.GoalKey; + +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import com.nisovin.magicspells.Perm; +import com.nisovin.magicspells.util.Util; +import com.nisovin.magicspells.commands.exceptions.InvalidCommandArgumentException; + +public class UtilCommands { + + private static final CloudKey TARGET_ENTITY_KEY = CloudKey.of("entity", SingleEntitySelector.class); + + static void register(@NotNull PaperCommandManager manager) { + Command.Builder base = manager + .commandBuilder("ms", "magicspells") + .literal("util"); + + manager.command(base + .literal("listgoals") + .required(TARGET_ENTITY_KEY, SingleEntitySelectorParser.singleEntitySelectorParser()) + .commandDescription(Description.of("List a mob's goals.")) + .permission(Perm.COMMAND_UTIL_LIST_GOALS) + .handler(UtilCommands::listGoals) + ); + } + + private static void listGoals(CommandContext context) { + SingleEntitySelector selector = context.get(TARGET_ENTITY_KEY); + + CommandSourceStack stack = context.sender(); + CommandSender sender = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + + Entity entity = selector.single(); + if (!(entity instanceof Mob mob)) + throw new InvalidCommandArgumentException("Invalid entity specified - target is not a mob"); + + Collection> goals = Bukkit.getMobGoals().getAllGoals(mob); + if (goals.isEmpty()) { + sender.sendMessage(Util.getMessageText(Component.text("Target has no goals"))); + return; + } + + TextComponent.Builder message = Component.text() + .append(Component.text("Goals for ")) + .append(entity.name().hoverEvent(entity)) + .append(Component.text(":")); + + for (Goal goal : goals) { + GoalKey key = goal.getKey(); + + message.appendNewline(); + message.append(Component.text(" - " + key.getEntityClass().getSimpleName() + ": ")); + message.append(Component.text(key.getNamespacedKey().toString())); + message.appendSpace(); + message.append( + Component.text( + goal.getTypes() + .stream() + .map(Enum::name) + .collect(Collectors.joining(", ", "[", "]")) + ) + ); + } + + sender.sendMessage(Util.getMessageText(message)); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/VariableCommands.java b/core/src/main/java/com/nisovin/magicspells/commands/VariableCommands.java new file mode 100644 index 000000000..d4a0a3253 --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/VariableCommands.java @@ -0,0 +1,158 @@ +package com.nisovin.magicspells.commands; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +import net.kyori.adventure.text.Component; + +import org.incendo.cloud.Command; +import org.incendo.cloud.key.CloudKey; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.description.Description; +import org.incendo.cloud.paper.PaperCommandManager; +import org.incendo.cloud.component.CommandComponent; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.suggestion.SuggestionProvider; +import org.incendo.cloud.bukkit.data.SinglePlayerSelector; +import org.incendo.cloud.bukkit.parser.selector.SinglePlayerSelectorParser; + +import org.bukkit.entity.Player; +import org.bukkit.command.CommandSender; + +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import com.nisovin.magicspells.Perm; +import com.nisovin.magicspells.util.Util; +import com.nisovin.magicspells.MagicSpells; +import com.nisovin.magicspells.util.SpellData; +import com.nisovin.magicspells.util.VariableMod; +import com.nisovin.magicspells.variables.Variable; +import com.nisovin.magicspells.util.managers.VariableManager; +import com.nisovin.magicspells.variables.variabletypes.GlobalVariable; +import com.nisovin.magicspells.variables.variabletypes.GlobalStringVariable; +import com.nisovin.magicspells.commands.exceptions.InvalidCommandArgumentException; + +public class VariableCommands { + + private static final CloudKey TARGET_PLAYER_KEY = CloudKey.of("player", SinglePlayerSelector.class); + private static final CloudKey VARIABLE_MOD_KEY = CloudKey.of("variable mod", String.class); + private static final CloudKey VARIABLE_KEY = CloudKey.of("variable", String.class); + + static void register(@NotNull PaperCommandManager manager) { + Command.Builder base = manager.commandBuilder("ms", "magicspells").literal("variable"); + + var variableComponent = CommandComponent.builder() + .key(VARIABLE_KEY) + .parser(StringParser.stringParser()) + .suggestionProvider(SuggestionProvider.suggestingStrings( + () -> MagicSpells.getVariableManager().getVariables().keySet().iterator() + )); + + manager.command(base + .literal("show") + .argument(variableComponent.description(Description.of("The variable to show."))) + .optional(TARGET_PLAYER_KEY, SinglePlayerSelectorParser.singlePlayerSelectorParser()) + .permission(Perm.COMMAND_VARIABLE_SHOW) + .handler(VariableCommands::show) + ); + + Command.Builder modify = base + .literal("modify") + .argument(variableComponent.description(Description.of("The variable to modify."))) + .permission(Perm.COMMAND_VARIABLE_MODIFY); + + manager.command(modify + .literal("*") + .required( + VARIABLE_MOD_KEY, + StringParser.greedyStringParser(), + Description.of("A variable modifier.") + ) + .meta(HelpCommand.FILTER_FROM_HELP, true) + .handler(VariableCommands::modify) + ); + + manager.command(modify + .literal("-") + .required(VARIABLE_MOD_KEY, StringParser.greedyStringParser()) + .meta(HelpCommand.FILTER_FROM_HELP, true) + .handler(VariableCommands::modify) + ); + + manager.command(modify + .required( + TARGET_PLAYER_KEY, + SinglePlayerSelectorParser.singlePlayerSelectorParser(), + Description.of("The player whose variable you wish to modify. Use * or - for global variables.") + ) + .required( + VARIABLE_MOD_KEY, + StringParser.greedyStringParser(), + Description.of("The variable modifier.") + ) + .handler(VariableCommands::modify) + ); + } + + private static void show(CommandContext context) { + String variableName = context.get(VARIABLE_KEY); + + Variable variable = MagicSpells.getVariableManager().getVariable(variableName); + if (variable == null) + throw new InvalidCommandArgumentException("No matching variable found: '" + variableName + "'"); + + SinglePlayerSelector selector = context.getOrDefault(TARGET_PLAYER_KEY, null); + Player player = selector == null ? null : selector.single(); + + if (player == null && !(variable instanceof GlobalVariable || variable instanceof GlobalStringVariable)) { + CommandSourceStack stack = context.sender(); + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + if (!(executor instanceof Player sender)) + throw new InvalidCommandArgumentException("Player must be specified for non-global variables"); + + player = sender; + } + + String name = player == null ? null : player.getName(); + String currentValue = variable.getStringValue(name); + + String message = (player == null ? "Variable" : name + "'s variable") + " value for " + variableName + " is: " + currentValue; + context.sender().getSender().sendMessage(Util.getMessageText(Component.text(message))); + } + + private static void modify(CommandContext context) { + VariableManager variableManager = MagicSpells.getVariableManager(); + String variableName = context.get(VARIABLE_KEY); + + Variable variable = variableManager.getVariable(variableName); + if (variable == null) + throw new InvalidCommandArgumentException("No matching variable found: '" + variableName + "'"); + + SinglePlayerSelector selector = context.getOrDefault(TARGET_PLAYER_KEY, null); + Player player = selector == null ? null : selector.single(); + + if (player == null && !(variable instanceof GlobalVariable || variable instanceof GlobalStringVariable)) + throw new InvalidCommandArgumentException("Player must be specified for non-global variables"); + + String name = player == null ? null : player.getName(); + + String variableModString = context.get(VARIABLE_MOD_KEY); + VariableMod variableMod; + try { + variableMod = new VariableMod(variableModString); + } catch (Exception e) { + throw new InvalidCommandArgumentException("Invalid variable mod '" + variableModString + "'", e); + } + + String oldValue = variable.getStringValue(name); + variableManager.processVariableMods(variable, variableMod, player, new SpellData(player)); + String newValue = variable.getStringValue(name); + + String message = (player == null ? "Variable" : name + "'s variable") + " value for " + variableName + + " was modified from '" + oldValue + "' to '" + newValue + "'."; + + context.sender().getSender().sendMessage(Util.getMessageText(Component.text(message))); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/exceptions/GenericCommandException.java b/core/src/main/java/com/nisovin/magicspells/commands/exceptions/GenericCommandException.java new file mode 100644 index 000000000..e7845331a --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/exceptions/GenericCommandException.java @@ -0,0 +1,13 @@ +package com.nisovin.magicspells.commands.exceptions; + +public class GenericCommandException extends RuntimeException { + + public GenericCommandException(String message) { + super(message); + } + + public GenericCommandException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/exceptions/InvalidCommandArgumentException.java b/core/src/main/java/com/nisovin/magicspells/commands/exceptions/InvalidCommandArgumentException.java new file mode 100644 index 000000000..0881b93a9 --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/exceptions/InvalidCommandArgumentException.java @@ -0,0 +1,13 @@ +package com.nisovin.magicspells.commands.exceptions; + +public class InvalidCommandArgumentException extends RuntimeException { + + public InvalidCommandArgumentException(String message) { + super(message); + } + + public InvalidCommandArgumentException(String message, Throwable cause) { + super(message, cause); + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/nisovin/magicspells/commands/parsers/AngleParser.java b/core/src/main/java/com/nisovin/magicspells/commands/parsers/AngleParser.java new file mode 100644 index 000000000..399380200 --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/parsers/AngleParser.java @@ -0,0 +1,85 @@ +package com.nisovin.magicspells.commands.parsers; + +import org.jetbrains.annotations.NotNull; + +import java.util.stream.Collectors; + +import org.incendo.cloud.type.range.Range; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.ArgumentParser; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.parser.ParserDescriptor; +import org.incendo.cloud.component.CommandComponent; +import org.incendo.cloud.parser.ArgumentParseResult; +import org.incendo.cloud.parser.standard.FloatParser; +import org.incendo.cloud.parser.standard.IntegerParser; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; + +import com.nisovin.magicspells.util.Angle; + +/** + * Parser that parsers a {@link Angle} from two floats. + * + * @param Command sender type + */ +public final class AngleParser implements ArgumentParser, BlockingSuggestionProvider.Strings { + + private static final Range SUGGESTION_RANGE = Range.intRange(Integer.MIN_VALUE, Integer.MAX_VALUE); + + /** + * Creates a new angle parser. + * + * @param command sender type + * @return the created parser + */ + public static @NotNull ParserDescriptor angleParser() { + return ParserDescriptor.of(new AngleParser<>(), Angle.class); + } + + /** + * Returns a {@link CommandComponent.Builder} using {@link #angleParser()} as the parser. + * + * @param the command sender type + * @return the component builder + */ + public static CommandComponent.@NotNull Builder angleComponent() { + return CommandComponent.builder().parser(angleParser()); + } + + @Override + public @NotNull ArgumentParseResult<@NotNull Angle> parse(final @NotNull CommandContext<@NotNull C> commandContext, final @NotNull CommandInput commandInput) { + final String input = commandInput.skipWhitespace().peekString(); + + final boolean relative; + if (commandInput.peek() == '~') { + relative = true; + commandInput.moveCursor(1); + } else { + relative = false; + } + + final float angle; + try { + final boolean empty = commandInput.peekString().isEmpty() || commandInput.peek() == ' '; + angle = empty ? 0 : commandInput.readFloat(); + } catch (final Exception e) { + return ArgumentParseResult.failure(new FloatParser.FloatParseException(input, new FloatParser<>(FloatParser.DEFAULT_MINIMUM, FloatParser.DEFAULT_MAXIMUM), commandContext)); + } + + return ArgumentParseResult.success(new Angle(angle, relative)); + } + + @Override + public @NotNull Iterable<@NotNull String> stringSuggestions(final @NotNull CommandContext commandContext, final @NotNull CommandInput input) { + String prefix; + if (input.hasRemainingInput() && input.peek() == '~') { + prefix = "~"; + input.moveCursor(1); + } else { + prefix = ""; + } + + return IntegerParser.getSuggestions(SUGGESTION_RANGE, input).stream().map(string -> prefix + string).collect(Collectors.toList()); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/parsers/OwnedSpellParser.java b/core/src/main/java/com/nisovin/magicspells/commands/parsers/OwnedSpellParser.java new file mode 100644 index 000000000..58adfaf1f --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/parsers/OwnedSpellParser.java @@ -0,0 +1,102 @@ +package com.nisovin.magicspells.commands.parsers; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; +import java.util.function.Predicate; + +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.parser.ParserDescriptor; +import org.incendo.cloud.parser.ArgumentParseResult; + +import org.bukkit.entity.Player; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.command.CommandSender; +import org.bukkit.inventory.EntityEquipment; + +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import com.nisovin.magicspells.Perm; +import com.nisovin.magicspells.Spell; +import com.nisovin.magicspells.Spellbook; +import com.nisovin.magicspells.util.Util; +import com.nisovin.magicspells.MagicSpells; +import com.nisovin.magicspells.commands.exceptions.GenericCommandException; + +public class OwnedSpellParser extends SpellParser { + + public static ParserDescriptor ownedSpellParser() { + return ParserDescriptor.of(new OwnedSpellParser(), Spell.class); + } + + @Override + public @NotNull ArgumentParseResult<@NotNull Spell> parse(@NotNull CommandContext<@NotNull CommandSourceStack> context, @NotNull CommandInput input) { + ArgumentParseResult result = super.parse(context, input); + CommandSourceStack stack = context.sender(); + CommandSender sender = stack.getExecutor(); + + Optional parsedValue = result.parsedValue(); + if (parsedValue.isPresent()) { + Spell spell = parsedValue.get(); + + if (sender instanceof Player player) { + Spellbook spellbook = MagicSpells.getSpellbook(player); + if (spell.isHelperSpell() && !Perm.COMMAND_CAST_SELF_HELPER.has(player) || !spellbook.hasSpell(spell)) + return ArgumentParseResult.failure(new GenericCommandException(MagicSpells.getUnknownSpellMessage())); + } + + if (!spell.canCastByCommand()) + return ArgumentParseResult.failure(new GenericCommandException("You cannot cast this spell using commands.")); + + if (spell.isRequiringCastItemOnCommand()) { + if (!(sender instanceof LivingEntity entity)) + return ArgumentParseResult.failure(new GenericCommandException(spell.getStrWrongCastItem())); + + EntityEquipment equipment = entity.getEquipment(); + if (equipment == null) + return ArgumentParseResult.failure(new GenericCommandException(spell.getStrWrongCastItem())); + + ItemStack item = equipment.getItemInMainHand(); + if (!spell.isValidItemForCastCommand(item)) + return ArgumentParseResult.failure(new GenericCommandException(spell.getStrWrongCastItem())); + } + } + + return result; + } + + @Override + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); + if (!(stack.getExecutor() instanceof Player player)) return super.stringSuggestions(context, input); + + return OwnedSpellParser.suggest(player); + } + + public static @NotNull List<@NotNull String> suggest(@NotNull Player player) { + return suggest(player, null); + } + + public static @NotNull List<@NotNull String> suggest(@NotNull Player player, @Nullable Predicate predicate) { + Spellbook spellbook = MagicSpells.getSpellbook(player); + boolean canCastHelperSpells = Perm.COMMAND_CAST_SELF_HELPER.has(player); + + Stream stream = MagicSpells.spells().stream() + .filter(Spell::canCastByCommand) + .filter(spell -> canCastHelperSpells || !spell.isHelperSpell()) + .filter(spellbook::hasSpell); + + if (predicate != null) stream = stream.filter(predicate); + + return stream + .map(spell -> Util.getPlainString(Util.getMiniMessage(spell.getName()))) + .map(SpellParser::escapeIfRequired) + .toList(); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/parsers/RotationParser.java b/core/src/main/java/com/nisovin/magicspells/commands/parsers/RotationParser.java new file mode 100644 index 000000000..fb30b29e0 --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/parsers/RotationParser.java @@ -0,0 +1,102 @@ +package com.nisovin.magicspells.commands.parsers; + +import org.jetbrains.annotations.NotNull; + +import java.util.stream.Collectors; + +import org.incendo.cloud.type.range.Range; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.ArgumentParser; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.parser.ParserDescriptor; +import org.incendo.cloud.component.CommandComponent; +import org.incendo.cloud.parser.ArgumentParseResult; +import org.incendo.cloud.parser.standard.IntegerParser; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; + +import com.nisovin.magicspells.util.Angle; +import com.nisovin.magicspells.util.Rotation; +import com.nisovin.magicspells.commands.exceptions.InvalidCommandArgumentException; + +/** + * Parser that parses a {@link Rotation} from two floats. + * + * @param Command sender type + */ +public final class RotationParser implements ArgumentParser, BlockingSuggestionProvider.Strings { + + private static final Range SUGGESTION_RANGE = Range.intRange(Integer.MIN_VALUE, Integer.MAX_VALUE); + + private final AngleParser angleParser = new AngleParser<>(); + + /** + * Creates a new rotation parser. + * + * @param command sender type + * @return the created parser + */ + public static @NotNull ParserDescriptor rotationParser() { + return ParserDescriptor.of(new RotationParser<>(), Rotation.class); + } + + /** + * Returns a {@link CommandComponent.Builder} using {@link #rotationParser()} as the parser. + * + * @param the command sender type + * @return the component builder + */ + public static CommandComponent.@NotNull Builder rotationComponent() { + return CommandComponent.builder().parser(rotationParser()); + } + + @Override + public @NotNull ArgumentParseResult<@NotNull Rotation> parse(final @NotNull CommandContext commandContext, final @NotNull CommandInput commandInput) { + if (commandInput.remainingTokens() < 2) { + return ArgumentParseResult.failure(new InvalidCommandArgumentException( + "Invalid rotation specified. Required format is ' '" + )); + } + + Angle[] angles = new Angle[2]; + for (int i = 0; i < 2; i++) { + if (commandInput.peekString().isEmpty()) { + return ArgumentParseResult.failure(new InvalidCommandArgumentException( + "Invalid rotation specified. Required format is ' '" + )); + } + + ArgumentParseResult angle = this.angleParser.parse(commandContext, commandInput); + if (angle.failure().isPresent()) { + return ArgumentParseResult.failure(angle.failure().get()); + } + + angles[i] = angle.parsedValue().orElseThrow(NullPointerException::new); + } + + return ArgumentParseResult.success(Rotation.of(angles[0], angles[1])); + } + + @Override + public @NotNull Iterable<@NotNull String> stringSuggestions(final @NotNull CommandContext commandContext, final @NotNull CommandInput input) { + final CommandInput inputCopy = input.copy(); + + int idx = input.cursor(); + for (int i = 0; i < 2; i++) { + input.skipWhitespace(); + idx = input.cursor(); + + if (!input.hasRemainingInput()) break; + + ArgumentParseResult angle = this.angleParser.parse(commandContext, input); + + if (angle.failure().isPresent() || !input.hasRemainingInput()) break; + } + input.cursor(idx); + + if (input.hasRemainingInput() && input.peek() == '~') input.moveCursor(1); + + String prefix = inputCopy.difference(input, true); + return IntegerParser.getSuggestions(SUGGESTION_RANGE, input).stream().map(string -> prefix + string).collect(Collectors.toList()); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/parsers/SpellCastArgumentsParser.java b/core/src/main/java/com/nisovin/magicspells/commands/parsers/SpellCastArgumentsParser.java new file mode 100644 index 000000000..fb026b167 --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/parsers/SpellCastArgumentsParser.java @@ -0,0 +1,130 @@ +package com.nisovin.magicspells.commands.parsers; + +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import java.util.concurrent.CompletableFuture; + +import io.leangen.geantyref.TypeToken; + +import com.google.common.collect.Iterables; + +import org.incendo.cloud.key.CloudKey; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.ArgumentParser; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.parser.ParserDescriptor; +import org.incendo.cloud.parser.ArgumentParseResult; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.suggestion.SuggestionProvider; +import org.incendo.cloud.execution.preprocessor.CommandPreprocessingContext; + +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import com.nisovin.magicspells.Perm; +import com.nisovin.magicspells.MagicSpells; +import com.nisovin.magicspells.commands.CastCommands; +import com.nisovin.magicspells.commands.exceptions.InvalidCommandArgumentException; + +public class SpellCastArgumentsParser implements ArgumentParser.FutureArgumentParser, SuggestionProvider { + + private static final CloudKey> PARSE_SUGGESTIONS = CloudKey.of("__parse_suggestions__", new TypeToken<>() {}); + private static final List POWER_SUGGESTIONS = List.of(Suggestion.suggestion("-p"), Suggestion.suggestion("--power")); + + public static ParserDescriptor spellCastArgumentsParser() { + return ParserDescriptor.of(new SpellCastArgumentsParser(), String[].class); + } + + private final StringParser stringParser = new StringParser<>(StringParser.StringMode.QUOTED); + + @SuppressWarnings("unchecked") + @Override + public @NotNull CompletableFuture<@NotNull ArgumentParseResult> parseFuture(@NotNull CommandContext context, @NotNull CommandInput input) { + List spellArguments = new ArrayList<>(); + + int preParseCursor = input.cursor(); + while (input.hasRemainingInput()) { + String value = input.peekString(); + if (value.isEmpty() || value.equals("-p") || (value.startsWith("--") && "--power".startsWith(value))) + break; + + ArgumentParseResult result = stringParser.parse(context, input); + if (result.parsedValue().isPresent()) { + spellArguments.add(result.parsedValue().get()); + input.skipWhitespace(); + continue; + } + + Optional failure = result.failure(); + if (failure.isPresent() && failure.get() instanceof StringParser.StringParseException stringParseException) { + return ArgumentParseResult.failureFuture( + new InvalidCommandArgumentException("Invalid spell cast argument '" + stringParseException.input() + "'") + ); + } + + return ArgumentParseResult.failureFuture(new InvalidCommandArgumentException("Invalid spell cast arguments")); + } + + // Fail parsing if there would be suggestions. + if (input.input().endsWith(" ")) { + int parsedCursor = input.cursor(); + + SuggestionProvider provider = context.get(CastCommands.SPELL_KEY).suggestionProvider(); + if (provider != SuggestionProvider.noSuggestions()) { + return provider.suggestionsFuture(context, input.cursor(preParseCursor)) + .thenApply(result -> { + if (Iterables.isEmpty(result)) { + input.cursor(parsedCursor); + + context.store(PARSE_SUGGESTIONS, context.hasPermission(Perm.COMMAND_CAST_POWER) ? + POWER_SUGGESTIONS : Collections.emptyList() + ); + + return ArgumentParseResult.success(spellArguments.toArray(new String[0])); + } + + List suggestions = MagicSpells + .getCommandManager() + .suggestionProcessor() + .process( + CommandPreprocessingContext.of(context, input.cursor(preParseCursor)), + (Stream) StreamSupport.stream(result.spliterator(), false) + ) + .toList(); + + context.store(PARSE_SUGGESTIONS, context.hasPermission(Perm.COMMAND_CAST_POWER) ? + Iterables.concat(suggestions, POWER_SUGGESTIONS) : suggestions + ); + + if (suggestions.isEmpty()) { + input.cursor(parsedCursor); + return ArgumentParseResult.success(spellArguments.toArray(new String[0])); + } + + return ArgumentParseResult.failure( + new InvalidCommandArgumentException("Spell cast arguments should not contain trailing whitespace") + ); + }); + } + } + + return ArgumentParseResult.successFuture(spellArguments.toArray(new String[0])); + } + + @Override + public @NotNull CompletableFuture<@NotNull ? extends Iterable<@NotNull ? extends Suggestion>> suggestionsFuture(@NotNull CommandContext context, @NotNull CommandInput input) { + if (context.contains(PARSE_SUGGESTIONS)) + return CompletableFuture.completedFuture(context.get(PARSE_SUGGESTIONS)); + + return context.get(CastCommands.SPELL_KEY) + .suggestionProvider() + .suggestionsFuture(context, input) + .thenApply(result -> context.hasPermission(Perm.COMMAND_CAST_POWER) ? + Iterables.concat(result, POWER_SUGGESTIONS) : result + ); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/parsers/SpellParser.java b/core/src/main/java/com/nisovin/magicspells/commands/parsers/SpellParser.java new file mode 100644 index 000000000..5c486a27f --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/parsers/SpellParser.java @@ -0,0 +1,76 @@ +package com.nisovin.magicspells.commands.parsers; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.function.Predicate; + +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.ArgumentParser; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.parser.ParserDescriptor; +import org.incendo.cloud.parser.ArgumentParseResult; +import org.incendo.cloud.parser.standard.StringParser; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; + +import com.nisovin.magicspells.Spell; +import com.nisovin.magicspells.util.Util; +import com.nisovin.magicspells.MagicSpells; +import com.nisovin.magicspells.commands.exceptions.GenericCommandException; + +public class SpellParser implements ArgumentParser, BlockingSuggestionProvider.Strings { + + public static ParserDescriptor spellParser() { + return ParserDescriptor.of(new SpellParser<>(), Spell.class); + } + + private final StringParser stringParser = new StringParser<>(StringParser.StringMode.QUOTED); + + @Override + public @NotNull ArgumentParseResult<@NotNull Spell> parse(@NotNull CommandContext<@NotNull C> context, @NotNull CommandInput input) { + ArgumentParseResult result = stringParser.parse(context, input); + + return result.parsedValue() + .map(MagicSpells::getSpellByName) + .map(ArgumentParseResult::success) + .orElseGet(() -> ArgumentParseResult.failure(new GenericCommandException(MagicSpells.getUnknownSpellMessage()))); + } + + @Override + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext commandContext, @NotNull CommandInput input) { + return suggest(); + } + + public static @NotNull List<@NotNull String> suggest() { + return MagicSpells.spells().stream() + .filter(Spell::canCastByCommand) + .map(spell -> Util.getPlainString(Util.getMiniMessage(spell.getName()))) + .map(SpellParser::escapeIfRequired) + .toList(); + } + + public static @NotNull List<@NotNull String> suggest(@NotNull Predicate predicate) { + return MagicSpells.spells().stream() + .filter(Spell::canCastByCommand) + .filter(predicate) + .map(spell -> Util.getPlainString(Util.getMiniMessage(spell.getName()))) + .map(SpellParser::escapeIfRequired) + .toList(); + } + + public static String escapeIfRequired(@NotNull String string) { + for (char c : string.toCharArray()) + if (shouldQuote(c)) + return '"' + string.replace("\"", "\\\"") + '"'; + + return string; + } + + private static boolean shouldQuote(char c) { + return switch (c) { + case ' ', '"', '\'', '[', ']', '{', '}' -> true; + default -> false; + }; + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/parsers/VarargsParser.java b/core/src/main/java/com/nisovin/magicspells/commands/parsers/VarargsParser.java new file mode 100644 index 000000000..4a24f4c94 --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/commands/parsers/VarargsParser.java @@ -0,0 +1,88 @@ +package com.nisovin.magicspells.commands.parsers; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +import io.leangen.geantyref.TypeToken; + +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.parser.ArgumentParser; +import org.incendo.cloud.suggestion.Suggestion; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.parser.ParserDescriptor; +import org.incendo.cloud.parser.ArgumentParseResult; +import org.incendo.cloud.suggestion.SuggestionProvider; + +public class VarargsParser implements ArgumentParser>, SuggestionProvider { + + public static ParserDescriptor> varargsParser(@NotNull ArgumentParser parser) { + return ParserDescriptor.of(new VarargsParser<>(parser, null), new TypeToken<>() {}); + } + + public static ParserDescriptor> varargsParser(@NotNull ArgumentParser parser, @Nullable SuggestionProvider suggestionProvider) { + return ParserDescriptor.of(new VarargsParser<>(parser, suggestionProvider), new TypeToken<>() {}); + } + + private final ArgumentParser parser; + private final SuggestionProvider suggestionProvider; + + public VarargsParser(@NotNull ArgumentParser parser, @Nullable SuggestionProvider suggestionProvider) { + this.parser = parser; + this.suggestionProvider = suggestionProvider; + } + + @Override + public @NotNull ArgumentParseResult<@NotNull Collection> parse(@NotNull CommandContext<@NotNull C> context, @NotNull CommandInput input) { + List arguments = new ArrayList<>(); + + while (input.hasRemainingInput()) { + ArgumentParseResult result = parser.parse(context, input); + if (result.parsedValue().isPresent()) { + arguments.add(result.parsedValue().get()); + input.skipWhitespace(); + + continue; + } + + return ArgumentParseResult.failure(result.failure().get()); + } + + return ArgumentParseResult.success(arguments); + } + + @Override + public @NotNull CompletableFuture> suggestionsFuture(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandInput inputCopy = input.copy(); + + int idx = input.cursor(); + while (input.hasRemainingInput()) { + input.skipWhitespace(); + idx = input.cursor(); + + if (!input.hasRemainingInput()) break; + + ArgumentParseResult result = parser.parse(context, input); + if (result.failure().isPresent() || !input.hasRemainingInput()) break; + } + input.cursor(idx); + + String prefix = inputCopy.difference(input, true); + + return (suggestionProvider != null ? suggestionProvider : parser.suggestionProvider()) + .suggestionsFuture(context, input) + .thenApply(suggestions -> { + List prefixed = new ArrayList<>(); + + for (Suggestion suggestion : suggestions) + prefixed.add(suggestion.withSuggestion(prefix + suggestion.suggestion())); + + return prefixed; + }); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/mana/ManaBar.java b/core/src/main/java/com/nisovin/magicspells/mana/ManaBar.java index 86f727b0b..0ab3ea048 100644 --- a/core/src/main/java/com/nisovin/magicspells/mana/ManaBar.java +++ b/core/src/main/java/com/nisovin/magicspells/mana/ManaBar.java @@ -1,94 +1,82 @@ package com.nisovin.magicspells.mana; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.Style; + import org.bukkit.Bukkit; -import org.bukkit.ChatColor; import org.bukkit.entity.Player; -import com.nisovin.magicspells.util.Util; import com.nisovin.magicspells.util.compat.EventUtil; import com.nisovin.magicspells.events.ManaChangeEvent; public class ManaBar { - private String prefix; - private String playerName; + private final String playerName; private ManaRank rank; - private ChatColor colorFull; - private ChatColor colorEmpty; - private int mana; private int maxMana; private int regenAmount; - + private String barFormat; + public ManaBar(Player player, ManaRank rank) { playerName = player.getName().toLowerCase(); - setRank(rank); + setRank(rank); } - + public void setRank(ManaRank rank) { this.rank = rank; mana = rank.getStartingMana(); maxMana = rank.getMaxMana(); regenAmount = rank.getRegenAmount(); - setDisplayData(rank.getPrefix(), rank.getColorFull(), rank.getColorEmpty()); + barFormat = rank.getBarFormat(); } - + public Player getPlayer() { return Bukkit.getPlayerExact(playerName); } - + public ManaRank getManaRank() { return rank; } - + public int getMana() { return mana; } - + public int getMaxMana() { return maxMana; } - + public int getRegenAmount() { return regenAmount; } - + public void setMaxMana(int max) { maxMana = max; if (mana > maxMana) mana = maxMana; } - + public void setRegenAmount(int amount) { regenAmount = amount; } - - private void setDisplayData(String prefix, ChatColor colorFull, ChatColor colorEmpty) { - this.prefix = Util.colorize(prefix); - this.colorFull = colorFull; - this.colorEmpty = colorEmpty; - } - - public String getPrefix() { - return prefix; - } - public ChatColor getColorFull() { - return colorFull; + public String getBarFormat() { + return barFormat; } - public ChatColor getColorEmpty() { - return colorEmpty; + public void setBarFormat(String barFormat) { + this.barFormat = barFormat; } - + public boolean has(int amount) { return mana >= amount; } - + public boolean changeMana(int amount, ManaChangeReason reason) { int newAmt = mana; - + if (amount > 0) { if (mana == maxMana) return false; newAmt += amount; @@ -100,7 +88,7 @@ public boolean changeMana(int amount, ManaChangeReason reason) { } if (newAmt == mana) return false; - + newAmt = callManaChangeEvent(newAmt, reason); if (newAmt > maxMana) newAmt = maxMana; if (newAmt < 0) newAmt = 0; @@ -108,23 +96,23 @@ public boolean changeMana(int amount, ManaChangeReason reason) { mana = newAmt; return true; } - + public boolean setMana(int amount, ManaChangeReason reason) { int newAmt = amount; if (newAmt > maxMana) newAmt = maxMana; else if (newAmt < 0) newAmt = 0; - + newAmt = callManaChangeEvent(newAmt, reason); if (newAmt == mana) return false; mana = newAmt; return true; } - + public boolean regenerate() { if ((regenAmount > 0 && mana == maxMana) || (regenAmount < 0 && mana == 0)) return false; return changeMana(regenAmount, ManaChangeReason.REGEN); } - + private int callManaChangeEvent(int newAmt, ManaChangeReason reason) { Player player = getPlayer(); if (player == null || !player.isOnline()) return newAmt; @@ -132,5 +120,5 @@ private int callManaChangeEvent(int newAmt, ManaChangeReason reason) { EventUtil.call(event); return event.getNewAmount(); } - + } diff --git a/core/src/main/java/com/nisovin/magicspells/mana/ManaRank.java b/core/src/main/java/com/nisovin/magicspells/mana/ManaRank.java index e4cfd0da6..41578ffa1 100644 --- a/core/src/main/java/com/nisovin/magicspells/mana/ManaRank.java +++ b/core/src/main/java/com/nisovin/magicspells/mana/ManaRank.java @@ -1,38 +1,35 @@ package com.nisovin.magicspells.mana; -import org.bukkit.ChatColor; +import net.kyori.adventure.text.format.Style; public class ManaRank { private String name; - private String prefix; + private String barFormat; private char symbol; - private int barSize; + private int maxMana; private int startingMana; private int regenAmount; private int regenInterval; - private ChatColor colorFull; - private ChatColor colorEmpty; - ManaRank() { } - ManaRank(String name, String prefix, char symbol, int barSize, int maxMana, int startingMana, int regenAmount, int regenInterval, ChatColor colorFull, ChatColor colorEmpty) { + ManaRank(String name, char symbol, int barSize, String manaBarFormat, int maxMana, int startingMana, int regenAmount, int regenInterval) { this.name = name; - this.prefix = prefix; + this.symbol = symbol; this.barSize = barSize; + this.barFormat = manaBarFormat; + this.maxMana = maxMana; - this.startingMana = startingMana; this.regenAmount = regenAmount; + this.startingMana = startingMana; this.regenInterval = regenInterval; - this.colorFull = colorFull; - this.colorEmpty = colorEmpty; } public String getName() { @@ -43,12 +40,12 @@ public void setName(String name) { this.name = name; } - public String getPrefix() { - return prefix; + public String getBarFormat() { + return barFormat; } - public void setPrefix(String prefix) { - this.prefix = prefix; + public void setBarFormat(String barFormat) { + this.barFormat = barFormat; } public char getSymbol() { @@ -98,36 +95,18 @@ public int getRegenInterval() { public void setRegenInterval(int regenInterval) { this.regenInterval = regenInterval; } - - public ChatColor getColorFull() { - return colorFull; - } - - public void setColorFull(ChatColor colorFull) { - this.colorFull = colorFull; - } - - public ChatColor getColorEmpty() { - return colorEmpty; - } - - public void setColorEmpty(ChatColor colorEmpty) { - this.colorEmpty = colorEmpty; - } @Override public String toString() { return "ManaRank:[" + "name=" + name - + ",prefix=" + prefix + ",symbol=" + symbol - + ",barSize" + barSize + + ",barSize=" + barSize + + ",barFormat=" + barFormat + ",maxMana=" + maxMana + ",startingMana=" + startingMana + ",regenAmount=" + regenAmount + ",regenInterval=" + regenInterval - + ",colorFull=" + colorFull - + ",colorEmpty=" + colorEmpty + ']'; } diff --git a/core/src/main/java/com/nisovin/magicspells/mana/ManaSystem.java b/core/src/main/java/com/nisovin/magicspells/mana/ManaSystem.java index 38240038b..51acf5eba 100644 --- a/core/src/main/java/com/nisovin/magicspells/mana/ManaSystem.java +++ b/core/src/main/java/com/nisovin/magicspells/mana/ManaSystem.java @@ -2,7 +2,12 @@ import java.util.*; -import org.bukkit.ChatColor; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; + import org.bukkit.entity.Player; import com.nisovin.magicspells.util.Util; @@ -13,44 +18,45 @@ public class ManaSystem extends ManaHandler { - private String defaultBarPrefix; - private char defaultSymbol; - private int defaultBarSize; - private ChatColor defaultBarColorFull; - private ChatColor defaultBarColorEmpty; - - private int defaultMaxMana; - private int defaultStartingMana; - private int defaultRegenAmount; - private int defaultRegenInterval; - - private boolean showManaOnUse; - private boolean showManaOnRegen; - private boolean showManaOnHungerBar; - private boolean showManaOnActionBar; - private boolean showManaOnExperienceBar; + private final boolean showManaOnUse; + private final boolean showManaOnRegen; + private final boolean showManaOnHungerBar; + private final boolean showManaOnActionBar; + private final boolean showManaOnExperienceBar; private List modifierList; private ModifierSet modifiers; - - private ManaRank defaultRank; - private List ranks; - - private Map manaBars; - private Set regenerators; + + private final ManaRank defaultRank; + private final List ranks; + + private final Map manaBars; + private final Set regenerators; public ManaSystem(MagicConfig config) { String path = "mana."; - defaultBarPrefix = config.getString(path + "default-prefix", "Mana:"); - defaultSymbol = config.getString(path + "default-symbol", "=").charAt(0); - defaultBarSize = config.getInt(path + "default-size", 35); - defaultBarColorFull = ChatColor.getByChar(config.getString(path + "default-color-full", ChatColor.GREEN.getChar() + "")); - defaultBarColorEmpty = ChatColor.getByChar(config.getString(path + "default-color-empty", ChatColor.BLACK.getChar() + "")); - defaultMaxMana = config.getInt(path + "default-max-mana", 100); - defaultStartingMana = config.getInt(path + "default-starting-mana", defaultMaxMana); - defaultRegenAmount = config.getInt(path + "default-regen-amount", 5); - defaultRegenInterval = config.getInt(path + "default-regen-interval", TimeUtil.TICKS_PER_SECOND); + String legacyDefaultBarPrefix = Util.getStrictString(config.getString(path + "default-prefix", "Mana:")); + NamedTextColor legacyDefaultColorFull = Util.getLegacyColor(config.getString(path + "default-color-full", null), NamedTextColor.GREEN); + NamedTextColor legacyDefaultColorEmpty = Util.getLegacyColor(config.getString(path + "default-color-empty", null), NamedTextColor.BLACK); + + String defaultBarFormat = config.getString(path + "default-bar-format", null); + boolean legacyFormat = defaultBarFormat == null; + if (legacyFormat) { + defaultBarFormat = "<" + MagicSpells.getLegacyTextColor() + ">" + + legacyDefaultBarPrefix + + " {<" + legacyDefaultColorFull + ">" + + "<" + legacyDefaultColorEmpty + ">} [/]" + + ""; + } + + char defaultSymbol = config.getString(path + "default-symbol", "=").charAt(0); + int defaultBarSize = config.getInt(path + "default-size", 35); + + int defaultMaxMana = config.getInt(path + "default-max-mana", 100); + int defaultStartingMana = config.getInt(path + "default-starting-mana", defaultMaxMana); + int defaultRegenAmount = config.getInt(path + "default-regen-amount", 5); + int defaultRegenInterval = config.getInt(path + "default-regen-interval", TimeUtil.TICKS_PER_SECOND); showManaOnUse = config.getBoolean(path + "show-mana-on-use", false); showManaOnRegen = config.getBoolean(path + "show-mana-on-regen", false); @@ -59,8 +65,7 @@ public ManaSystem(MagicConfig config) { showManaOnExperienceBar = config.getBoolean(path + "show-mana-on-experience-bar", true); modifierList = config.getStringList(path + "modifiers", null); - - defaultRank = new ManaRank("default", defaultBarPrefix, defaultSymbol, defaultBarSize, defaultMaxMana, defaultStartingMana, defaultRegenAmount, defaultRegenInterval, defaultBarColorFull, defaultBarColorEmpty); + defaultRank = new ManaRank("default", defaultSymbol, defaultBarSize, defaultBarFormat, defaultMaxMana, defaultStartingMana, defaultRegenAmount, defaultRegenInterval); regenerators = new HashSet<>(); ranks = new ArrayList<>(); @@ -71,27 +76,41 @@ public ManaSystem(MagicConfig config) { for (String key : rankKeys) { String keyPath = "mana.ranks." + key + "."; + String barFormat = config.getString(keyPath + "bar-format", legacyFormat ? null : defaultBarFormat); + if (barFormat == null) { + String legacyBarPrefix = Util.getStrictString(config.getString(keyPath + "prefix", null)); + if (legacyBarPrefix == null) legacyBarPrefix = legacyDefaultBarPrefix; + + NamedTextColor legacyColorFull = Util.getLegacyColor(config.getString(keyPath + "color-full", null), legacyDefaultColorFull); + NamedTextColor legacyColorEmpty = Util.getLegacyColor(config.getString(keyPath + "color-empty", null), legacyDefaultColorEmpty); + + barFormat = "<" + MagicSpells.getLegacyTextColor() + ">" + + legacyBarPrefix + + " {<" + legacyColorFull + ">" + + "<" + legacyColorEmpty + ">} [/]" + + ""; + } + ManaRank r = new ManaRank(); r.setName(key); - r.setPrefix(config.getString(keyPath + "prefix", defaultBarPrefix)); + r.setSymbol(config.getString(keyPath + "symbol", defaultSymbol + "").charAt(0)); r.setBarSize(config.getInt(keyPath + "size", defaultBarSize)); + r.setBarFormat(barFormat); + r.setMaxMana(config.getInt(keyPath + "max-mana", defaultMaxMana)); - r.setStartingMana(config.getInt(keyPath + "starting-mana", defaultStartingMana)); r.setRegenAmount(config.getInt(keyPath + "regen-amount", defaultRegenAmount)); + r.setStartingMana(config.getInt(keyPath + "starting-mana", defaultStartingMana)); r.setRegenInterval(config.getInt(keyPath + "regen-interval", defaultRegenAmount)); - r.setColorFull(ChatColor.getByChar(config.getString(keyPath + "color-full", defaultBarColorFull.getChar() + ""))); - r.setColorEmpty(ChatColor.getByChar(config.getString(keyPath + "color-empty", defaultBarColorEmpty.getChar() + ""))); regenerators.add(new Regenerator(r, r.getRegenInterval())); - ranks.add(r); } } regenerators.add(new Regenerator(defaultRank, defaultRegenInterval)); } - + // DEBUG INFO: level 2, adding mana modifiers @Override public void initialize() { @@ -101,7 +120,7 @@ public void initialize() { modifierList = null; } } - + // DEBUG INFO: level 1, creating mana bar for player playerName with rank rankName private ManaBar getManaBar(Player player) { ManaBar bar = manaBars.get(player.getUniqueId()); @@ -114,7 +133,7 @@ private ManaBar getManaBar(Player player) { } return bar; } - + // DEBUG INFO: level 1, updating mana bar for player playerName with rank rankName @Override public void createManaBar(final Player player) { @@ -129,7 +148,7 @@ public void createManaBar(final Player player) { } MagicSpells.scheduleDelayedTask(() -> showMana(player), 11); } - + @Override public boolean updateManaRankIfNecessary(Player player) { if (manaBars.containsKey(player.getUniqueId())) { @@ -143,7 +162,7 @@ public boolean updateManaRankIfNecessary(Player player) { return false; } - + // DEBUG INFO: level 3, fetching mana rank for playerName // DEBUG INFO: level 3, checking rank rankName // DEBUG INFO: level 3, rank found @@ -166,13 +185,13 @@ public int getMaxMana(Player player) { ManaBar bar = getManaBar(player); return bar.getMaxMana(); } - + @Override public void setMaxMana(Player player, int amount) { ManaBar bar = getManaBar(player); bar.setMaxMana(amount); } - + @Override public int getRegenAmount(Player player) { ManaBar bar = getManaBar(player); @@ -190,7 +209,7 @@ public int getMana(Player player) { ManaBar bar = getManaBar(player); return bar.getMana(); } - + @Override public boolean hasMana(Player player, int amount) { ManaBar bar = getManaBar(player); @@ -209,7 +228,7 @@ public boolean addMana(Player player, int amount, ManaChangeReason reason) { public boolean removeMana(Player player, int amount, ManaChangeReason reason) { return addMana(player, -amount, reason); } - + @Override public boolean setMana(Player player, int amount, ManaChangeReason reason) { ManaBar bar = getManaBar(player); @@ -226,32 +245,36 @@ public void showMana(Player player, boolean showInChat) { if (showManaOnActionBar) showManaOnActionBar(player, bar); if (showManaOnExperienceBar) showManaOnExperienceBar(player, bar); } - + @Override public ModifierSet getModifiers() { return modifiers; } - private String getManaMessage(ManaBar bar) { - int segments = (int) (((double) bar.getMana() / (double) bar.getMaxMana()) * bar.getManaRank().getBarSize()); - StringBuilder text = new StringBuilder(MagicSpells.getTextColor() + bar.getPrefix() + MagicSpells.getTextColor() + " {" + bar.getColorFull()); - int i = 0; - for (; i < segments; i++) { - text.append(bar.getManaRank().getSymbol()); - } - text.append(bar.getColorEmpty()); - for (; i < bar.getManaRank().getBarSize(); i++) { - text.append(bar.getManaRank().getSymbol()); - } - text.append(MagicSpells.getTextColor()).append("} [").append(bar.getMana()).append('/').append(bar.getMaxMana()).append(']'); + private Component getManaMessage(ManaBar bar) { + ManaRank rank = bar.getManaRank(); + + int mana = bar.getMana(); + int maxMana = bar.getMaxMana(); + int barSize = rank.getBarSize(); + String symbol = String.valueOf(rank.getSymbol()); + + double progress = (double) mana / maxMana; + int segments = (int) (progress * barSize); - return text.toString(); + return MiniMessage.miniMessage().deserialize( + bar.getBarFormat(), + Placeholder.component("full_segment", Component.text(symbol.repeat(segments))), + Placeholder.component("empty_segment", Component.text(symbol.repeat(barSize - segments))), + Placeholder.component("mana", Component.text(mana)), + Placeholder.component("max_mana", Component.text(maxMana)) + ); } - + private void showManaInChat(Player player, ManaBar bar) { player.sendMessage(getManaMessage(bar)); } - + private void showManaOnHungerBar(Player player, ManaBar bar) { int food = Math.round(((float) bar.getMana() / (float) bar.getMaxMana()) * 20); @@ -263,9 +286,9 @@ private void showManaOnHungerBar(Player player, ManaBar bar) { } private void showManaOnActionBar(Player player, ManaBar bar) { - player.sendActionBar(Util.getMiniMessage(getManaMessage(bar))); + player.sendActionBar(getManaMessage(bar)); } - + private void showManaOnExperienceBar(Player player, ManaBar bar) { MagicSpells.getExpBarManager().update(player, bar.getMana(), (float) bar.getMana() / (float) bar.getMaxMana()); } @@ -277,7 +300,7 @@ public boolean usingHungerBar() { public boolean usingActionBar() { return showManaOnActionBar; } - + public boolean usingExperienceBar() { return showManaOnExperienceBar; } @@ -292,7 +315,7 @@ public void disable() { } regenerators.clear(); } - + private class Regenerator implements Runnable { private final ManaRank rank; @@ -321,7 +344,7 @@ public void run() { showMana(player, showManaOnRegen); } } - + } } diff --git a/core/src/main/java/com/nisovin/magicspells/spells/BowSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/BowSpell.java index c782abf60..9f90d4b86 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/BowSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/BowSpell.java @@ -79,12 +79,12 @@ public BowSpell(MagicConfig config, String spellName) { List names = getConfigStringList("bow-names", null); if (names != null) { bowNames = new ArrayList<>(); - names.forEach(str -> bowNames.add(Util.getMiniMessage(str))); + names.forEach(str -> bowNames.add(Util.getItemMiniMessage(str))); bowName = null; } else { String bowNameString = getConfigString("bow-name", null); - bowName = Util.getMiniMessage(bowNameString); + bowName = Util.getItemMiniMessage(bowNameString); bowNames = null; } @@ -92,7 +92,7 @@ public BowSpell(MagicConfig config, String spellName) { List disallowedNames = getConfigStringList("disallowed-bow-names", null); if (disallowedNames != null) { disallowedBowNames = new ArrayList<>(); - disallowedNames.forEach(str -> disallowedBowNames.add(Util.getMiniMessage(str))); + disallowedNames.forEach(str -> disallowedBowNames.add(Util.getItemMiniMessage(str))); } else disallowedBowNames = null; if (config.isList(internalKey + "can-trigger")) { diff --git a/core/src/main/java/com/nisovin/magicspells/spells/CommandSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/CommandSpell.java index 4a2c37afc..c7701724c 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/CommandSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/CommandSpell.java @@ -1,7 +1,5 @@ package com.nisovin.magicspells.spells; -import java.util.List; - import org.bukkit.command.CommandSender; import com.nisovin.magicspells.Spell; @@ -32,7 +30,4 @@ public boolean canCastByCommand() { @Override public abstract boolean castFromConsole(CommandSender sender, String[] args); - @Override - public abstract List tabComplete(CommandSender sender, String[] args); - } diff --git a/core/src/main/java/com/nisovin/magicspells/spells/MenuSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/MenuSpell.java index daec4b302..d2683161e 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/MenuSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/MenuSpell.java @@ -22,8 +22,6 @@ import net.kyori.adventure.text.Component; -import co.aikar.commands.ACFUtil; - import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.Subspell; import com.nisovin.magicspells.MagicSpells; @@ -286,8 +284,13 @@ private void applyOptionsToInventory(Player opener, MenuInventory menu) { int quantity; Variable variable = MagicSpells.getVariableManager().getVariable(option.quantity); - if (variable == null) quantity = ACFUtil.parseInt(option.quantity, 1); - else quantity = (int) Math.round(variable.getValue(opener)); + if (variable == null) { + try { + quantity = Integer.parseInt(option.quantity); + } catch (NumberFormatException e) { + quantity = 1; + } + } else quantity = (int) Math.round(variable.getValue(opener)); item.setAmount(quantity); // Set item for all defined slots. @@ -307,7 +310,7 @@ private void applyOptionsToInventory(Player opener, MenuInventory menu) { private Component translateRawComponent(Component component, Player player, SpellData data) { String text = Util.getStringFromComponent(component); text = MagicSpells.doReplacements(text, player, data); - return Util.getMiniMessage(text); + return Util.getItemMiniMessage(text); } private ItemStack translateItem(Player opener, ItemStack item, SpellData data) { diff --git a/core/src/main/java/com/nisovin/magicspells/spells/PlayerMenuSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/PlayerMenuSpell.java index 6c2d4de4b..39c5bab3d 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/PlayerMenuSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/PlayerMenuSpell.java @@ -230,11 +230,11 @@ private void open(Player opener, PlayerMenuInventory menu) { ItemStack head = new ItemStack(Material.PLAYER_HEAD); head.editMeta(SkullMeta.class, meta -> { meta.setOwningPlayer(player); - meta.displayName(Util.getMiniMessage(skullName, opener, subData)); + meta.displayName(Util.getItemMiniMessage(skullName, opener, subData)); if (skullLore != null) { List lore = new ArrayList<>(); - for (String loreLine : skullLore) lore.add(Util.getMiniMessage(loreLine, opener, subData)); + for (String loreLine : skullLore) lore.add(Util.getItemMiniMessage(loreLine, opener, subData)); meta.lore(lore); } @@ -283,7 +283,7 @@ public void onItemClick(InventoryClickEvent event) { OfflinePlayer target = skullMeta.getOwningPlayer(); if (target == null || !target.isOnline()) { - meta.displayName(Util.getMiniMessage(skullNameOffline, opener, menu.openerData)); + meta.displayName(Util.getItemMiniMessage(skullNameOffline, opener, menu.openerData)); if (spellOffline != null) spellOffline.subcast(menu.openerData); if (menu.stayOpen) item.setItemMeta(meta); @@ -300,11 +300,11 @@ public void onItemClick(InventoryClickEvent event) { SpellData targetData = menu.openerData.target(targetPlayer); - meta.displayName(Util.getMiniMessage(skullName, opener, targetData)); + meta.displayName(Util.getItemMiniMessage(skullName, opener, targetData)); item.setItemMeta(meta); if (menu.radius > 0 && targetPlayer.getLocation().distance(opener.getLocation()) > menu.radius) { - meta.displayName(Util.getMiniMessage(skullNameRadius, opener, targetData)); + meta.displayName(Util.getItemMiniMessage(skullNameRadius, opener, targetData)); if (spellRange != null) spellRange.subcast(menu.openerData); if (menu.stayOpen) item.setItemMeta(meta); diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/AdminTeachSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/AdminTeachSpell.java index 16544912d..9803e68de 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/AdminTeachSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/AdminTeachSpell.java @@ -1,28 +1,42 @@ package com.nisovin.magicspells.spells.command; import java.util.Set; -import java.util.List; import java.util.Arrays; import java.util.HashSet; import java.util.Collection; +import org.incendo.cloud.bukkit.parser.PlayerParser; +import org.incendo.cloud.suggestion.SuggestionProvider; +import org.incendo.cloud.parser.aggregate.AggregateParser; + import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.command.CommandSender; import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.command.ConsoleCommandSender; + +import io.papermc.paper.command.brigadier.CommandSourceStack; import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.Spellbook; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.spells.CommandSpell; +import com.nisovin.magicspells.commands.parsers.SpellParser; +import com.nisovin.magicspells.commands.parsers.VarargsParser; // NOTE: THIS DOES NOT PERFORM MANY SAFETY CHECKS, IT IS MEANT TO BE FAST FOR ADMINS // NOTE: THIS CURRENTLY ONLY CASTS FROM CONSOLE public class AdminTeachSpell extends CommandSpell { - + + private static final SuggestionProvider suggestions = AggregateParser + .builder() + .withComponent("player", PlayerParser.playerParser()) + .withComponent("spells", VarargsParser.varargsParser(new SpellParser<>())) + .withDirectMapper(Object.class, ((commandContext, context) -> new Object())) + .build() + .suggestionProvider(); + public AdminTeachSpell(MagicConfig config, String spellName) { super(config, spellName); } @@ -51,13 +65,12 @@ public boolean castFromConsole(CommandSender sender, String[] args) { // Format should be <...> return true; } - + @Override - public List tabComplete(CommandSender sender, String[] args) { - if (!(sender instanceof ConsoleCommandSender) || args.length != 1) return null; - return TxtUtil.tabCompleteSpellName(sender); + public SuggestionProvider suggestionProvider() { + return suggestions; } - + private static class AdminTeachTask extends BukkitRunnable { private final CommandSender sender; diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/BindSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/BindSpell.java index ca768b861..b2417adc5 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/BindSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/BindSpell.java @@ -1,12 +1,22 @@ package com.nisovin.magicspells.spells.command; +import org.jetbrains.annotations.NotNull; + import java.util.Set; import java.util.List; import java.util.HashSet; +import java.util.Objects; +import java.util.Collections; + +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; import org.bukkit.entity.Player; import org.bukkit.command.CommandSender; +import io.papermc.paper.command.brigadier.CommandSourceStack; + import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.Spellbook; @@ -14,8 +24,9 @@ import com.nisovin.magicspells.spells.CommandSpell; import com.nisovin.magicspells.util.config.ConfigData; import com.nisovin.magicspells.spelleffects.EffectPosition; +import com.nisovin.magicspells.commands.parsers.OwnedSpellParser; -public class BindSpell extends CommandSpell { +public class BindSpell extends CommandSpell implements BlockingSuggestionProvider.Strings { private Set bindableItems; @@ -127,7 +138,7 @@ public CastResult cast(SpellData data) { spellbook.save(); MagicSpells.debug(3, " Bind successful."); - sendMessage(strCastSelf, caster, data, "%s", spell.getName()); + sendMessage(strCastSelf, caster, data, "%s", Util.getStrictString(spell.getName())); playSpellEffects(EffectPosition.CASTER, caster, data); return new CastResult(PostCastAction.NO_MESSAGES, data); @@ -139,8 +150,20 @@ public boolean castFromConsole(CommandSender sender, String[] args) { } @Override - public List tabComplete(CommandSender sender, String[] args) { - return sender instanceof Player && args.length == 1 ? TxtUtil.tabCompleteSpellName(sender) : null; + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); + + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + if (!(executor instanceof Player caster)) return Collections.emptyList(); + + CastItem castItem = new CastItem(caster.getInventory().getItemInMainHand()); + if (bindableItems != null && !bindableItems.contains(castItem)) return Collections.emptyList(); + + return OwnedSpellParser.suggest(caster, spell -> { + if (spell.isHelperSpell() || !spell.canCastWithItem()) return false; + if (allowedSpells != null && !allowedSpells.contains(spell)) return false; + return spell.canBind(castItem); + }); } public Set getBindableItems() { diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/ForgetSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/ForgetSpell.java index 70270bb42..442f88f12 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/ForgetSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/ForgetSpell.java @@ -1,13 +1,23 @@ package com.nisovin.magicspells.spells.command; +import org.jetbrains.annotations.NotNull; + import java.util.List; -import java.util.ArrayList; +import java.util.Objects; +import java.util.Collections; + +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import com.nisovin.magicspells.Perm; import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.Spellbook; @@ -15,11 +25,12 @@ import com.nisovin.magicspells.spells.CommandSpell; import com.nisovin.magicspells.util.config.ConfigData; import com.nisovin.magicspells.events.SpellForgetEvent; +import com.nisovin.magicspells.commands.parsers.OwnedSpellParser; // Advanced perm allows you to make others forget a spell // Put * for the spell to forget all of them -public class ForgetSpell extends CommandSpell { +public class ForgetSpell extends CommandSpell implements BlockingSuggestionProvider.Strings { private final ConfigData allowSelfForget; @@ -155,48 +166,57 @@ public boolean castFromConsole(CommandSender sender, String[] args) { if (!forgetEvent.callEvent()) return false; String consoleName = MagicSpells.getConsoleName(); - String targetDisplayName = Util.getStringFromComponent(target.displayName()); + String targetDisplayName = Util.getStrictString(target.displayName()); if (!all) { targetSpellbook.removeSpell(spell); targetSpellbook.save(); - sendMessage(strCastTarget, target, args, "%a", consoleName, "%s", spell.getName(), "%t", targetDisplayName); - sender.sendMessage(formatMessage(strCastSelf, "%a", consoleName, "%s", spell.getName(), "%t", targetDisplayName)); + + String spellName = Util.getStrictString(spell.getName()); + sendMessage(strCastTarget, target, args, "%a", consoleName, "%s", spellName, "%t", targetDisplayName); + sender.sendMessage(Util.getMiniMessage(formatMessage(strCastSelf, "%a", consoleName, "%s", spellName, "%t", targetDisplayName))); } else { targetSpellbook.removeAllSpells(); targetSpellbook.save(); - sender.sendMessage(formatMessage(strResetTarget, "%a", consoleName, "%t", targetDisplayName)); + + sender.sendMessage(Util.getMiniMessage(formatMessage(strResetTarget, "%a", consoleName, "%t", targetDisplayName))); } return true; } @Override - public List tabComplete(CommandSender sender, String[] args) { - if (sender instanceof ConsoleCommandSender) { - if (args.length == 1) return TxtUtil.tabCompletePlayerName(sender); - if (args.length == 2) { - List ret = new ArrayList<>(); - ret.add("*"); - ret.addAll(TxtUtil.tabCompleteSpellName(sender)); - return ret; - } - } else if (sender instanceof Player player) { - if (args.length == 1) { - List ret = new ArrayList<>(); - if (MagicSpells.getSpellbook(player).hasAdvancedPerm("forget")) { - ret.addAll(TxtUtil.tabCompletePlayerName(sender)); - } - ret.add("*"); - ret.addAll(TxtUtil.tabCompleteSpellName(sender)); - return ret; - } - if (args.length == 2 && Bukkit.getPlayer(args[0]) != null) { - List ret = new ArrayList<>(); - ret.add("*"); - ret.addAll(TxtUtil.tabCompleteSpellName(sender)); - return ret; + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); + + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + if (!(executor instanceof ConsoleCommandSender) && !executor.hasPermission(Perm.ADVANCED.getNode() + "forget")) { + if (!(executor instanceof Player caster) || !allowSelfForget.get(new SpellData(caster))) + return Collections.emptyList(); + + return OwnedSpellParser.suggest(caster); + } + + CommandInput original = input.copy(); + + String playerName = input.readString(); + if (!input.isEmpty() || input.input().endsWith(" ")) { + Player target = Bukkit.getPlayer(playerName); + + if (target != null) { + String prefix = original.difference(input.skipWhitespace(), true); + + return OwnedSpellParser.suggest(target).stream() + .map(s -> prefix + s) + .toList(); } } - return null; + + List suggestions = TxtUtil.tabCompletePlayerName(executor); + if (executor instanceof Player caster && allowSelfForget.get(new SpellData(caster))) { + suggestions.add("*"); + suggestions.addAll(OwnedSpellParser.suggest(caster)); + } + + return suggestions; } public String getStrUsage() { diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/HelpSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/HelpSpell.java index 63e0fa7ce..4446bd918 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/HelpSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/HelpSpell.java @@ -1,18 +1,29 @@ package com.nisovin.magicspells.spells.command; -import java.util.List; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.Collections; + +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; import org.bukkit.entity.Player; import org.bukkit.command.CommandSender; +import io.papermc.paper.command.brigadier.CommandSourceStack; + import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.Spellbook; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.spells.CommandSpell; import com.nisovin.magicspells.util.config.ConfigData; +import com.nisovin.magicspells.commands.parsers.SpellParser; +import com.nisovin.magicspells.commands.parsers.OwnedSpellParser; -public class HelpSpell extends CommandSpell { +public class HelpSpell extends CommandSpell implements BlockingSuggestionProvider.Strings { private final ConfigData requireKnownSpell; @@ -64,8 +75,13 @@ public boolean castFromConsole(CommandSender sender, String[] args) { } @Override - public List tabComplete(CommandSender sender, String[] args) { - return sender instanceof Player && args.length == 1 ? TxtUtil.tabCompleteSpellName(sender) : null; + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); + + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + if (!(executor instanceof Player caster)) return Collections.emptyList(); + + return requireKnownSpell.get(new SpellData(caster)) ? OwnedSpellParser.suggest(caster) : SpellParser.suggest(); } public String getStrUsage() { diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/ImbueSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/ImbueSpell.java index 917b59b2a..272f26fdf 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/ImbueSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/ImbueSpell.java @@ -1,8 +1,14 @@ package com.nisovin.magicspells.spells.command; +import org.jetbrains.annotations.NotNull; + import java.util.*; import java.util.regex.Pattern; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; + import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.entity.Player; @@ -16,16 +22,19 @@ import org.bukkit.persistence.PersistentDataType; import org.bukkit.event.player.PlayerInteractEvent; +import io.papermc.paper.command.brigadier.CommandSourceStack; + import com.nisovin.magicspells.Perm; import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.spells.CommandSpell; import com.nisovin.magicspells.util.config.ConfigData; +import com.nisovin.magicspells.commands.parsers.OwnedSpellParser; // Advanced perm is for specifying the number of uses if it isn't normally allowed -public class ImbueSpell extends CommandSpell { +public class ImbueSpell extends CommandSpell implements BlockingSuggestionProvider.Strings { private static final Pattern CAST_ARG_USES_PATTERN = Pattern.compile("\\d+"); private static final NamespacedKey KEY = new NamespacedKey(MagicSpells.getInstance(), "imbue_data"); @@ -147,11 +156,15 @@ public boolean castFromConsole(CommandSender sender, String[] args) { } @Override - public List tabComplete(CommandSender sender, String[] args) { - if (!(sender instanceof Player)) return null; - if (args.length == 1) return TxtUtil.tabCompleteSpellName(sender); - if (args.length == 2) return List.of("1"); - return null; + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); + + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + if (!(executor instanceof Player caster)) return Collections.emptyList(); + + return OwnedSpellParser.suggest(caster, + requireTeachPerm.get(new SpellData(caster)) ? MagicSpells.getSpellbook(caster)::canTeach : null + ); } @EventHandler(priority = EventPriority.HIGHEST) @@ -212,11 +225,11 @@ private void setItemNameAndLore(ItemStack item, Spell spell, int uses) { ItemMeta meta = item.getItemMeta(); if (!strItemName.isEmpty()) { String displayName = strItemName.replace("%s", spell.getName()).replace("%u", uses + ""); - meta.displayName(Util.getMiniMessage(displayName)); + meta.displayName(Util.getItemMiniMessage(displayName)); } if (!strItemLore.isEmpty()) { String lore = strItemLore.replace("%s", spell.getName()).replace("%u", uses + ""); - meta.lore(Collections.singletonList(Util.getMiniMessage(lore))); + meta.lore(Collections.singletonList(Util.getItemMiniMessage(lore))); } item.setItemMeta(meta); } diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/ItemSerializeSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/ItemSerializeSpell.java index 44d07f8bc..6a3823e6a 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/ItemSerializeSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/ItemSerializeSpell.java @@ -1,7 +1,6 @@ package com.nisovin.magicspells.spells.command; import java.io.File; -import java.util.List; import java.io.IOException; import org.bukkit.entity.Player; @@ -85,11 +84,6 @@ public boolean castFromConsole(CommandSender sender, String[] args) { return false; } - @Override - public List tabComplete(CommandSender sender, String[] args) { - return null; - } - public File getDataFolder() { return dataFolder; } diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/KeybindSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/KeybindSpell.java index 5543d65a4..21e6a8a96 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/KeybindSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/KeybindSpell.java @@ -1,11 +1,20 @@ package com.nisovin.magicspells.spells.command; +import org.jetbrains.annotations.NotNull; + import java.io.File; import java.util.Map; import java.util.List; import java.util.HashMap; +import java.util.Objects; import java.io.IOException; -import java.util.ArrayList; +import java.util.Collections; + +import com.google.common.collect.Iterables; + +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -18,14 +27,17 @@ import org.bukkit.event.player.PlayerAnimationEvent; import org.bukkit.configuration.file.YamlConfiguration; +import io.papermc.paper.command.brigadier.CommandSourceStack; + import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.spells.CommandSpell; import com.nisovin.magicspells.util.magicitems.MagicItem; import com.nisovin.magicspells.util.magicitems.MagicItems; +import com.nisovin.magicspells.commands.parsers.OwnedSpellParser; -public class KeybindSpell extends CommandSpell { +public class KeybindSpell extends CommandSpell implements BlockingSuggestionProvider.Strings { private final Map playerKeybinds; @@ -139,11 +151,13 @@ public boolean castFromConsole(CommandSender sender, String[] args) { } @Override - public List tabComplete(CommandSender sender, String[] args) { - if (!(sender instanceof Player) || args.length != 1) return null; - List ret = new ArrayList<>(List.of("clear", "clearall")); - ret.addAll(TxtUtil.tabCompleteSpellName(sender)); - return ret; + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); + + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + if (!(executor instanceof Player caster)) return Collections.emptyList(); + + return Iterables.concat(OwnedSpellParser.suggest(caster), List.of("clear", "clearall")); } @EventHandler diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/ListSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/ListSpell.java index f00c4f093..0825e12a7 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/ListSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/ListSpell.java @@ -1,18 +1,30 @@ package com.nisovin.magicspells.spells.command; +import org.jetbrains.annotations.NotNull; + import java.util.List; +import java.util.Objects; import java.util.Collection; +import java.util.Collections; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.TextComponent; + +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; -import net.kyori.adventure.text.Component; +import io.papermc.paper.command.brigadier.CommandSourceStack; import com.nisovin.magicspells.Spell; -import com.nisovin.magicspells.Spellbook; import com.nisovin.magicspells.util.*; +import com.nisovin.magicspells.Spellbook; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.spells.PassiveSpell; import com.nisovin.magicspells.spells.CommandSpell; @@ -20,7 +32,7 @@ // Advanced perm is for listing other player's spells -public class ListSpell extends CommandSpell { +public class ListSpell extends CommandSpell implements BlockingSuggestionProvider.Strings { private final List spellsToHide; @@ -56,12 +68,16 @@ public CastResult cast(SpellData data) { if (!(data.caster() instanceof Player caster)) return new CastResult(PostCastAction.ALREADY_HANDLED, data); Spellbook spellbook = MagicSpells.getSpellbook(caster); - String extra = ""; + ComponentLike extra = Component.space(); if (data.hasArgs() && spellbook.hasAdvancedPerm("list")) { - Player p = Bukkit.getPlayer(data.args()[0]); - if (p != null) { - spellbook = MagicSpells.getSpellbook(p); - extra = '(' + Util.getStringFromComponent(p.displayName()) + ") "; + Player player = Bukkit.getPlayer(data.args()[0]); + if (player != null) { + spellbook = MagicSpells.getSpellbook(player); + + extra = Component.text() + .append(Component.text(" (")) + .append(player.displayName()) + .append(Component.text(") ")); } } @@ -81,19 +97,21 @@ public CastResult cast(SpellData data) { return new CastResult(PostCastAction.HANDLE_NORMALLY, data); } - Component message = Util.getMiniMessage(MagicSpells.getTextColor() + strPrefix + " " + extra); + TextComponent.Builder message = Component.text() + .append(Util.getMiniMessage(strPrefix)) + .append(extra); boolean prev = false; for (Spell spell : spells) { if (shouldListSpell(spell, spellbook, onlyShowCastableSpells)) { - if (prev) message = message.append(Component.text(", ")); + if (prev) message.append(Component.text(", ")); - message = message.append(Util.getMiniMessage(spell.getName())); + message.append(Util.getMiniMessage(spell.getName())); prev = true; } } - caster.sendMessage(message); + caster.sendMessage(Util.getMessageText(message)); playSpellEffects(data); return new CastResult(PostCastAction.HANDLE_NORMALLY, data); @@ -129,14 +147,16 @@ public boolean castFromConsole(CommandSender sender, String[] args) { } @Override - public List tabComplete(CommandSender sender, String[] args) { - if (args.length != 1) return null; + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); - if (sender instanceof Player player) { - if (!MagicSpells.getSpellbook(player).hasAdvancedPerm("list")) return null; - } else if (!(sender instanceof ConsoleCommandSender)) return null; + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + if (executor instanceof Player caster) { + Spellbook spellbook = MagicSpells.getSpellbook(caster); + if (!spellbook.hasAdvancedPerm("list")) return Collections.emptyList(); + } else if (!(executor instanceof ConsoleCommandSender)) return Collections.emptyList(); - return TxtUtil.tabCompletePlayerName(sender); + return TxtUtil.tabCompletePlayerName(executor); } private boolean shouldListSpell(Spell spell, Spellbook spellbook, boolean onlyShowCastableSpells) { diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/ScrollSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/ScrollSpell.java index eac84fd52..c7a956682 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/ScrollSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/ScrollSpell.java @@ -1,10 +1,16 @@ package com.nisovin.magicspells.spells.command; +import org.jetbrains.annotations.NotNull; + import java.util.*; import java.util.regex.Pattern; import net.kyori.adventure.text.Component; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; + import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.NamespacedKey; @@ -21,14 +27,18 @@ import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerItemHeldEvent; +import io.papermc.paper.command.brigadier.CommandSourceStack; + import com.nisovin.magicspells.Perm; import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.Spellbook; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.spells.CommandSpell; +import com.nisovin.magicspells.commands.parsers.SpellParser; +import com.nisovin.magicspells.commands.parsers.OwnedSpellParser; -public class ScrollSpell extends CommandSpell { +public class ScrollSpell extends CommandSpell implements BlockingSuggestionProvider.Strings { private static final Pattern SCROLL_DATA_USES_PATTERN = Pattern.compile("^\\d+$"); private static final Pattern CAST_ARGUMENT_USE_COUNT_PATTERN = Pattern.compile("^-?\\d+$"); @@ -215,10 +225,10 @@ public ItemStack createScroll(Spell spell, int uses, ItemStack item) { String usageCount = uses >= 0 ? String.valueOf(uses) : "many"; String displayName = strScrollName.replace("%s", spell.getName()).replace("%u", usageCount); - meta.displayName(Util.getMiniMessage(displayName)); + meta.displayName(Util.getItemMiniMessage(displayName)); if (strScrollSubtext != null && !strScrollSubtext.isEmpty()) { - Component lore = Util.getMiniMessage(strScrollSubtext.replace("%s", spell.getName()).replace("%u", usageCount)); + Component lore = Util.getItemMiniMessage(strScrollSubtext.replace("%s", spell.getName()).replace("%u", usageCount)); meta.lore(Collections.singletonList(lore)); } @@ -228,19 +238,30 @@ public ItemStack createScroll(Spell spell, int uses, ItemStack item) { return item; } - + @Override - public List tabComplete(CommandSender sender, String[] args) { - if (sender instanceof ConsoleCommandSender) { - if (args.length == 1) return TxtUtil.tabCompletePlayerName(sender); - if (args.length == 2) return TxtUtil.tabCompleteSpellName(sender); - if (args.length == 3) return List.of("1"); - } - else if (sender instanceof Player) { - if (args.length == 1) return TxtUtil.tabCompleteSpellName(sender); - if (args.length == 2) return List.of("1"); - } - return null; + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); + + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + if (executor instanceof Player caster) + return OwnedSpellParser.suggest(caster, requireTeachPerm ? MagicSpells.getSpellbook(caster)::canTeach : null); + + if (!(executor instanceof ConsoleCommandSender)) return Collections.emptyList(); + + List suggestions = TxtUtil.tabCompletePlayerName(executor); + CommandInput original = input.copy(); + + String playerName = input.readString(); + if (playerName.isEmpty() || input.isEmpty() && !input.input().endsWith(" ")) return suggestions; + + Player player = Bukkit.getPlayer(playerName); + if (player == null) return suggestions; + + String diff = original.difference(input.skipWhitespace(), true); + SpellParser.suggest().stream().map(spell -> diff + spell).forEach(suggestions::add); + + return suggestions; } @EventHandler(priority=EventPriority.MONITOR) diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/SpellbookSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/SpellbookSpell.java index ccd80cd9f..6f8f75ae7 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/SpellbookSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/SpellbookSpell.java @@ -1,13 +1,20 @@ package com.nisovin.magicspells.spells.command; +import org.jetbrains.annotations.NotNull; + import java.io.File; import java.util.List; +import java.util.Objects; import java.util.Scanner; import java.io.FileWriter; import java.util.ArrayList; import java.io.BufferedWriter; import java.io.FileNotFoundException; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; + import org.bukkit.World; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -21,9 +28,10 @@ import org.bukkit.command.CommandSender; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.command.ConsoleCommandSender; import org.bukkit.event.player.PlayerInteractEvent; +import io.papermc.paper.command.brigadier.CommandSourceStack; + import com.nisovin.magicspells.Perm; import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.util.*; @@ -34,12 +42,13 @@ import com.nisovin.magicspells.events.SpellLearnEvent; import com.nisovin.magicspells.util.config.ConfigData; import com.nisovin.magicspells.spelleffects.EffectPosition; +import com.nisovin.magicspells.commands.parsers.OwnedSpellParser; import com.nisovin.magicspells.events.SpellLearnEvent.LearnSource; // Advanced perm is for being able to destroy spellbooks // Op is currently required for using the reload -public class SpellbookSpell extends CommandSpell { +public class SpellbookSpell extends CommandSpell implements BlockingSuggestionProvider.Strings { private List bookSpells; private List bookUses; @@ -237,15 +246,19 @@ public void onBlockBreak(BlockBreakEvent event) { } @Override - public List tabComplete(CommandSender sender, String[] args) { - if (args.length == 1) { - List ret = new ArrayList<>(); - ret.add("reload"); - if (sender instanceof Player) ret.addAll(TxtUtil.tabCompleteSpellName(sender)); - return ret; + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + + List suggestions = new ArrayList<>(); + if (executor.isOp()) suggestions.add("reload"); + + if (executor instanceof Player player) { + Spellbook spellbook = MagicSpells.getSpellbook(player); + suggestions.addAll(OwnedSpellParser.suggest(player, spellbook::canTeach)); } - if (args.length != 2 || args[0].equals("reload") || sender instanceof ConsoleCommandSender) return null; - return List.of("1"); + + return suggestions; } private void loadSpellbooks() { diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/SublistSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/SublistSpell.java index 8d10b4826..e4e9005ba 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/SublistSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/SublistSpell.java @@ -1,7 +1,11 @@ package com.nisovin.magicspells.spells.command; +import org.jetbrains.annotations.NotNull; + import java.util.List; +import java.util.Objects; import java.util.Collection; +import java.util.Collections; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -9,10 +13,18 @@ import org.bukkit.command.ConsoleCommandSender; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.TextComponent; + +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; + +import io.papermc.paper.command.brigadier.CommandSourceStack; import com.nisovin.magicspells.Spell; -import com.nisovin.magicspells.Spellbook; import com.nisovin.magicspells.util.*; +import com.nisovin.magicspells.Spellbook; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.spells.CommandSpell; import com.nisovin.magicspells.spells.PassiveSpell; @@ -20,7 +32,7 @@ // Advanced perm is for listing other player's spells -public class SublistSpell extends CommandSpell { +public class SublistSpell extends CommandSpell implements BlockingSuggestionProvider.Strings { private final List spellsToHide; private final List spellsToShow; @@ -49,12 +61,16 @@ public CastResult cast(SpellData data) { if (!(data.caster() instanceof Player caster)) return new CastResult(PostCastAction.ALREADY_HANDLED, data); Spellbook spellbook = MagicSpells.getSpellbook(caster); - String extra = ""; + ComponentLike extra = Component.space(); if (data.hasArgs() && spellbook.hasAdvancedPerm("list")) { - Player p = Bukkit.getPlayer(data.args()[0]); - if (p != null) { - spellbook = MagicSpells.getSpellbook(p); - extra = '(' + Util.getStringFromComponent(p.displayName()) + ") "; + Player player = Bukkit.getPlayer(data.args()[0]); + if (player != null) { + spellbook = MagicSpells.getSpellbook(player); + + extra = Component.text() + .append(Component.text(" (")) + .append(player.displayName()) + .append(Component.text(") ")); } } @@ -74,19 +90,21 @@ public CastResult cast(SpellData data) { return new CastResult(PostCastAction.HANDLE_NORMALLY, data); } - Component message = Util.getMiniMessage(MagicSpells.getTextColor() + strPrefix + " " + extra); + TextComponent.Builder message = Component.text() + .append(Util.getMiniMessage(strPrefix)) + .append(extra); boolean prev = false; for (Spell spell : spells) { if (shouldListSpell(spell, spellbook, onlyShowCastableSpells)) { - if (prev) message = message.append(Component.text(", ")); + if (prev) message.append(Component.text(", ")); - message = message.append(Util.getMiniMessage(spell.getName())); + message.append(Util.getMiniMessage(spell.getName())); prev = true; } } - caster.sendMessage(message); + caster.sendMessage(Util.getMessageText(message)); playSpellEffects(data); return new CastResult(PostCastAction.HANDLE_NORMALLY, data); @@ -122,8 +140,16 @@ public boolean castFromConsole(CommandSender sender, String[] args) { } @Override - public List tabComplete(CommandSender sender, String[] args) { - return sender instanceof ConsoleCommandSender && args.length == 1 ? TxtUtil.tabCompletePlayerName(sender) : null; + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); + + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + if (executor instanceof Player caster) { + Spellbook spellbook = MagicSpells.getSpellbook(caster); + if (!spellbook.hasAdvancedPerm("list")) return Collections.emptyList(); + } else if (!(executor instanceof ConsoleCommandSender)) return Collections.emptyList(); + + return TxtUtil.tabCompletePlayerName(executor); } private boolean shouldListSpell(Spell spell, Spellbook spellbook, boolean onlyShowCastableSpells) { diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/TeachSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/TeachSpell.java index b049e1108..694988a45 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/TeachSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/TeachSpell.java @@ -1,11 +1,20 @@ package com.nisovin.magicspells.spells.command; +import org.jetbrains.annotations.NotNull; + import java.util.List; +import java.util.Objects; + +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.command.CommandSender; +import io.papermc.paper.command.brigadier.CommandSourceStack; + import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.Spellbook; @@ -14,9 +23,11 @@ import com.nisovin.magicspells.util.compat.EventUtil; import com.nisovin.magicspells.events.SpellLearnEvent; import com.nisovin.magicspells.util.config.ConfigData; +import com.nisovin.magicspells.commands.parsers.SpellParser; +import com.nisovin.magicspells.commands.parsers.OwnedSpellParser; import com.nisovin.magicspells.events.SpellLearnEvent.LearnSource; -public class TeachSpell extends CommandSpell { +public class TeachSpell extends CommandSpell implements BlockingSuggestionProvider.Strings { private ConfigData requireKnownSpell; @@ -144,14 +155,44 @@ public boolean castFromConsole(CommandSender sender, String[] args) { sender.sendMessage(formatMessage(strCastSelf, "%a", consoleName, "%s", spell.getName(), "%t", displayName)); return true; } - + @Override - public List tabComplete(CommandSender sender, String[] args) { - if (args.length == 1) return TxtUtil.tabCompletePlayerName(sender); - if (args.length == 2) return TxtUtil.tabCompleteSpellName(sender); - return null; + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + + CommandInput original = input.copy(); + + String playerName = input.readString(); + if (playerName.isEmpty() || Bukkit.getPlayer(playerName) == null) { + return TxtUtil.tabCompletePlayerName(executor, + !(executor instanceof Player caster) || !requireKnownSpell.get(new SpellData(caster))); + } + + if (executor instanceof Player caster && requireKnownSpell.get(new SpellData(caster))) { + if (!input.isEmpty() || input.input().endsWith(" ")) { + String diff = original.difference(input.skipWhitespace(), true); + Spellbook spellbook = MagicSpells.getSpellbook(caster); + + return OwnedSpellParser.suggest(caster, spellbook::canTeach).stream() + .map(s -> diff + s) + .toList(); + } + + return TxtUtil.tabCompletePlayerName(executor, false); + } + + if (!input.isEmpty() || input.input().endsWith(" ")) { + String diff = original.difference(input.skipWhitespace(), true); + + return SpellParser.suggest().stream() + .map(s -> diff + s) + .toList(); + } + + return TxtUtil.tabCompletePlayerName(executor, true); } - + private boolean callEvent(Spell spell, Player learner, Object teacher) { SpellLearnEvent event = new SpellLearnEvent(spell, learner, LearnSource.TEACH, teacher); EventUtil.call(event); diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/TomeSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/TomeSpell.java index 064bd5f89..3d16f5498 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/TomeSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/TomeSpell.java @@ -1,8 +1,15 @@ package com.nisovin.magicspells.spells.command; -import java.util.List; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.Collections; import java.util.regex.Pattern; +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; + import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.entity.Player; @@ -14,6 +21,8 @@ import org.bukkit.persistence.PersistentDataType; import org.bukkit.event.player.PlayerInteractEvent; +import io.papermc.paper.command.brigadier.CommandSourceStack; + import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.Spellbook; @@ -23,14 +32,15 @@ import com.nisovin.magicspells.events.SpellLearnEvent; import com.nisovin.magicspells.util.config.ConfigData; import com.nisovin.magicspells.spelleffects.EffectPosition; +import com.nisovin.magicspells.commands.parsers.OwnedSpellParser; import com.nisovin.magicspells.events.SpellLearnEvent.LearnSource; // TODO this should not be hardcoded to use a book -public class TomeSpell extends CommandSpell { +public class TomeSpell extends CommandSpell implements BlockingSuggestionProvider.Strings { private static final Pattern INT_PATTERN = Pattern.compile("^\\d+$"); private static final NamespacedKey KEY = new NamespacedKey(MagicSpells.getInstance(), "tome_data"); - + private boolean consumeBook; private boolean cancelReadOnLearn; @@ -116,10 +126,15 @@ public boolean castFromConsole(CommandSender sender, String[] args) { } @Override - public List tabComplete(CommandSender sender, String[] args) { - if (args.length == 1) return TxtUtil.tabCompleteSpellName(sender); - if (args.length == 2) return List.of("1"); - return null; + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); + + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + if (!(executor instanceof Player caster)) return Collections.emptyList(); + + return OwnedSpellParser.suggest(caster, + requireTeachPerm.get(new SpellData(caster)) ? MagicSpells.getSpellbook(caster)::canTeach : null + ); } public ItemStack createTome(Spell spell, int uses, ItemStack item, SpellData data) { @@ -136,7 +151,7 @@ public ItemStack createTome(Spell spell, int uses, ItemStack item, SpellData dat item.editMeta(meta -> meta.getPersistentDataContainer().set(KEY, PersistentDataType.STRING, spell.getInternalName() + (finalUses > 0 ? "," + finalUses : ""))); return item; } - + @EventHandler public void onInteract(PlayerInteractEvent event) { if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) return; @@ -147,7 +162,7 @@ public void onInteract(PlayerInteractEvent event) { String spellData = item.getItemMeta().getPersistentDataContainer().get(KEY, PersistentDataType.STRING); if (spellData == null || spellData.isEmpty()) return; - + String[] data = spellData.split(","); Spell spell = MagicSpells.getSpellByInternalName(data[0]); int uses = -1; diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/UnbindSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/UnbindSpell.java index 5b7ba679f..038605907 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/UnbindSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/UnbindSpell.java @@ -1,21 +1,32 @@ package com.nisovin.magicspells.spells.command; +import org.jetbrains.annotations.NotNull; + import java.util.Set; import java.util.List; import java.util.HashSet; +import java.util.Objects; import java.util.ArrayList; +import java.util.Collections; + +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; import org.bukkit.entity.Player; import org.bukkit.command.CommandSender; +import io.papermc.paper.command.brigadier.CommandSourceStack; + import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.Spellbook; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.spells.CommandSpell; import com.nisovin.magicspells.spelleffects.EffectPosition; +import com.nisovin.magicspells.commands.parsers.SpellParser; -public class UnbindSpell extends CommandSpell { +public class UnbindSpell extends CommandSpell implements BlockingSuggestionProvider.Strings { private Set allowedSpells; @@ -111,12 +122,26 @@ public boolean castFromConsole(CommandSender sender, String[] args) { } @Override - public List tabComplete(CommandSender sender, String[] args) { - if (!(sender instanceof Player) || args.length != 1) return null; - List ret = new ArrayList<>(); - ret.add("*"); - ret.addAll(TxtUtil.tabCompleteSpellName(sender)); - return ret; + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); + + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + if (!(executor instanceof Player caster)) return Collections.emptyList(); + + Spellbook spellbook = MagicSpells.getSpellbook(caster); + CastItem castItem = new CastItem(caster.getInventory().getItemInMainHand()); + + List spells = spellbook.getItemSpells().get(castItem); + if (spells == null || spells.isEmpty()) return Collections.emptyList(); + + List suggestions = new ArrayList<>(); + for (Spell spell : spells) { + if (allowedSpells != null && !allowedSpells.contains(spell)) continue; + suggestions.add(SpellParser.escapeIfRequired(Util.getPlainString(spell.getName()))); + } + if (!suggestions.isEmpty()) suggestions.add("*"); + + return suggestions; } public Set getAllowedSpells() { diff --git a/core/src/main/java/com/nisovin/magicspells/spells/instant/ConjureBookSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/instant/ConjureBookSpell.java index 213583c03..c11513a40 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/instant/ConjureBookSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/instant/ConjureBookSpell.java @@ -115,7 +115,7 @@ private static Component createComponent(String raw, Player player, String displ if (displayName != null) raw = DISPLAY_NAME_VARIABLE_PATTERN.matcher(raw).replaceAll(displayName); raw = MagicSpells.doReplacements(raw, player, data); } - return Util.getMiniMessage(raw); + return Util.getItemMiniMessage(raw); } private ItemStack createBook(Player player, SpellData data) { diff --git a/core/src/main/java/com/nisovin/magicspells/spells/instant/ConjureFireworkSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/instant/ConjureFireworkSpell.java index 6a98a4edc..f0a9331e9 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/instant/ConjureFireworkSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/instant/ConjureFireworkSpell.java @@ -48,7 +48,7 @@ public ConjureFireworkSpell(MagicConfig config, String spellName) { firework = new ItemStack(Material.FIREWORK_ROCKET); FireworkMeta meta = (FireworkMeta) firework.getItemMeta(); - if (!fireworkName.isEmpty()) meta.displayName(Util.getMiniMessage(fireworkName)); + if (!fireworkName.isEmpty()) meta.displayName(Util.getItemMiniMessage(fireworkName)); List fireworkEffects = getConfigStringList("firework-effects", null); if (fireworkEffects != null && !fireworkEffects.isEmpty()) { diff --git a/core/src/main/java/com/nisovin/magicspells/spells/instant/ConjureSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/instant/ConjureSpell.java index e3bf8de3f..e000418a7 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/instant/ConjureSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/instant/ConjureSpell.java @@ -468,8 +468,8 @@ private void addExpiresLine(ItemStack item, double expireHours) { if (lore == null) lore = new ArrayList<>(); long expiresAt = System.currentTimeMillis() + (long) (expireHours * TimeUtil.MILLISECONDS_PER_HOUR); - lore.add(Util.getMiniMessage(getExpiresText(expiresAt))); - lore.add(Util.getMiniMessage(expPrefix + expiresAt)); + lore.add(Util.getItemMiniMessage(getExpiresText(expiresAt))); + lore.add(Util.getItemMiniMessage(expPrefix + expiresAt)); meta.lore(lore); item.setItemMeta(meta); } @@ -555,7 +555,7 @@ private ExpirationResult updateExpiresLineIfNeeded(ItemStack item) { long expiresAt = Long.parseLong(lastLine.replace(expPrefix, "")); if (expiresAt < System.currentTimeMillis()) return ExpirationResult.EXPIRED; - lore.set(lore.size() - 2, Util.getMiniMessage(getExpiresText(expiresAt))); + lore.set(lore.size() - 2, Util.getItemMiniMessage(getExpiresText(expiresAt))); meta.lore(lore); item.setItemMeta(meta); return ExpirationResult.UPDATE; diff --git a/core/src/main/java/com/nisovin/magicspells/spells/instant/EnderchestSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/instant/EnderchestSpell.java index c02035a39..a0e9804da 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/instant/EnderchestSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/instant/EnderchestSpell.java @@ -1,18 +1,27 @@ package com.nisovin.magicspells.spells.instant; -import java.util.List; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.Collections; + +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.command.CommandSender; -import org.bukkit.command.ConsoleCommandSender; +import com.nisovin.magicspells.Perm; import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.spells.InstantSpell; import com.nisovin.magicspells.spells.TargetedEntitySpell; -public class EnderchestSpell extends InstantSpell implements TargetedEntitySpell { +import io.papermc.paper.command.brigadier.CommandSourceStack; + +public class EnderchestSpell extends InstantSpell implements TargetedEntitySpell, BlockingSuggestionProvider.Strings { public EnderchestSpell(MagicConfig config, String spellName) { super(config, spellName); @@ -49,14 +58,14 @@ public CastResult castAtEntity(SpellData data) { } @Override - public List tabComplete(CommandSender sender, String[] args) { - if (args.length != 1) return null; + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); - if (sender instanceof Player player) { - if (!MagicSpells.getSpellbook(player).hasAdvancedPerm(internalName)) return null; - } else if (!(sender instanceof ConsoleCommandSender)) return null; + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + if (!(executor instanceof Player player) || !executor.hasPermission(Perm.ADVANCED.getNode(this))) + return Collections.emptyList(); - return TxtUtil.tabCompletePlayerName(sender); + return TxtUtil.tabCompletePlayerName(player); } } diff --git a/core/src/main/java/com/nisovin/magicspells/spells/instant/RecallSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/instant/RecallSpell.java index dd41920e0..e38e88e51 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/instant/RecallSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/instant/RecallSpell.java @@ -1,6 +1,13 @@ package com.nisovin.magicspells.spells.instant; -import java.util.List; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.Collections; + +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -8,17 +15,19 @@ import org.bukkit.entity.Vehicle; import org.bukkit.entity.LivingEntity; import org.bukkit.command.CommandSender; -import org.bukkit.command.ConsoleCommandSender; + +import io.papermc.paper.command.brigadier.CommandSourceStack; import com.nisovin.magicspells.Spell; import com.nisovin.magicspells.util.*; +import com.nisovin.magicspells.Spellbook; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.spells.InstantSpell; import com.nisovin.magicspells.util.config.ConfigData; import com.nisovin.magicspells.spells.TargetedEntitySpell; import com.nisovin.magicspells.spelleffects.EffectPosition; -public class RecallSpell extends InstantSpell implements TargetedEntitySpell { +public class RecallSpell extends InstantSpell implements TargetedEntitySpell, BlockingSuggestionProvider.Strings { private final ConfigData maxRange; @@ -77,14 +86,16 @@ public CastResult castAtEntity(SpellData data) { } @Override - public List tabComplete(CommandSender sender, String[] args) { - if (args.length != 1) return null; + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); + + CommandSender executor = Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()); + if (!(executor instanceof Player caster)) return Collections.emptyList(); - if (sender instanceof Player player) { - if (!MagicSpells.getSpellbook(player).hasAdvancedPerm(internalName)) return null; - } else if (!(sender instanceof ConsoleCommandSender)) return null; + Spellbook spellbook = MagicSpells.getSpellbook(caster); + if (!spellbook.hasAdvancedPerm(internalName)) return Collections.emptyList(); - return TxtUtil.tabCompletePlayerName(sender); + return TxtUtil.tabCompletePlayerName(executor); } private CastResult recall(SpellData data, LivingEntity entity, Location markLocation) { diff --git a/core/src/main/java/com/nisovin/magicspells/spells/targeted/CaptureSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/targeted/CaptureSpell.java index fe2f9c7db..ac6bf9b03 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/targeted/CaptureSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/targeted/CaptureSpell.java @@ -65,11 +65,11 @@ public CastResult castAtEntity(SpellData data) { String[] replacements = {"%name%", getTargetName(target)}; item.editMeta(meta -> { - if (itemName != null) meta.displayName(Util.getMiniMessage(itemName, data, replacements)); + if (itemName != null) meta.displayName(Util.getItemMiniMessage(itemName, data, replacements)); if (itemLore != null) { List lore = new ArrayList<>(); - for (String line : itemLore) lore.add(Util.getMiniMessage(line, data, replacements)); + for (String line : itemLore) lore.add(Util.getItemMiniMessage(line, data, replacements)); meta.lore(lore); } }); diff --git a/core/src/main/java/com/nisovin/magicspells/spells/targeted/SummonSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/targeted/SummonSpell.java index c2980b736..27b0e9154 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/targeted/SummonSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/targeted/SummonSpell.java @@ -1,9 +1,15 @@ package com.nisovin.magicspells.spells.targeted; +import org.jetbrains.annotations.NotNull; + import java.util.Map; -import java.util.List; import java.util.UUID; import java.util.HashMap; +import java.util.Objects; + +import org.incendo.cloud.context.CommandInput; +import org.incendo.cloud.context.CommandContext; +import org.incendo.cloud.suggestion.BlockingSuggestionProvider; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -14,9 +20,10 @@ import org.bukkit.entity.LivingEntity; import org.bukkit.event.EventPriority; import org.bukkit.util.RayTraceResult; -import org.bukkit.command.CommandSender; import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import io.papermc.paper.command.brigadier.CommandSourceStack; + import com.nisovin.magicspells.util.*; import com.nisovin.magicspells.spells.TargetedSpell; import com.nisovin.magicspells.util.config.ConfigData; @@ -25,7 +32,7 @@ import com.nisovin.magicspells.spelleffects.EffectPosition; import com.nisovin.magicspells.spells.TargetedEntityFromLocationSpell; -public class SummonSpell extends TargetedSpell implements TargetedEntitySpell, TargetedEntityFromLocationSpell { +public class SummonSpell extends TargetedSpell implements TargetedEntitySpell, TargetedEntityFromLocationSpell, BlockingSuggestionProvider.Strings { private final Map pending; @@ -165,8 +172,9 @@ public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { } @Override - public List tabComplete(CommandSender sender, String[] args) { - return args.length == 1 ? TxtUtil.tabCompletePlayerName(sender) : null; + public @NotNull Iterable<@NotNull String> stringSuggestions(@NotNull CommandContext context, @NotNull CommandInput input) { + CommandSourceStack stack = context.sender(); + return TxtUtil.tabCompletePlayerName(Objects.requireNonNullElse(stack.getExecutor(), stack.getSender()), false); } private record SummonData(Location location, long time, int maxAcceptDelay, SpellData spellData) { diff --git a/core/src/main/java/com/nisovin/magicspells/util/Rotation.java b/core/src/main/java/com/nisovin/magicspells/util/Rotation.java new file mode 100644 index 000000000..938ddaacd --- /dev/null +++ b/core/src/main/java/com/nisovin/magicspells/util/Rotation.java @@ -0,0 +1,85 @@ +package com.nisovin.magicspells.util; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +import org.bukkit.Location; + +/** + * Represents a rotation that can be applied to a {@link Location}. + */ +public final class Rotation { + + private final Angle yaw; + private final Angle pitch; + + private Rotation(final @NotNull Angle yaw, final @NotNull Angle pitch) { + this.yaw = yaw; + this.pitch = pitch; + } + + /** + * Create a new rotation object. + * + * @param yaw yaw + * @param pitch pitch + * @return Created rotation instance. + */ + public static Rotation of(final @NotNull Angle yaw, final @NotNull Angle pitch) { + return new Rotation(yaw, pitch); + } + + /** + * Returns the yaw of this rotation. + * + * @return yaw + */ + public Angle yaw() { + return this.yaw; + } + + /** + * Returns the pitch of this rotation + * + * @return pitch + */ + public Angle pitch() { + return this.pitch; + } + + /** + * Applies this rotation to a location. + * + * @param location the location to be modified + * @return the modified location + */ + public @NotNull Location apply(final @NotNull Location location) { + location.setYaw(this.yaw.apply(location.getYaw())); + location.setPitch(this.pitch.apply(location.getPitch())); + return location; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Rotation that = (Rotation) o; + return this.yaw.equals(that.yaw) && this.pitch.equals(that.pitch); + } + + @Override + public int hashCode() { + return Objects.hash(this.yaw, this.pitch); + } + + @Override + public String toString() { + return String.format("Rotation{yaw=%s, pitch=%s}", this.yaw, this.pitch); + } + +} diff --git a/core/src/main/java/com/nisovin/magicspells/util/TxtUtil.java b/core/src/main/java/com/nisovin/magicspells/util/TxtUtil.java index 71a9cbf2d..4189055c7 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/TxtUtil.java +++ b/core/src/main/java/com/nisovin/magicspells/util/TxtUtil.java @@ -67,11 +67,19 @@ public static List tabCompleteSpellName(CommandSender sender) { } public static List tabCompletePlayerName(CommandSender sender) { + return tabCompletePlayerName(sender, true); + } + + public static List tabCompletePlayerName(CommandSender sender, boolean allowSelf) { List matches = new ArrayList<>(); + for (Player p : Bukkit.getOnlinePlayers()) { if (!sender.isOp() && sender instanceof Player player && !player.canSee(p)) continue; + if (!allowSelf && p.equals(sender)) continue; + matches.add(p.getName()); } + return matches; } diff --git a/core/src/main/java/com/nisovin/magicspells/util/Util.java b/core/src/main/java/com/nisovin/magicspells/util/Util.java index 335d7b1bc..184b7dd5e 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/Util.java +++ b/core/src/main/java/com/nisovin/magicspells/util/Util.java @@ -15,6 +15,9 @@ import java.util.function.Supplier; import java.util.function.Predicate; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.format.NamedTextColor; + import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.inventory.*; @@ -29,6 +32,7 @@ import org.bukkit.potion.PotionEffectType; import org.bukkit.configuration.ConfigurationSection; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -695,6 +699,12 @@ public static String getLegacyFromMiniMessage(String input) { return getLegacyFromComponent(getMiniMessage(input)); } + public static String getPlainString(String string) { + if (string == null) return null; + + return PlainTextComponentSerializer.plainText().serialize(getMiniMessage(string)); + } + public static String getPlainString(Component component) { if (component == null) return ""; return PlainTextComponentSerializer.plainText().serialize(component); @@ -723,29 +733,46 @@ public static Component getSmoothComponent(Component component) { .build()).append(component).compact(); } - public static Component getMiniMessage(String input) { + public static Component getMessageText(String input) { if (input == null) return null; - if (input.isEmpty()) return Component.empty(); - Component component = MiniMessage.miniMessage().deserialize(getMiniMessageFromLegacy(input)); - // Remove italics if they aren't present. Otherwise, item name and lore will render italic text. - return component.decoration(TextDecoration.ITALIC, component.hasDecoration(TextDecoration.ITALIC)); + return MiniMessage.miniMessage().deserialize(MagicSpells.getTextFormat(), Placeholder.parsed("text", getMiniMessageFromLegacy(input))); + } + + public static Component getMessageText(ComponentLike input) { + if (input == null) return null; + + return MiniMessage.miniMessage().deserialize(MagicSpells.getTextFormat(), Placeholder.component("text", input)); } - public static Component getMiniMessage(String input, SpellData data) { - return getMiniMessage(MagicSpells.doReplacements(input, data.caster(), data)); + public static Component getMiniMessage(String input) { + if (input == null) return null; + if (input.isEmpty()) return Component.empty(); + return MiniMessage.miniMessage().deserialize(getMiniMessageFromLegacy(input)); } public static Component getMiniMessage(String input, SpellData data, String... replacements) { return getMiniMessage(MagicSpells.doReplacements(input, data.caster(), data, replacements)); - } public static Component getMiniMessage(String input, LivingEntity recipient, SpellData data) { return getMiniMessage(MagicSpells.doReplacements(input, recipient, data)); } + public static Component getItemMiniMessage(String input) { + if (input == null) return null; + return getMiniMessage(input).decorationIfAbsent(TextDecoration.ITALIC, TextDecoration.State.FALSE); + } + + public static Component getItemMiniMessage(String input, SpellData data, String... replacements) { + return getItemMiniMessage(MagicSpells.doReplacements(input, data.caster(), data, replacements)); + } + + public static Component getItemMiniMessage(String input, LivingEntity recipient, SpellData data) { + return getItemMiniMessage(MagicSpells.doReplacements(input, recipient, data)); + } + public static Component getMiniMessageWithVars(Player player, String input) { if (input.isEmpty()) return Component.text(""); return getMiniMessage(MagicSpells.doReplacements(input, player, SpellData.NULL)); @@ -760,10 +787,69 @@ public static String getStringFromComponent(Component component) { return component == null ? "" : MiniMessage.miniMessage().serialize(component); } - public static String getStrictStringFromComponent(Component component) { + public static String getStrictString(Component component) { return component == null ? "" : STRICT_SERIALIZER.serialize(component); } + public static String getStrictString(String string) { + if (string == null) return null; + + return STRICT_SERIALIZER.serialize(getMiniMessage(string)); + } + + public static NamedTextColor getLegacyColor(@Nullable String color, @NotNull NamedTextColor def) { + return switch (color) { + case "0" -> NamedTextColor.BLACK; + case "1" -> NamedTextColor.DARK_BLUE; + case "2" -> NamedTextColor.DARK_GREEN; + case "3" -> NamedTextColor.DARK_AQUA; + case "4" -> NamedTextColor.DARK_RED; + case "5" -> NamedTextColor.DARK_PURPLE; + case "6" -> NamedTextColor.GOLD; + case "7" -> NamedTextColor.GRAY; + case "8" -> NamedTextColor.DARK_GRAY; + case "9" -> NamedTextColor.BLUE; + case "a" -> NamedTextColor.GREEN; + case "b" -> NamedTextColor.AQUA; + case "c" -> NamedTextColor.RED; + case "d" -> NamedTextColor.LIGHT_PURPLE; + case "e" -> NamedTextColor.YELLOW; + case "f" -> NamedTextColor.WHITE; + case null, default -> def; + }; + } + + public static Style getStyle(@Nullable String style, @NotNull Style def) { + return switch (style) { + case "0" -> Style.style(NamedTextColor.BLACK); + case "1" -> Style.style(NamedTextColor.DARK_BLUE); + case "2" -> Style.style(NamedTextColor.DARK_GREEN); + case "3" -> Style.style(NamedTextColor.DARK_AQUA); + case "4" -> Style.style(NamedTextColor.DARK_RED); + case "5" -> Style.style(NamedTextColor.DARK_PURPLE); + case "6" -> Style.style(NamedTextColor.GOLD); + case "7" -> Style.style(NamedTextColor.GRAY); + case "8" -> Style.style(NamedTextColor.DARK_GRAY); + case "9" -> Style.style(NamedTextColor.BLUE); + case "a" -> Style.style(NamedTextColor.GREEN); + case "b" -> Style.style(NamedTextColor.AQUA); + case "c" -> Style.style(NamedTextColor.RED); + case "d" -> Style.style(NamedTextColor.LIGHT_PURPLE); + case "e" -> Style.style(NamedTextColor.YELLOW); + case "f" -> Style.style(NamedTextColor.WHITE); + case "k" -> Style.style(TextDecoration.OBFUSCATED); + case "l" -> Style.style(TextDecoration.BOLD); + case "m" -> Style.style(TextDecoration.STRIKETHROUGH); + case "n" -> Style.style(TextDecoration.UNDERLINED); + case "o" -> Style.style(TextDecoration.ITALIC); + case null -> def; + default -> { + Component component = MiniMessage.miniMessage().deserialize(style); + yield component.style(); + } + }; + } + public static String colorize(String string) { if (string.isEmpty()) return ""; diff --git a/core/src/main/java/com/nisovin/magicspells/util/config/ConfigDataUtil.java b/core/src/main/java/com/nisovin/magicspells/util/config/ConfigDataUtil.java index f976b3968..2e1461a6b 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/config/ConfigDataUtil.java +++ b/core/src/main/java/com/nisovin/magicspells/util/config/ConfigDataUtil.java @@ -364,21 +364,11 @@ public static ConfigData getComponent(@NotNull ConfigurationSection c return data -> component; } - return new ConfigData<>() { - - @Override - public Component get(@NotNull SpellData data) { - String value = supplier.get(data); - if (value == null) return def; - - return Util.getMiniMessage(value); - } - - @Override - public boolean isConstant() { - return false; - } + return (VariableConfigData) data -> { + String value = supplier.get(data); + if (value == null) return def; + return Util.getMiniMessage(value); }; } @@ -393,21 +383,30 @@ public static ConfigData getComponent(@NotNull String value) { return data -> component; } - return new ConfigData<>() { + return (VariableConfigData) data -> { + String val = supplier.get(data); + if (val == null) return null; - @Override - public Component get(@NotNull SpellData data) { - String value = supplier.get(data); - if (value == null) return null; + return Util.getMiniMessage(val); + }; + } - return Util.getMiniMessage(value); - } + @NotNull + public static ConfigData getItemComponent(@NotNull ConfigurationSection config, @NotNull String path, @Nullable Component def) { + ConfigData supplier = getString(config, path, null); + if (supplier.isConstant()) { + String value = supplier.get(); + if (value == null) return data -> def; - @Override - public boolean isConstant() { - return false; - } + Component component = Util.getItemMiniMessage(value); + return data -> component; + } + + return (VariableConfigData) data -> { + String value = supplier.get(data); + if (value == null) return def; + return Util.getItemMiniMessage(value); }; } diff --git a/core/src/main/java/com/nisovin/magicspells/util/itemreader/LoreHandler.java b/core/src/main/java/com/nisovin/magicspells/util/itemreader/LoreHandler.java index 3cf02a595..43a639609 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/itemreader/LoreHandler.java +++ b/core/src/main/java/com/nisovin/magicspells/util/itemreader/LoreHandler.java @@ -22,9 +22,9 @@ public static void process(ConfigurationSection config, ItemMeta meta, MagicItem List lore = new ArrayList<>(); if (config.isList(CONFIG_NAME)) { for (String line : config.getStringList(CONFIG_NAME)) - lore.add(Util.getMiniMessage(line)); + lore.add(Util.getItemMiniMessage(line)); } else if (config.isString(CONFIG_NAME)) { - lore.add(Util.getMiniMessage(config.getString(CONFIG_NAME))); + lore.add(Util.getItemMiniMessage(config.getString(CONFIG_NAME))); } if (lore.isEmpty()) return; diff --git a/core/src/main/java/com/nisovin/magicspells/util/itemreader/NameHandler.java b/core/src/main/java/com/nisovin/magicspells/util/itemreader/NameHandler.java index d8dbc47a8..06387b472 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/itemreader/NameHandler.java +++ b/core/src/main/java/com/nisovin/magicspells/util/itemreader/NameHandler.java @@ -15,7 +15,7 @@ public class NameHandler { public static void process(ConfigurationSection config, ItemMeta meta, MagicItemData data) { if (!config.isString(CONFIG_NAME)) return; - meta.displayName(Util.getMiniMessage(config.getString(CONFIG_NAME))); + meta.displayName(Util.getItemMiniMessage(config.getString(CONFIG_NAME))); data.setAttribute(NAME, meta.displayName()); } diff --git a/core/src/main/java/com/nisovin/magicspells/util/itemreader/WrittenBookHandler.java b/core/src/main/java/com/nisovin/magicspells/util/itemreader/WrittenBookHandler.java index 12d6c3254..bd5e7c41f 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/itemreader/WrittenBookHandler.java +++ b/core/src/main/java/com/nisovin/magicspells/util/itemreader/WrittenBookHandler.java @@ -25,14 +25,14 @@ public static void process(ConfigurationSection config, ItemMeta meta, MagicItem if (!(meta instanceof BookMeta bookMeta)) return; if (config.isString(TITLE_CONFIG_NAME)) { - Component title = Util.getMiniMessage(config.getString(TITLE_CONFIG_NAME)); + Component title = Util.getItemMiniMessage(config.getString(TITLE_CONFIG_NAME)); bookMeta.title(title); data.setAttribute(TITLE, bookMeta.title()); } if (config.isString(AUTHOR_CONFIG_NAME)) { - Component author = Util.getMiniMessage(config.getString(AUTHOR_CONFIG_NAME)); + Component author = Util.getItemMiniMessage(config.getString(AUTHOR_CONFIG_NAME)); bookMeta.author(author); data.setAttribute(AUTHOR, bookMeta.author()); @@ -40,7 +40,7 @@ public static void process(ConfigurationSection config, ItemMeta meta, MagicItem if (config.isList(PAGES_CONFIG_NAME)) { List pages = new ArrayList<>(); - for (String page : config.getStringList(PAGES_CONFIG_NAME)) pages.add(Util.getMiniMessage(page)); + for (String page : config.getStringList(PAGES_CONFIG_NAME)) pages.add(Util.getItemMiniMessage(page)); if (pages.isEmpty()) return; diff --git a/core/src/main/java/com/nisovin/magicspells/util/magicitems/MagicItemDataParser.java b/core/src/main/java/com/nisovin/magicspells/util/magicitems/MagicItemDataParser.java index 7a0fd208b..6b5393294 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/magicitems/MagicItemDataParser.java +++ b/core/src/main/java/com/nisovin/magicspells/util/magicitems/MagicItemDataParser.java @@ -95,7 +95,7 @@ public static MagicItemData parseMagicItemData(String str) { switch (key.toLowerCase()) { case "name": - data.setAttribute(NAME, Util.getMiniMessage(value.getAsString())); + data.setAttribute(NAME, Util.getItemMiniMessage(value.getAsString())); break; case "amount": data.setAttribute(AMOUNT, value.getAsInt()); @@ -196,10 +196,10 @@ public static MagicItemData parseMagicItemData(String str) { data.setAttribute(SKULL_OWNER, value.getAsString()); break; case "title": - data.setAttribute(TITLE, Util.getMiniMessage(value.getAsString())); + data.setAttribute(TITLE, Util.getItemMiniMessage(value.getAsString())); break; case "author": - data.setAttribute(AUTHOR, Util.getMiniMessage(value.getAsString())); + data.setAttribute(AUTHOR, Util.getItemMiniMessage(value.getAsString())); break; case "uuid": String uuidString = value.getAsString(); @@ -272,7 +272,7 @@ public static MagicItemData parseMagicItemData(String str) { List lore = new ArrayList<>(); JsonArray jsonArray = value.getAsJsonArray(); for (JsonElement elementInside : jsonArray) { - lore.add(Util.getMiniMessage(elementInside.getAsString())); + lore.add(Util.getItemMiniMessage(elementInside.getAsString())); } if (!lore.isEmpty()) data.setAttribute(LORE, lore); @@ -283,7 +283,7 @@ public static MagicItemData parseMagicItemData(String str) { List pages = new ArrayList<>(); JsonArray pageArray = value.getAsJsonArray(); for (JsonElement page : pageArray) { - pages.add(Util.getMiniMessage(page.getAsString())); + pages.add(Util.getItemMiniMessage(page.getAsString())); } if (!pages.isEmpty()) data.setAttribute(PAGES, pages); diff --git a/core/src/main/resources/general.yml b/core/src/main/resources/general.yml index bd8fc02a4..193c55e86 100644 --- a/core/src/main/resources/general.yml +++ b/core/src/main/resources/general.yml @@ -7,7 +7,7 @@ terminate-effectlib-instances: true enable-error-logging: true enable-profiling: false error-log-limit: -1 -text-color: 3 +text-style: broadcast-range: 20 effectlib-instance-limit: 20000 ops-have-all-spells: true diff --git a/core/src/main/resources/mana.yml b/core/src/main/resources/mana.yml index 81218c487..bd4842f5b 100644 --- a/core/src/main/resources/mana.yml +++ b/core/src/main/resources/mana.yml @@ -3,8 +3,8 @@ enable-mana-system: true default-prefix: "Mana:" default-symbol: '=' default-size: 35 -default-color-full: 1 -default-color-empty: 0 +default-style-full: +default-style-empty: default-max-mana: 100 default-starting-mana: 100 @@ -26,8 +26,8 @@ ranks: starting-mana: 200 regen-amount: 10 regen-interval: 20 - color-full: 1 - color-empty: 0 + style-full: + style-empty: adept: prefix: "Mana:" symbol: '=' @@ -36,8 +36,8 @@ ranks: starting-mana: 150 regen-amount: 7 regen-interval: 20 - color-full: 1 - color-empty: 0 + style-full: + style-empty: novice: prefix: "Mana:" symbol: '=' @@ -46,5 +46,5 @@ ranks: starting-mana: 100 regen-amount: 5 regen-interval: 20 - color-full: 1 - color-empty: 0 + style-full: + style-empty: From 47c20e8d87058569935240208a251c63b3c067ba Mon Sep 17 00:00:00 2001 From: TonytheMacaroni Date: Fri, 28 Nov 2025 11:17:46 -0500 Subject: [PATCH 2/5] Apply review suggestions --- NOTICE.md | 6 +-- .../java/com/nisovin/magicspells/Spell.java | 7 ++- .../magicspells/commands/MagicCommands.java | 2 +- .../commands/ResetCooldownCommand.java | 1 - .../InvalidCommandArgumentException.java | 2 +- .../parsers/SpellCastArgumentsParser.java | 2 +- .../com/nisovin/magicspells/mana/ManaBar.java | 3 -- .../spells/command/AdminTeachSpell.java | 4 +- .../spells/command/ScrollSpell.java | 11 ++-- .../nisovin/magicspells/util/Rotation.java | 52 +------------------ .../com/nisovin/magicspells/util/TxtUtil.java | 5 -- .../com/nisovin/magicspells/util/Util.java | 33 +----------- core/src/main/resources/general.yml | 2 +- core/src/main/resources/mana.yml | 13 ++--- shop/build.gradle | 1 + 15 files changed, 24 insertions(+), 120 deletions(-) diff --git a/NOTICE.md b/NOTICE.md index d7a45a172..944630f67 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -28,9 +28,9 @@ The project includes software developed by third parties. For their full license - Licensed by: **Slikey** - License: **MIT License** (See `3rd_party_licenses/LICENSE-MIT`) -- Annotation Command Framework (ACF): - - Repository: `Chronoken/EffectLib` - - Licensed by: **Daniel Ennis and Contributors** +- Cloud Command Framework: + - Repository: `Incendo/cloud` + - Licensed by: **Alexander Söderberg** - License: **MIT License** (See `3rd_party_licenses/LICENSE-MIT`) - bStats: diff --git a/core/src/main/java/com/nisovin/magicspells/Spell.java b/core/src/main/java/com/nisovin/magicspells/Spell.java index 227a2e88b..e4fe926d3 100644 --- a/core/src/main/java/com/nisovin/magicspells/Spell.java +++ b/core/src/main/java/com/nisovin/magicspells/Spell.java @@ -1,7 +1,5 @@ package com.nisovin.magicspells; -import net.kyori.adventure.text.format.TextDecoration; -import net.kyori.adventure.text.format.TextDecorationAndState; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -1245,6 +1243,11 @@ protected boolean preCastTimeCheck(LivingEntity livingEntity, String[] args) { return true; } + @Deprecated(forRemoval = true) + public List tabComplete(CommandSender sender, String[] args) { + return null; + } + public SuggestionProvider suggestionProvider() { return this instanceof SuggestionProvider ? (SuggestionProvider) this : SuggestionProvider.noSuggestions(); } diff --git a/core/src/main/java/com/nisovin/magicspells/commands/MagicCommands.java b/core/src/main/java/com/nisovin/magicspells/commands/MagicCommands.java index f5e939b79..acf90ac9e 100644 --- a/core/src/main/java/com/nisovin/magicspells/commands/MagicCommands.java +++ b/core/src/main/java/com/nisovin/magicspells/commands/MagicCommands.java @@ -84,7 +84,7 @@ public static void register(@NotNull PaperCommandManager man if (fallbackMapping != null && fallbackMapping.mapper() != null) return fallbackMapping.mapper().apply(fallbackParser); - ArgumentTypeFactory fallbackFactory = defaultArgumentFactories.get(GenericTypeReflector.erase(fallbackParserClass)); + ArgumentTypeFactory fallbackFactory = defaultArgumentFactories.get(GenericTypeReflector.erase(fallbackParserClass)); if (fallbackFactory != null) return fallbackFactory.create(); diff --git a/core/src/main/java/com/nisovin/magicspells/commands/ResetCooldownCommand.java b/core/src/main/java/com/nisovin/magicspells/commands/ResetCooldownCommand.java index 0197b07e8..6df7e5cea 100644 --- a/core/src/main/java/com/nisovin/magicspells/commands/ResetCooldownCommand.java +++ b/core/src/main/java/com/nisovin/magicspells/commands/ResetCooldownCommand.java @@ -136,5 +136,4 @@ private static void resetCooldown(CommandContext context) { )); } - } diff --git a/core/src/main/java/com/nisovin/magicspells/commands/exceptions/InvalidCommandArgumentException.java b/core/src/main/java/com/nisovin/magicspells/commands/exceptions/InvalidCommandArgumentException.java index 0881b93a9..8406a8936 100644 --- a/core/src/main/java/com/nisovin/magicspells/commands/exceptions/InvalidCommandArgumentException.java +++ b/core/src/main/java/com/nisovin/magicspells/commands/exceptions/InvalidCommandArgumentException.java @@ -10,4 +10,4 @@ public InvalidCommandArgumentException(String message, Throwable cause) { super(message, cause); } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/nisovin/magicspells/commands/parsers/SpellCastArgumentsParser.java b/core/src/main/java/com/nisovin/magicspells/commands/parsers/SpellCastArgumentsParser.java index fb026b167..4baaec8ee 100644 --- a/core/src/main/java/com/nisovin/magicspells/commands/parsers/SpellCastArgumentsParser.java +++ b/core/src/main/java/com/nisovin/magicspells/commands/parsers/SpellCastArgumentsParser.java @@ -91,7 +91,7 @@ public static ParserDescriptor spellCastArgumentsP .suggestionProcessor() .process( CommandPreprocessingContext.of(context, input.cursor(preParseCursor)), - (Stream) StreamSupport.stream(result.spliterator(), false) + StreamSupport.stream(result.spliterator(), false) ) .toList(); diff --git a/core/src/main/java/com/nisovin/magicspells/mana/ManaBar.java b/core/src/main/java/com/nisovin/magicspells/mana/ManaBar.java index 0ab3ea048..5963476e9 100644 --- a/core/src/main/java/com/nisovin/magicspells/mana/ManaBar.java +++ b/core/src/main/java/com/nisovin/magicspells/mana/ManaBar.java @@ -1,8 +1,5 @@ package com.nisovin.magicspells.mana; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.Style; - import org.bukkit.Bukkit; import org.bukkit.entity.Player; diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/AdminTeachSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/AdminTeachSpell.java index 9803e68de..4a865e666 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/AdminTeachSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/AdminTeachSpell.java @@ -29,7 +29,7 @@ public class AdminTeachSpell extends CommandSpell { - private static final SuggestionProvider suggestions = AggregateParser + private static final SuggestionProvider SUGGESTIONS = AggregateParser .builder() .withComponent("player", PlayerParser.playerParser()) .withComponent("spells", VarargsParser.varargsParser(new SpellParser<>())) @@ -68,7 +68,7 @@ public boolean castFromConsole(CommandSender sender, String[] args) { @Override public SuggestionProvider suggestionProvider() { - return suggestions; + return SUGGESTIONS; } private static class AdminTeachTask extends BukkitRunnable { diff --git a/core/src/main/java/com/nisovin/magicspells/spells/command/ScrollSpell.java b/core/src/main/java/com/nisovin/magicspells/spells/command/ScrollSpell.java index c7a956682..f079990b2 100644 --- a/core/src/main/java/com/nisovin/magicspells/spells/command/ScrollSpell.java +++ b/core/src/main/java/com/nisovin/magicspells/spells/command/ScrollSpell.java @@ -249,19 +249,14 @@ public ItemStack createScroll(Spell spell, int uses, ItemStack item) { if (!(executor instanceof ConsoleCommandSender)) return Collections.emptyList(); - List suggestions = TxtUtil.tabCompletePlayerName(executor); CommandInput original = input.copy(); String playerName = input.readString(); - if (playerName.isEmpty() || input.isEmpty() && !input.input().endsWith(" ")) return suggestions; - - Player player = Bukkit.getPlayer(playerName); - if (player == null) return suggestions; + if (playerName.isEmpty() || input.isEmpty() || Bukkit.getPlayer(playerName) == null) + return TxtUtil.tabCompletePlayerName(executor); String diff = original.difference(input.skipWhitespace(), true); - SpellParser.suggest().stream().map(spell -> diff + spell).forEach(suggestions::add); - - return suggestions; + return SpellParser.suggest().stream().map(spell -> diff + spell).toList(); } @EventHandler(priority=EventPriority.MONITOR) diff --git a/core/src/main/java/com/nisovin/magicspells/util/Rotation.java b/core/src/main/java/com/nisovin/magicspells/util/Rotation.java index 938ddaacd..c0488489a 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/Rotation.java +++ b/core/src/main/java/com/nisovin/magicspells/util/Rotation.java @@ -2,22 +2,12 @@ import org.jetbrains.annotations.NotNull; -import java.util.Objects; - import org.bukkit.Location; /** * Represents a rotation that can be applied to a {@link Location}. */ -public final class Rotation { - - private final Angle yaw; - private final Angle pitch; - - private Rotation(final @NotNull Angle yaw, final @NotNull Angle pitch) { - this.yaw = yaw; - this.pitch = pitch; - } +public record Rotation(Angle yaw, Angle pitch) { /** * Create a new rotation object. @@ -30,24 +20,6 @@ public static Rotation of(final @NotNull Angle yaw, final @NotNull Angle pitch) return new Rotation(yaw, pitch); } - /** - * Returns the yaw of this rotation. - * - * @return yaw - */ - public Angle yaw() { - return this.yaw; - } - - /** - * Returns the pitch of this rotation - * - * @return pitch - */ - public Angle pitch() { - return this.pitch; - } - /** * Applies this rotation to a location. * @@ -60,26 +32,4 @@ public Angle pitch() { return location; } - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Rotation that = (Rotation) o; - return this.yaw.equals(that.yaw) && this.pitch.equals(that.pitch); - } - - @Override - public int hashCode() { - return Objects.hash(this.yaw, this.pitch); - } - - @Override - public String toString() { - return String.format("Rotation{yaw=%s, pitch=%s}", this.yaw, this.pitch); - } - } diff --git a/core/src/main/java/com/nisovin/magicspells/util/TxtUtil.java b/core/src/main/java/com/nisovin/magicspells/util/TxtUtil.java index 4189055c7..a0679196b 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/TxtUtil.java +++ b/core/src/main/java/com/nisovin/magicspells/util/TxtUtil.java @@ -83,9 +83,4 @@ public static List tabCompletePlayerName(CommandSender sender, boolean a return matches; } - public static String getPossessiveName(String name) { - name = name.trim(); - return name + "'" + (name.endsWith("s") ? "" : "s"); - } - } diff --git a/core/src/main/java/com/nisovin/magicspells/util/Util.java b/core/src/main/java/com/nisovin/magicspells/util/Util.java index 184b7dd5e..36f4110b7 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/Util.java +++ b/core/src/main/java/com/nisovin/magicspells/util/Util.java @@ -32,7 +32,6 @@ import org.bukkit.potion.PotionEffectType; import org.bukkit.configuration.ConfigurationSection; -import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -55,6 +54,7 @@ import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; @@ -819,37 +819,6 @@ public static NamedTextColor getLegacyColor(@Nullable String color, @NotNull Nam }; } - public static Style getStyle(@Nullable String style, @NotNull Style def) { - return switch (style) { - case "0" -> Style.style(NamedTextColor.BLACK); - case "1" -> Style.style(NamedTextColor.DARK_BLUE); - case "2" -> Style.style(NamedTextColor.DARK_GREEN); - case "3" -> Style.style(NamedTextColor.DARK_AQUA); - case "4" -> Style.style(NamedTextColor.DARK_RED); - case "5" -> Style.style(NamedTextColor.DARK_PURPLE); - case "6" -> Style.style(NamedTextColor.GOLD); - case "7" -> Style.style(NamedTextColor.GRAY); - case "8" -> Style.style(NamedTextColor.DARK_GRAY); - case "9" -> Style.style(NamedTextColor.BLUE); - case "a" -> Style.style(NamedTextColor.GREEN); - case "b" -> Style.style(NamedTextColor.AQUA); - case "c" -> Style.style(NamedTextColor.RED); - case "d" -> Style.style(NamedTextColor.LIGHT_PURPLE); - case "e" -> Style.style(NamedTextColor.YELLOW); - case "f" -> Style.style(NamedTextColor.WHITE); - case "k" -> Style.style(TextDecoration.OBFUSCATED); - case "l" -> Style.style(TextDecoration.BOLD); - case "m" -> Style.style(TextDecoration.STRIKETHROUGH); - case "n" -> Style.style(TextDecoration.UNDERLINED); - case "o" -> Style.style(TextDecoration.ITALIC); - case null -> def; - default -> { - Component component = MiniMessage.miniMessage().deserialize(style); - yield component.style(); - } - }; - } - public static String colorize(String string) { if (string.isEmpty()) return ""; diff --git a/core/src/main/resources/general.yml b/core/src/main/resources/general.yml index 193c55e86..f0d20540f 100644 --- a/core/src/main/resources/general.yml +++ b/core/src/main/resources/general.yml @@ -7,7 +7,7 @@ terminate-effectlib-instances: true enable-error-logging: true enable-profiling: false error-log-limit: -1 -text-style: +text-format: broadcast-range: 20 effectlib-instance-limit: 20000 ops-have-all-spells: true diff --git a/core/src/main/resources/mana.yml b/core/src/main/resources/mana.yml index bd4842f5b..c9c13ed37 100644 --- a/core/src/main/resources/mana.yml +++ b/core/src/main/resources/mana.yml @@ -1,10 +1,8 @@ enable-mana-system: true -default-prefix: "Mana:" +default-bar-format: "Mana: {} [/]" default-symbol: '=' default-size: 35 -default-style-full: -default-style-empty: default-max-mana: 100 default-starting-mana: 100 @@ -26,8 +24,7 @@ ranks: starting-mana: 200 regen-amount: 10 regen-interval: 20 - style-full: - style-empty: + bar-format: "Mana: {} [/]" adept: prefix: "Mana:" symbol: '=' @@ -36,8 +33,7 @@ ranks: starting-mana: 150 regen-amount: 7 regen-interval: 20 - style-full: - style-empty: + bar-format: "Mana: {} [/]" novice: prefix: "Mana:" symbol: '=' @@ -46,5 +42,4 @@ ranks: starting-mana: 100 regen-amount: 5 regen-interval: 20 - style-full: - style-empty: + bar-format: "Mana: {} [/]" diff --git a/shop/build.gradle b/shop/build.gradle index 98367db55..b581d17f6 100644 --- a/shop/build.gradle +++ b/shop/build.gradle @@ -1,5 +1,6 @@ dependencies { implementation project(":core") + implementation("org.incendo:cloud-paper:2.0.0-beta.13") implementation("net.milkbowl.vault:VaultAPI:1.7") { transitive = false } } From 788ec51dd8b2269cee8a695abc88cb6e1175d9ff Mon Sep 17 00:00:00 2001 From: TonytheMacaroni Date: Fri, 5 Dec 2025 21:11:06 -0500 Subject: [PATCH 3/5] Cleanup - Fix formatting of /ms taskinfo - Remove * literal in /ms variable modify - Remove unused Util#downloadFile - Cleanup /ms util command permissions --- .../com/nisovin/magicspells/MagicSpells.java | 4 +-- .../java/com/nisovin/magicspells/Perm.java | 3 --- .../magicspells/commands/TaskInfoCommand.java | 6 +++-- .../magicspells/commands/UtilCommands.java | 6 ++--- .../commands/VariableCommands.java | 11 -------- .../com/nisovin/magicspells/util/Util.java | 25 ------------------- 6 files changed, 8 insertions(+), 47 deletions(-) diff --git a/core/src/main/java/com/nisovin/magicspells/MagicSpells.java b/core/src/main/java/com/nisovin/magicspells/MagicSpells.java index f989bae73..c5e3dc3e9 100644 --- a/core/src/main/java/com/nisovin/magicspells/MagicSpells.java +++ b/core/src/main/java/com/nisovin/magicspells/MagicSpells.java @@ -943,9 +943,7 @@ private void initPermissions() { permissions.add(new Permission(Perm.COMMAND_VARIABLE_SHOW.getNode(), PermissionDefault.OP)); permissions.add(new Permission(Perm.COMMAND_VARIABLE_MODIFY.getNode(), PermissionDefault.OP)); permissions.add(new Permission(Perm.COMMAND_MAGIC_ITEM.getNode(), PermissionDefault.OP)); - permissions.add(new Permission(Perm.COMMAND_UTIL_DOWNLOAD.getNode(), PermissionDefault.OP)); - permissions.add(new Permission(Perm.COMMAND_UTIL_UPDATE.getNode(), PermissionDefault.OP)); - permissions.add(new Permission(Perm.COMMAND_UTIL_SAVE_SKIN.getNode(), PermissionDefault.OP)); + permissions.add(new Permission(Perm.COMMAND_UTIL_LIST_GOALS.getNode(), PermissionDefault.OP)); permissions.add(new Permission(Perm.COMMAND_PROFILE_REPORT.getNode(), PermissionDefault.OP)); permissions.add(new Permission(Perm.COMMAND_DEBUG.getNode(), PermissionDefault.OP)); permissions.add(new Permission(Perm.COMMAND_TASKINFO.getNode(), PermissionDefault.OP)); diff --git a/core/src/main/java/com/nisovin/magicspells/Perm.java b/core/src/main/java/com/nisovin/magicspells/Perm.java index 443a70fe5..746f9df88 100644 --- a/core/src/main/java/com/nisovin/magicspells/Perm.java +++ b/core/src/main/java/com/nisovin/magicspells/Perm.java @@ -40,9 +40,6 @@ public enum Perm implements Permission { COMMAND_VARIABLE_SHOW("magicspells.command.variable.show"), COMMAND_VARIABLE_MODIFY("magicspells.command.variable.modify"), COMMAND_MAGIC_ITEM("magicspells.command.magicitem"), - COMMAND_UTIL_DOWNLOAD("magicspells.command.util.download"), - COMMAND_UTIL_UPDATE("magicspells.command.util.update"), - COMMAND_UTIL_SAVE_SKIN("magicspells.command.util.saveskin"), COMMAND_UTIL_LIST_GOALS("magicspells.command.util.listgoals"), COMMAND_PROFILE_REPORT("magicspells.command.profilereport"), COMMAND_DEBUG("magicspells.command.debug"), diff --git a/core/src/main/java/com/nisovin/magicspells/commands/TaskInfoCommand.java b/core/src/main/java/com/nisovin/magicspells/commands/TaskInfoCommand.java index 9719b5692..6dad7b93f 100644 --- a/core/src/main/java/com/nisovin/magicspells/commands/TaskInfoCommand.java +++ b/core/src/main/java/com/nisovin/magicspells/commands/TaskInfoCommand.java @@ -36,13 +36,15 @@ private static void taskInfo(CommandContext context) { context.sender().getSender().sendMessage(Util.getMessageText( Component.text() .content("Tasks:") + .appendNewline() .append(Component.text(" * All - ").append(number(msTasks))) - .append(Component.text(" * EffectLib - ")).append(number(effectLibTasks)) + .appendNewline() + .append(Component.text(" * EffectLib - ").append(number(effectLibTasks))) )); } private static Component number(long number) { - return Component.text(number + "\n", number > 0 ? NamedTextColor.GREEN : NamedTextColor.GRAY); + return Component.text(number, number > 0 ? NamedTextColor.GREEN : NamedTextColor.GRAY); } } diff --git a/core/src/main/java/com/nisovin/magicspells/commands/UtilCommands.java b/core/src/main/java/com/nisovin/magicspells/commands/UtilCommands.java index 32c2333b7..ce919aded 100644 --- a/core/src/main/java/com/nisovin/magicspells/commands/UtilCommands.java +++ b/core/src/main/java/com/nisovin/magicspells/commands/UtilCommands.java @@ -59,7 +59,7 @@ private static void listGoals(CommandContext context) { if (!(entity instanceof Mob mob)) throw new InvalidCommandArgumentException("Invalid entity specified - target is not a mob"); - Collection> goals = Bukkit.getMobGoals().getAllGoals(mob); + Collection> goals = Bukkit.getMobGoals().getAllGoals(mob); if (goals.isEmpty()) { sender.sendMessage(Util.getMessageText(Component.text("Target has no goals"))); return; @@ -70,8 +70,8 @@ private static void listGoals(CommandContext context) { .append(entity.name().hoverEvent(entity)) .append(Component.text(":")); - for (Goal goal : goals) { - GoalKey key = goal.getKey(); + for (Goal<@NotNull Mob> goal : goals) { + GoalKey<@NotNull Mob> key = goal.getKey(); message.appendNewline(); message.append(Component.text(" - " + key.getEntityClass().getSimpleName() + ": ")); diff --git a/core/src/main/java/com/nisovin/magicspells/commands/VariableCommands.java b/core/src/main/java/com/nisovin/magicspells/commands/VariableCommands.java index d4a0a3253..40fa526a1 100644 --- a/core/src/main/java/com/nisovin/magicspells/commands/VariableCommands.java +++ b/core/src/main/java/com/nisovin/magicspells/commands/VariableCommands.java @@ -62,17 +62,6 @@ static void register(@NotNull PaperCommandManager manager) { .argument(variableComponent.description(Description.of("The variable to modify."))) .permission(Perm.COMMAND_VARIABLE_MODIFY); - manager.command(modify - .literal("*") - .required( - VARIABLE_MOD_KEY, - StringParser.greedyStringParser(), - Description.of("A variable modifier.") - ) - .meta(HelpCommand.FILTER_FROM_HELP, true) - .handler(VariableCommands::modify) - ); - manager.command(modify .literal("-") .required(VARIABLE_MOD_KEY, StringParser.greedyStringParser()) diff --git a/core/src/main/java/com/nisovin/magicspells/util/Util.java b/core/src/main/java/com/nisovin/magicspells/util/Util.java index 36f4110b7..ccab84aa1 100644 --- a/core/src/main/java/com/nisovin/magicspells/util/Util.java +++ b/core/src/main/java/com/nisovin/magicspells/util/Util.java @@ -1,13 +1,5 @@ package com.nisovin.magicspells.util; -import java.io.File; -import java.io.FileOutputStream; - -import java.net.URL; -import java.net.URI; -import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; - import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -538,23 +530,6 @@ public static Vector getVectorToTarget(Location origin, Location target) { return target.toVector().subtract(origin.toVector()); } - public static boolean downloadFile(String url, File file) { - try { - URL website = URI.create(url).toURL(); - ReadableByteChannel rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(file); - - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - - return true; - } catch (Exception e) { - e.printStackTrace(); - return false; - } - } - private static final Map uniqueIds = new HashMap<>(); public static String getUniqueId(Player player) { From f45e0ef3648f1986a6f7f96a958264b1326c44ba Mon Sep 17 00:00:00 2001 From: TonytheMacaroni Date: Sat, 6 Dec 2025 18:38:22 -0500 Subject: [PATCH 4/5] Add in entity scheduler and global region scheduler task counts to /ms taskinfo --- .../magicspells/commands/TaskInfoCommand.java | 11 +++- .../volatilecode/ManagerVolatile.java | 6 ++ .../volatilecode/VolatileCodeDisabled.java | 10 +++ .../latest/VolatileCodeLatest.java | 65 +++++++++++++++++++ .../volatilecode/VolatileCodeHandle.java | 4 ++ .../volatilecode/VolatileCodeHelper.java | 3 + .../v1_21_10/VolatileCode_v1_21_10.java | 64 ++++++++++++++++++ 7 files changed, 162 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/nisovin/magicspells/commands/TaskInfoCommand.java b/core/src/main/java/com/nisovin/magicspells/commands/TaskInfoCommand.java index 6dad7b93f..1e39949c1 100644 --- a/core/src/main/java/com/nisovin/magicspells/commands/TaskInfoCommand.java +++ b/core/src/main/java/com/nisovin/magicspells/commands/TaskInfoCommand.java @@ -14,6 +14,7 @@ import com.nisovin.magicspells.Perm; import com.nisovin.magicspells.util.Util; import com.nisovin.magicspells.MagicSpells; +import com.nisovin.magicspells.volatilecode.VolatileCodeHandle; public class TaskInfoCommand { @@ -33,17 +34,25 @@ private static void taskInfo(CommandContext context) { int effectLibTasks = MagicSpells.getEffectManager().getEffects().size(); + VolatileCodeHandle handler = MagicSpells.getVolatileCodeHandler(); + MagicSpells instance = MagicSpells.getInstance(); + context.sender().getSender().sendMessage(Util.getMessageText( Component.text() .content("Tasks:") .appendNewline() - .append(Component.text(" * All - ").append(number(msTasks))) + .append(Component.text(" * Bukkit Scheduler - ").append(number(msTasks))) + .appendNewline() + .append(Component.text(" * Entity Scheduler - ").append(number(handler.countEntitySchedulerTasks()))) + .appendNewline() + .append(Component.text(" * Global Region Scheduler - ").append(number(handler.countGlobalRegionSchedulerTasks()))) .appendNewline() .append(Component.text(" * EffectLib - ").append(number(effectLibTasks))) )); } private static Component number(long number) { + if (number < 0) return Component.text("?", NamedTextColor.RED); return Component.text(number, number > 0 ? NamedTextColor.GREEN : NamedTextColor.GRAY); } diff --git a/core/src/main/java/com/nisovin/magicspells/volatilecode/ManagerVolatile.java b/core/src/main/java/com/nisovin/magicspells/volatilecode/ManagerVolatile.java index 218642ba2..1e9a1f259 100644 --- a/core/src/main/java/com/nisovin/magicspells/volatilecode/ManagerVolatile.java +++ b/core/src/main/java/com/nisovin/magicspells/volatilecode/ManagerVolatile.java @@ -5,6 +5,7 @@ import org.bukkit.Bukkit; import org.bukkit.event.Listener; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; import com.nisovin.magicspells.MagicSpells; import com.nisovin.magicspells.volatilecode.latest.VolatileCodeLatest; @@ -41,6 +42,11 @@ public YamlConfiguration getMainConfig() { return MagicSpells.getMagicConfig().getMainConfig(); } + @Override + public Plugin getPlugin() { + return MagicSpells.getInstance(); + } + }; public static VolatileCodeHandle constructVolatileCodeHandler() { diff --git a/core/src/main/java/com/nisovin/magicspells/volatilecode/VolatileCodeDisabled.java b/core/src/main/java/com/nisovin/magicspells/volatilecode/VolatileCodeDisabled.java index 5e3e487f4..913c897f1 100644 --- a/core/src/main/java/com/nisovin/magicspells/volatilecode/VolatileCodeDisabled.java +++ b/core/src/main/java/com/nisovin/magicspells/volatilecode/VolatileCodeDisabled.java @@ -71,4 +71,14 @@ public GlowManager getGlowManager() { return null; } + @Override + public long countGlobalRegionSchedulerTasks() { + return -1; + } + + @Override + public long countEntitySchedulerTasks() { + return -1; + } + } diff --git a/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileCodeLatest.java b/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileCodeLatest.java index 7971af410..05be57b4e 100644 --- a/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileCodeLatest.java +++ b/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileCodeLatest.java @@ -3,6 +3,11 @@ import java.util.*; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.invoke.VarHandle; +import java.util.function.Consumer; +import java.lang.invoke.MethodHandles; + +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import org.bukkit.World; import org.bukkit.Bukkit; @@ -10,6 +15,7 @@ import org.bukkit.util.Vector; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; import org.bukkit.entity.LivingEntity; import org.bukkit.inventory.ItemStack; import org.bukkit.event.entity.ExplosionPrimeEvent; @@ -28,6 +34,9 @@ import io.papermc.paper.adventure.PaperAdventure; import io.papermc.paper.advancement.AdvancementDisplay; +import io.papermc.paper.threadedregions.EntityScheduler; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler; import com.nisovin.magicspells.util.glow.GlowManager; import com.nisovin.magicspells.volatilecode.VolatileCodeHandle; @@ -38,6 +47,7 @@ import net.minecraft.advancements.*; import net.minecraft.world.phys.Vec3; import net.minecraft.resources.Identifier; +import net.minecraft.server.MinecraftServer; import net.minecraft.world.entity.EntityType; import net.minecraft.network.protocol.game.*; import net.minecraft.server.level.ServerLevel; @@ -60,6 +70,11 @@ public class VolatileCodeLatest extends VolatileCodeHandle { private final EntityDataAccessor<@NotNull Byte> DATA_SHARED_FLAGS_ID; private final Method UPDATE_EFFECT_PARTICLES; + private final Long2ObjectOpenHashMap> GLOBAL_REGION_TASKS; + private final VarHandle CURRENTLY_EXECUTING_HANDLE; + private final VarHandle ONE_TIME_DELAYED_HANDLE; + private final VarHandle RUN_HANDLE; + @SuppressWarnings("unchecked") public VolatileCodeLatest(VolatileCodeHelper helper) throws Exception { super(helper); @@ -80,6 +95,18 @@ public VolatileCodeLatest(VolatileCodeHelper helper) throws Exception { UPDATE_EFFECT_PARTICLES = nmsEntityClass.getDeclaredMethod("updateSynchronizedMobEffectParticles"); UPDATE_EFFECT_PARTICLES.setAccessible(true); + + VarHandle tasksByDeadlineHandle = MethodHandles.privateLookupIn(FoliaGlobalRegionScheduler.class, MethodHandles.lookup()) + .findVarHandle(FoliaGlobalRegionScheduler.class, "tasksByDeadline", Long2ObjectOpenHashMap.class); + GLOBAL_REGION_TASKS = (Long2ObjectOpenHashMap>) tasksByDeadlineHandle.get(Bukkit.getGlobalRegionScheduler()); + + MethodHandles.Lookup privateLookup = MethodHandles.privateLookupIn(EntityScheduler.class, MethodHandles.lookup()); + + CURRENTLY_EXECUTING_HANDLE = privateLookup.findVarHandle(EntityScheduler.class, "currentlyExecuting", ArrayDeque.class); + ONE_TIME_DELAYED_HANDLE = privateLookup.findVarHandle(EntityScheduler.class, "oneTimeDelayed", Long2ObjectOpenHashMap.class); + + Class scheduledTaskClass = privateLookup.findClass("io.papermc.paper.threadedregions.EntityScheduler$ScheduledTask"); + RUN_HANDLE = privateLookup.findVarHandle(scheduledTaskClass, "run", Consumer.class); } @Override @@ -225,4 +252,42 @@ public GlowManager getGlowManager() { return new VolatileGlowManagerLatest(helper); } + @Override + public long countGlobalRegionSchedulerTasks() { + Plugin plugin = helper.getPlugin(); + + return GLOBAL_REGION_TASKS.values().stream() + .flatMap(List::stream) + .filter(task -> task.getOwningPlugin() == plugin) + .count(); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public long countEntitySchedulerTasks() { + EntityScheduler.EntitySchedulerTickList entitySchedulerTickList = MinecraftServer.getServer().entitySchedulerTickList; + EntityScheduler[] schedulers = entitySchedulerTickList.getAllSchedulers(); + Plugin plugin = helper.getPlugin(); + + int count = 0; + for (EntityScheduler scheduler : schedulers) { + Long2ObjectOpenHashMap oneTimeDelayed = (Long2ObjectOpenHashMap) ONE_TIME_DELAYED_HANDLE.get(scheduler); + ArrayDeque currentlyExecuting = (ArrayDeque) CURRENTLY_EXECUTING_HANDLE.get(scheduler); + + for (List taskList : oneTimeDelayed.values()) { + for (Object taskObject : taskList) { + ScheduledTask task = (ScheduledTask) (Consumer) RUN_HANDLE.get(taskObject); + if (task.getOwningPlugin() == plugin) count++; + } + } + + for (Object taskObject : currentlyExecuting) { + ScheduledTask task = (ScheduledTask) (Consumer) RUN_HANDLE.get(taskObject); + if (task.getOwningPlugin() == plugin) count++; + } + } + + return count; + } + } diff --git a/nms/shared/src/main/java/com/nisovin/magicspells/volatilecode/VolatileCodeHandle.java b/nms/shared/src/main/java/com/nisovin/magicspells/volatilecode/VolatileCodeHandle.java index 8dc620fa0..c32ba68a4 100644 --- a/nms/shared/src/main/java/com/nisovin/magicspells/volatilecode/VolatileCodeHandle.java +++ b/nms/shared/src/main/java/com/nisovin/magicspells/volatilecode/VolatileCodeHandle.java @@ -40,4 +40,8 @@ public VolatileCodeHandle(VolatileCodeHelper helper) { public abstract GlowManager getGlowManager(); + public abstract long countGlobalRegionSchedulerTasks(); + + public abstract long countEntitySchedulerTasks(); + } diff --git a/nms/shared/src/main/java/com/nisovin/magicspells/volatilecode/VolatileCodeHelper.java b/nms/shared/src/main/java/com/nisovin/magicspells/volatilecode/VolatileCodeHelper.java index debc99fdb..7218af447 100644 --- a/nms/shared/src/main/java/com/nisovin/magicspells/volatilecode/VolatileCodeHelper.java +++ b/nms/shared/src/main/java/com/nisovin/magicspells/volatilecode/VolatileCodeHelper.java @@ -2,6 +2,7 @@ import org.bukkit.event.Listener; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; public interface VolatileCodeHelper { @@ -15,4 +16,6 @@ public interface VolatileCodeHelper { YamlConfiguration getMainConfig(); + Plugin getPlugin(); + } diff --git a/nms/v1_21_10/src/main/java/com/nisovin/magicspells/volatilecode/v1_21_10/VolatileCode_v1_21_10.java b/nms/v1_21_10/src/main/java/com/nisovin/magicspells/volatilecode/v1_21_10/VolatileCode_v1_21_10.java index f3cf3882c..02ba81727 100644 --- a/nms/v1_21_10/src/main/java/com/nisovin/magicspells/volatilecode/v1_21_10/VolatileCode_v1_21_10.java +++ b/nms/v1_21_10/src/main/java/com/nisovin/magicspells/volatilecode/v1_21_10/VolatileCode_v1_21_10.java @@ -3,6 +3,11 @@ import java.util.*; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.invoke.VarHandle; +import java.util.function.Consumer; +import java.lang.invoke.MethodHandles; + +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import org.bukkit.World; import org.bukkit.Bukkit; @@ -10,6 +15,7 @@ import org.bukkit.util.Vector; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; import org.bukkit.entity.LivingEntity; import org.bukkit.inventory.ItemStack; import org.bukkit.event.entity.ExplosionPrimeEvent; @@ -26,6 +32,9 @@ import io.papermc.paper.adventure.PaperAdventure; import io.papermc.paper.advancement.AdvancementDisplay; +import io.papermc.paper.threadedregions.EntityScheduler; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler; import com.nisovin.magicspells.util.glow.GlowManager; import com.nisovin.magicspells.volatilecode.VolatileCodeHandle; @@ -35,6 +44,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.advancements.*; import net.minecraft.world.phys.Vec3; +import net.minecraft.server.MinecraftServer; import net.minecraft.world.entity.EntityType; import net.minecraft.network.protocol.game.*; import net.minecraft.server.level.ServerLevel; @@ -58,6 +68,11 @@ public class VolatileCode_v1_21_10 extends VolatileCodeHandle { private final EntityDataAccessor DATA_SHARED_FLAGS_ID; private final Method UPDATE_EFFECT_PARTICLES; + private final Long2ObjectOpenHashMap> GLOBAL_REGION_TASKS; + private final VarHandle CURRENTLY_EXECUTING_HANDLE; + private final VarHandle ONE_TIME_DELAYED_HANDLE; + private final VarHandle RUN_HANDLE; + @SuppressWarnings("unchecked") public VolatileCode_v1_21_10(VolatileCodeHelper helper) throws Exception { super(helper); @@ -78,6 +93,17 @@ public VolatileCode_v1_21_10(VolatileCodeHelper helper) throws Exception { UPDATE_EFFECT_PARTICLES = nmsEntityClass.getDeclaredMethod("updateSynchronizedMobEffectParticles"); UPDATE_EFFECT_PARTICLES.setAccessible(true); + + VarHandle tasksByDeadlineHandle = MethodHandles.privateLookupIn(FoliaGlobalRegionScheduler.class, MethodHandles.lookup()).findVarHandle(FoliaGlobalRegionScheduler.class, "tasksByDeadline", Long2ObjectOpenHashMap.class); + GLOBAL_REGION_TASKS = (Long2ObjectOpenHashMap>) tasksByDeadlineHandle.get(Bukkit.getGlobalRegionScheduler()); + + MethodHandles.Lookup privateLookup = MethodHandles.privateLookupIn(EntityScheduler.class, MethodHandles.lookup()); + + CURRENTLY_EXECUTING_HANDLE = privateLookup.findVarHandle(EntityScheduler.class, "currentlyExecuting", ArrayDeque.class); + ONE_TIME_DELAYED_HANDLE = privateLookup.findVarHandle(EntityScheduler.class, "oneTimeDelayed", Long2ObjectOpenHashMap.class); + + Class scheduledTaskClass = privateLookup.findClass("io.papermc.paper.threadedregions.EntityScheduler$ScheduledTask"); + RUN_HANDLE = privateLookup.findVarHandle(scheduledTaskClass, "run", Consumer.class); } @Override @@ -223,4 +249,42 @@ public GlowManager getGlowManager() { return new VolatileGlowManager_v1_21_10(helper); } + @Override + public long countGlobalRegionSchedulerTasks() { + Plugin plugin = helper.getPlugin(); + + return GLOBAL_REGION_TASKS.values().stream() + .flatMap(List::stream) + .filter(task -> task.getOwningPlugin() == plugin) + .count(); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public long countEntitySchedulerTasks() { + EntityScheduler.EntitySchedulerTickList entitySchedulerTickList = MinecraftServer.getServer().entitySchedulerTickList; + EntityScheduler[] schedulers = entitySchedulerTickList.getAllSchedulers(); + Plugin plugin = helper.getPlugin(); + + int count = 0; + for (EntityScheduler scheduler : schedulers) { + Long2ObjectOpenHashMap oneTimeDelayed = (Long2ObjectOpenHashMap) ONE_TIME_DELAYED_HANDLE.get(scheduler); + ArrayDeque currentlyExecuting = (ArrayDeque) CURRENTLY_EXECUTING_HANDLE.get(scheduler); + + for (List taskList : oneTimeDelayed.values()) { + for (Object taskObject : taskList) { + ScheduledTask task = (ScheduledTask) (Consumer) RUN_HANDLE.get(taskObject); + if (task.getOwningPlugin() == plugin) count++; + } + } + + for (Object taskObject : currentlyExecuting) { + ScheduledTask task = (ScheduledTask) (Consumer) RUN_HANDLE.get(taskObject); + if (task.getOwningPlugin() == plugin) count++; + } + } + + return count; + } + } From f96a3c2d15ccde3733eaacea003c39511034efb9 Mon Sep 17 00:00:00 2001 From: TonytheMacaroni Date: Wed, 10 Dec 2025 13:45:37 -0500 Subject: [PATCH 5/5] Cleanup reflection --- .../latest/VolatileCodeLatest.java | 45 +++++++++---------- .../v1_21_10/VolatileCode_v1_21_10.java | 44 +++++++++--------- 2 files changed, 42 insertions(+), 47 deletions(-) diff --git a/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileCodeLatest.java b/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileCodeLatest.java index 05be57b4e..b624d5c79 100644 --- a/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileCodeLatest.java +++ b/nms/latest/src/main/java/com/nisovin/magicspells/volatilecode/latest/VolatileCodeLatest.java @@ -1,10 +1,10 @@ package com.nisovin.magicspells.volatilecode.latest; import java.util.*; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.lang.invoke.VarHandle; import java.util.function.Consumer; +import java.lang.invoke.MethodType; +import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -68,7 +68,7 @@ public class VolatileCodeLatest extends VolatileCodeHandle { private final EntityDataAccessor<@NotNull List> DATA_EFFECT_PARTICLES; private final EntityDataAccessor<@NotNull Boolean> DATA_EFFECT_AMBIENCE_ID; private final EntityDataAccessor<@NotNull Byte> DATA_SHARED_FLAGS_ID; - private final Method UPDATE_EFFECT_PARTICLES; + private final MethodHandle UPDATE_EFFECT_PARTICLES; private final Long2ObjectOpenHashMap> GLOBAL_REGION_TASKS; private final VarHandle CURRENTLY_EXECUTING_HANDLE; @@ -79,34 +79,31 @@ public class VolatileCodeLatest extends VolatileCodeHandle { public VolatileCodeLatest(VolatileCodeHelper helper) throws Exception { super(helper); - Field dataSharedFlagsIdField = net.minecraft.world.entity.Entity.class.getDeclaredField("DATA_SHARED_FLAGS_ID"); - dataSharedFlagsIdField.setAccessible(true); - DATA_SHARED_FLAGS_ID = (EntityDataAccessor<@NotNull Byte>) dataSharedFlagsIdField.get(null); + MethodHandles.Lookup lookup = MethodHandles.lookup(); - Class nmsEntityClass = net.minecraft.world.entity.LivingEntity.class; + Class leClass = net.minecraft.world.entity.LivingEntity.class; + Class eClass = net.minecraft.world.entity.Entity.class; - Field dataEffectParticlesField = nmsEntityClass.getDeclaredField("DATA_EFFECT_PARTICLES"); - dataEffectParticlesField.setAccessible(true); - DATA_EFFECT_PARTICLES = (EntityDataAccessor<@NotNull List>) dataEffectParticlesField.get(null); + DATA_SHARED_FLAGS_ID = (EntityDataAccessor<@NotNull Byte>) lookup + .findStaticVarHandle(eClass, "DATA_SHARED_FLAGS_ID", EntityDataAccessor.class).get(); - Field dataEffectAmbienceIdField = nmsEntityClass.getDeclaredField("DATA_EFFECT_AMBIENCE_ID"); - dataEffectAmbienceIdField.setAccessible(true); - DATA_EFFECT_AMBIENCE_ID = (EntityDataAccessor<@NotNull Boolean>) dataEffectAmbienceIdField.get(null); + DATA_EFFECT_PARTICLES = (EntityDataAccessor<@NotNull List>) lookup + .findStaticVarHandle(leClass, "DATA_EFFECT_PARTICLES", EntityDataAccessor.class).get(); - UPDATE_EFFECT_PARTICLES = nmsEntityClass.getDeclaredMethod("updateSynchronizedMobEffectParticles"); - UPDATE_EFFECT_PARTICLES.setAccessible(true); + DATA_EFFECT_AMBIENCE_ID = (EntityDataAccessor<@NotNull Boolean>) lookup + .findStaticVarHandle(leClass, "DATA_EFFECT_AMBIENCE_ID", EntityDataAccessor.class).get(); - VarHandle tasksByDeadlineHandle = MethodHandles.privateLookupIn(FoliaGlobalRegionScheduler.class, MethodHandles.lookup()) - .findVarHandle(FoliaGlobalRegionScheduler.class, "tasksByDeadline", Long2ObjectOpenHashMap.class); - GLOBAL_REGION_TASKS = (Long2ObjectOpenHashMap>) tasksByDeadlineHandle.get(Bukkit.getGlobalRegionScheduler()); + UPDATE_EFFECT_PARTICLES = lookup.findVirtual(leClass, "updateSynchronizedMobEffectParticles", MethodType.methodType(void.class)); - MethodHandles.Lookup privateLookup = MethodHandles.privateLookupIn(EntityScheduler.class, MethodHandles.lookup()); + GLOBAL_REGION_TASKS = (Long2ObjectOpenHashMap>) lookup + .findVarHandle(FoliaGlobalRegionScheduler.class, "tasksByDeadline", Long2ObjectOpenHashMap.class) + .get(Bukkit.getGlobalRegionScheduler()); - CURRENTLY_EXECUTING_HANDLE = privateLookup.findVarHandle(EntityScheduler.class, "currentlyExecuting", ArrayDeque.class); - ONE_TIME_DELAYED_HANDLE = privateLookup.findVarHandle(EntityScheduler.class, "oneTimeDelayed", Long2ObjectOpenHashMap.class); + CURRENTLY_EXECUTING_HANDLE = lookup.findVarHandle(EntityScheduler.class, "currentlyExecuting", ArrayDeque.class); + ONE_TIME_DELAYED_HANDLE = lookup.findVarHandle(EntityScheduler.class, "oneTimeDelayed", Long2ObjectOpenHashMap.class); - Class scheduledTaskClass = privateLookup.findClass("io.papermc.paper.threadedregions.EntityScheduler$ScheduledTask"); - RUN_HANDLE = privateLookup.findVarHandle(scheduledTaskClass, "run", Consumer.class); + Class scheduledTaskClass = lookup.findClass("io.papermc.paper.threadedregions.EntityScheduler$ScheduledTask"); + RUN_HANDLE = lookup.findVarHandle(scheduledTaskClass, "run", Consumer.class); } @Override @@ -125,7 +122,7 @@ public void addPotionGraphicalEffect(LivingEntity entity, int color, long durati helper.scheduleDelayedTask(() -> { try { UPDATE_EFFECT_PARTICLES.invoke(nmsEntity); - } catch (Exception e) { + } catch (Throwable e) { e.printStackTrace(); } }, duration); diff --git a/nms/v1_21_10/src/main/java/com/nisovin/magicspells/volatilecode/v1_21_10/VolatileCode_v1_21_10.java b/nms/v1_21_10/src/main/java/com/nisovin/magicspells/volatilecode/v1_21_10/VolatileCode_v1_21_10.java index 02ba81727..600ad13fb 100644 --- a/nms/v1_21_10/src/main/java/com/nisovin/magicspells/volatilecode/v1_21_10/VolatileCode_v1_21_10.java +++ b/nms/v1_21_10/src/main/java/com/nisovin/magicspells/volatilecode/v1_21_10/VolatileCode_v1_21_10.java @@ -1,10 +1,10 @@ package com.nisovin.magicspells.volatilecode.v1_21_10; import java.util.*; -import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.lang.invoke.VarHandle; import java.util.function.Consumer; +import java.lang.invoke.MethodType; +import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -66,7 +66,7 @@ public class VolatileCode_v1_21_10 extends VolatileCodeHandle { private final EntityDataAccessor> DATA_EFFECT_PARTICLES; private final EntityDataAccessor DATA_EFFECT_AMBIENCE_ID; private final EntityDataAccessor DATA_SHARED_FLAGS_ID; - private final Method UPDATE_EFFECT_PARTICLES; + private final MethodHandle UPDATE_EFFECT_PARTICLES; private final Long2ObjectOpenHashMap> GLOBAL_REGION_TASKS; private final VarHandle CURRENTLY_EXECUTING_HANDLE; @@ -77,33 +77,31 @@ public class VolatileCode_v1_21_10 extends VolatileCodeHandle { public VolatileCode_v1_21_10(VolatileCodeHelper helper) throws Exception { super(helper); - Field dataSharedFlagsIdField = net.minecraft.world.entity.Entity.class.getDeclaredField("DATA_SHARED_FLAGS_ID"); - dataSharedFlagsIdField.setAccessible(true); - DATA_SHARED_FLAGS_ID = (EntityDataAccessor) dataSharedFlagsIdField.get(null); + MethodHandles.Lookup lookup = MethodHandles.lookup(); - Class nmsEntityClass = net.minecraft.world.entity.LivingEntity.class; + Class leClass = net.minecraft.world.entity.LivingEntity.class; + Class eClass = net.minecraft.world.entity.Entity.class; - Field dataEffectParticlesField = nmsEntityClass.getDeclaredField("DATA_EFFECT_PARTICLES"); - dataEffectParticlesField.setAccessible(true); - DATA_EFFECT_PARTICLES = (EntityDataAccessor>) dataEffectParticlesField.get(null); + DATA_SHARED_FLAGS_ID = (EntityDataAccessor) lookup + .findStaticVarHandle(eClass, "DATA_SHARED_FLAGS_ID", EntityDataAccessor.class).get(); - Field dataEffectAmbienceIdField = nmsEntityClass.getDeclaredField("DATA_EFFECT_AMBIENCE_ID"); - dataEffectAmbienceIdField.setAccessible(true); - DATA_EFFECT_AMBIENCE_ID = (EntityDataAccessor) dataEffectAmbienceIdField.get(null); + DATA_EFFECT_PARTICLES = (EntityDataAccessor>) lookup + .findStaticVarHandle(leClass, "DATA_EFFECT_PARTICLES", EntityDataAccessor.class).get(); - UPDATE_EFFECT_PARTICLES = nmsEntityClass.getDeclaredMethod("updateSynchronizedMobEffectParticles"); - UPDATE_EFFECT_PARTICLES.setAccessible(true); + DATA_EFFECT_AMBIENCE_ID = (EntityDataAccessor) lookup + .findStaticVarHandle(leClass, "DATA_EFFECT_AMBIENCE_ID", EntityDataAccessor.class).get(); - VarHandle tasksByDeadlineHandle = MethodHandles.privateLookupIn(FoliaGlobalRegionScheduler.class, MethodHandles.lookup()).findVarHandle(FoliaGlobalRegionScheduler.class, "tasksByDeadline", Long2ObjectOpenHashMap.class); - GLOBAL_REGION_TASKS = (Long2ObjectOpenHashMap>) tasksByDeadlineHandle.get(Bukkit.getGlobalRegionScheduler()); + UPDATE_EFFECT_PARTICLES = lookup.findVirtual(leClass, "updateSynchronizedMobEffectParticles", MethodType.methodType(void.class)); - MethodHandles.Lookup privateLookup = MethodHandles.privateLookupIn(EntityScheduler.class, MethodHandles.lookup()); + GLOBAL_REGION_TASKS = (Long2ObjectOpenHashMap>) lookup + .findVarHandle(FoliaGlobalRegionScheduler.class, "tasksByDeadline", Long2ObjectOpenHashMap.class) + .get(Bukkit.getGlobalRegionScheduler()); - CURRENTLY_EXECUTING_HANDLE = privateLookup.findVarHandle(EntityScheduler.class, "currentlyExecuting", ArrayDeque.class); - ONE_TIME_DELAYED_HANDLE = privateLookup.findVarHandle(EntityScheduler.class, "oneTimeDelayed", Long2ObjectOpenHashMap.class); + CURRENTLY_EXECUTING_HANDLE = lookup.findVarHandle(EntityScheduler.class, "currentlyExecuting", ArrayDeque.class); + ONE_TIME_DELAYED_HANDLE = lookup.findVarHandle(EntityScheduler.class, "oneTimeDelayed", Long2ObjectOpenHashMap.class); - Class scheduledTaskClass = privateLookup.findClass("io.papermc.paper.threadedregions.EntityScheduler$ScheduledTask"); - RUN_HANDLE = privateLookup.findVarHandle(scheduledTaskClass, "run", Consumer.class); + Class scheduledTaskClass = lookup.findClass("io.papermc.paper.threadedregions.EntityScheduler$ScheduledTask"); + RUN_HANDLE = lookup.findVarHandle(scheduledTaskClass, "run", Consumer.class); } @Override @@ -122,7 +120,7 @@ public void addPotionGraphicalEffect(LivingEntity entity, int color, long durati helper.scheduleDelayedTask(() -> { try { UPDATE_EFFECT_PARTICLES.invoke(nmsEntity); - } catch (Exception e) { + } catch (Throwable e) { e.printStackTrace(); } }, duration);