fixed custom commands (using 3.22.5 code), fixes plugin not loading on spigot servers, fixes folia. Still bugs with logic refreshing, inventory items in multi-panel get stuck when server shuts down and starts again, placeholders not parsing when using %cp- in some cases not resolved

This commit is contained in:
rockyhawk64 2025-10-08 21:53:41 +11:00
parent 3060b92661
commit aebcf51cc2
8 changed files with 113 additions and 199 deletions

View File

@ -3,6 +3,7 @@ main: me.rockyhawk.commandpanels.CommandPanels
name: CommandPanels
author: RockyHawk
api-version: '1.13'
folia-supported: true
description: Fully Custom GUIs. Make your Server Professional.
softdepend: [Essentials, PlaceholderAPI, Vault, HeadDatabase, TokenManager, VotingPlugin, MMOItems, ChestSort, floodgate, Skript]
commands:

View File

@ -153,7 +153,6 @@ public class Context {
//setup class files
setupEconomy();
Bukkit.getServer().getMessenger().registerOutgoingPluginChannel(plugin, "BungeeCord");
Bukkit.getServer().getMessenger().registerOutgoingPluginChannel(plugin, "velocity:main");
plugin.getCommand("commandpanel").setExecutor(new PanelCommand(this));
plugin.getCommand("commandpanel").setTabCompleter(new PanelTabComplete(this));
@ -186,6 +185,7 @@ public class Context {
Bukkit.getServer().getPluginManager().registerEvents(new InteractionHandler(this), plugin);
Bukkit.getServer().getPluginManager().registerEvents(inventorySaver, plugin);
Bukkit.getServer().getPluginManager().registerEvents(inputUtils, plugin);
Bukkit.getServer().getPluginManager().registerEvents(openCommands, plugin);
Bukkit.getServer().getPluginManager().registerEvents(new SessionUtils(this), plugin);
Bukkit.getServer().getPluginManager().registerEvents(generator, plugin);
Bukkit.getServer().getPluginManager().registerEvents(new DroppedItemHandler(this), plugin);

View File

@ -1,80 +0,0 @@
package me.rockyhawk.commandpanels.commands.opencommands;
import me.rockyhawk.commandpanels.Context;
import me.rockyhawk.commandpanels.api.Panel;
import me.rockyhawk.commandpanels.manager.session.PanelPosition;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class BaseCommandPanel extends Command {
private final Context ctx;
private final Panel panel;
private final List<String> subcommandPatterns;
public BaseCommandPanel(Context ctx, Panel panel, String name, List<String> subcommandPatterns) {
super(name);
this.ctx = ctx;
this.panel = panel;
this.subcommandPatterns = subcommandPatterns;
}
@Override
public boolean execute(@NotNull CommandSender sender, @NotNull String label, @NotNull String[] args) {
if (!(sender instanceof Player)) {
sender.sendMessage("This command can only be used by players.");
return true;
}
Player player = (Player) sender;
// If no args and empty pattern allowed
if (args.length == 0 && subcommandPatterns.contains("")) {
panel.copy().open(player, PanelPosition.Top);
return true;
}
String[] phEnds = ctx.placeholders.getPlaceholderEnds(panel, true);
// Try to match each subcommand pattern
for (String pattern : subcommandPatterns) {
if (pattern.isEmpty() && args.length == 0) {
panel.copy().open(player, PanelPosition.Top);
return true;
}
String[] patternParts = pattern.split(" ");
if (patternParts.length != args.length) {
continue; // length mismatch
}
Panel openPanel = panel.copy();
boolean matched = true;
for (int i = 0; i < patternParts.length; i++) {
String expected = patternParts[i];
String actual = args[i];
if (expected.startsWith(phEnds[0]) && expected.endsWith(phEnds[1])) {
// Placeholder found: extract key without phEnds
String key = expected.substring(phEnds[0].length(), expected.length() - phEnds[1].length());
openPanel.placeholders.addPlaceholder(key, actual);
} else if (!expected.equalsIgnoreCase(actual)) {
matched = false;
break;
}
}
if (matched) {
openPanel.open(player, PanelPosition.Top);
return true;
}
}
// No pattern matched
return false;
}
}

View File

@ -1,115 +1,120 @@
package me.rockyhawk.commandpanels.commands.opencommands;
import com.google.common.collect.Maps;
import me.rockyhawk.commandpanels.Context;
import me.rockyhawk.commandpanels.api.Panel;
import me.rockyhawk.commandpanels.manager.session.PanelPosition;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.entity.Player;
import java.lang.reflect.Field;
import org.bukkit.ChatColor;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class OpenCommands {
private final Context ctx;
private final Map<String, Command> commands = Maps.newHashMap();
private final Map<String, Command> knownCommands = getKnownCommands();
private final CommandMap commandMap = getCommandMap();
private Map<String, Command> getKnownCommands() {
try {
Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands");
knownCommandsField.setAccessible(true);
return (Map<String, Command>) knownCommandsField.get(getCommandMap());
} catch (Exception e) {
throw new RuntimeException("Could not get known commands", e);
}
}
public class OpenCommands implements Listener {
Context ctx;
public OpenCommands(Context pl) {
this.ctx = pl;
if(ctx.version.isAtLeast("1.13")){
Bukkit.getServer().getPluginManager().registerEvents(new PriorityHandler(this), ctx.plugin);
}
}
private void unloadCommands() {
commands.forEach((commandName, command) -> unregisterCommand(command));
}
public void unregisterCommand(Command command) {
// For custom commands that are used to open panels
@EventHandler
public void PlayerCommand(PlayerCommandPreprocessEvent e) {
try {
command.getAliases().forEach(knownCommands::remove);
knownCommands.remove(command.getName());
command.unregister(commandMap);
} catch (Exception ignored) {}
}
for (Panel panel : ctx.plugin.panelList) {
if (panel.getConfig().contains("commands")) {
List<String> panelCommands = panel.getConfig().getStringList("commands");
for(String cmd : panelCommands){
if(cmd.equalsIgnoreCase(e.getMessage().replace("/", ""))){
e.setCancelled(true);
panel.copy().open(e.getPlayer(), PanelPosition.Top);
return;
}
private void loadCommands() {
ctx.plugin.panelList.forEach(panel -> {
if (!panel.getConfig().contains("commands")) {
return;
}
boolean correctCommand = true;
ArrayList<String[]> placeholders = new ArrayList<>(); //should read placeholder,argument
String[] phEnds = ctx.placeholders.getPlaceholderEnds(panel,true); //start and end of placeholder
String[] command = cmd.split("\\s");
String[] message = e.getMessage().replace("/", "").split("\\s"); //command split into args
List<String> panelCommands = panel.getConfig().getStringList("commands");
if (panelCommands.isEmpty()) {
return;
}
if(command.length != message.length){
continue;
}
// Group commands by their root command (first word)
Map<String, List<String>> commandsByRoot = new HashMap<>();
for(int i = 0; i < cmd.split("\\s").length; i++){
if(command[i].startsWith(phEnds[0])){
placeholders.add(new String[]{command[i].replace(phEnds[0],"").replace(phEnds[1],""), message[i]});
}else if(!command[i].equals(message[i])){
correctCommand = false;
}
}
for (String fullCommand : panelCommands) {
String[] parts = fullCommand.split(" ", 2);
String rootCommand = parts[0]; // e.g. "home"
String subcommand = parts.length > 1 ? parts[1] : "";
commandsByRoot.computeIfAbsent(rootCommand, k -> new ArrayList<>()).add(subcommand);
}
// For each root command, create and register a BaseCommandPanel
for (Map.Entry<String, List<String>> entry : commandsByRoot.entrySet()) {
String rootCommand = entry.getKey();
List<String> subcommands = entry.getValue();
Command command = new BaseCommandPanel(ctx, panel, rootCommand, subcommands);
// Unregister old command if present
Command existing = knownCommands.remove(rootCommand);
if (existing != null) {
existing.unregister(commandMap);
if(correctCommand){
e.setCancelled(true);
Panel openPanel = panel.copy();
for(String[] placeholder : placeholders){
openPanel.placeholders.addPlaceholder(placeholder[0],placeholder[1]);
}
openPanel.open(e.getPlayer(),PanelPosition.Top);
return;
}
}
}
commandMap.register(command.getName(), "commandpanels", command);
commands.put(command.getName(), command);
}
});
}
private CommandMap getCommandMap() {
try {
final Class<? extends Server> serverClass = Bukkit.getServer().getClass();
final Field commandMapField = serverClass.getDeclaredField("commandMap");
commandMapField.setAccessible(true);
return (CommandMap) commandMapField.get(Bukkit.getServer());
} catch (Exception e) {
throw new RuntimeException("Unable to get command map", e);
}catch(NullPointerException exc){
//this is placed to prevent null exceptions if the commandpanels reload command has file changes
ctx.debug.send(exc,e.getPlayer(), ctx);
}
}
//this will require a server restart for new commands
public void registerCommands(){
if (ctx.version.isBelow("1.13")) return;
unloadCommands();
if (!ctx.configHandler.isTrue("config.auto-register-commands")) {
File commandsLoc = new File("commands.yml");
YamlConfiguration cmdCF;
try {
cmdCF = YamlConfiguration.loadConfiguration(commandsLoc);
}catch(Exception e){
//could not access the commands.yml file
ctx.debug.send(e,null, ctx);
return;
}
loadCommands();
Bukkit.getOnlinePlayers().forEach(Player::updateCommands);
//remove old commandpanels commands
for(String existingCommands : cmdCF.getConfigurationSection("aliases").getKeys(false)){
try {
if (cmdCF.getStringList("aliases." + existingCommands).get(0).equals("commandpanel")) {
cmdCF.set("aliases." + existingCommands, null);
}
}catch(Exception ignore){}
}
//make the command 'commandpanels' to identify it
ArrayList<String> temp = new ArrayList<>();
temp.add("commandpanel");
for (Panel panel : ctx.plugin.panelList) {
if(panel.getConfig().contains("panelType")){
if(panel.getConfig().getStringList("panelType").contains("nocommandregister")){
continue;
}
}
if(panel.getConfig().contains("commands")){
List<String> panelCommands = panel.getConfig().getStringList("commands");
for(String command : panelCommands){
cmdCF.set("aliases." + command.split("\\s")[0],temp);
}
}
}
try {
cmdCF.save(commandsLoc);
} catch (IOException var10) {
Bukkit.getConsoleSender().sendMessage("[CommandPanels]" + ChatColor.RED + " WARNING: Could not register custom commands!");
}
}
}
}

View File

@ -1,21 +0,0 @@
package me.rockyhawk.commandpanels.commands.opencommands;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerLoadEvent;
public class PriorityHandler implements Listener {
private OpenCommands openCommands;
public PriorityHandler(OpenCommands ext) {
openCommands = ext;
}
// This listener will register commands after the server loads so that it can properly override other plugins
@EventHandler
public void onServerLoad(ServerLoadEvent e) {
openCommands.registerCommands();
}
}

View File

@ -2,6 +2,7 @@ package me.rockyhawk.commandpanels.interaction.commands;
import me.rockyhawk.commandpanels.Context;
import me.rockyhawk.commandpanels.api.Panel;
import me.rockyhawk.commandpanels.formatter.MiniMessage;
import me.rockyhawk.commandpanels.interaction.commands.paywalls.*;
import me.rockyhawk.commandpanels.interaction.commands.paywalls.itempaywall.ItemPaywall;
import me.rockyhawk.commandpanels.interaction.commands.tags.*;
@ -98,7 +99,16 @@ public class CommandRunner {
tags.add(new EnchantTag());
tags.add(new EvalDelayTag());
tags.add(new GiveItemTag());
tags.add(new MiniMessageTag());
try {
// Check all the minimessage classes exist before loading
Class.forName("net.kyori.adventure.text.Component");
Class.forName("net.kyori.adventure.text.format.TextDecoration");
Class.forName("net.kyori.adventure.text.minimessage.MiniMessage");
Class.forName("net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer");
tags.add(new MiniMessageTag());
} catch (ClassNotFoundException ignore) {
// Do not load minimessage on Spigot
}
tags.add(new OpenTag());
tags.add(new PlaceholderTag());
tags.add(new RefreshTag());

View File

@ -8,7 +8,6 @@ import org.bukkit.Sound;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitRunnable;
public class RefreshTask implements Runnable {
private final Context ctx;
@ -85,7 +84,7 @@ public class RefreshTask implements Runnable {
}
public void start() {
this.task = ctx.scheduler.runTaskTimerForEntity(player, this, 1, 1);
this.task = ctx.scheduler.runRepeatingForLocation(player.getLocation(), this, refreshDelay);
}
public void stop() {

View File

@ -25,7 +25,7 @@ public class SchedulerAdapter {
private boolean isFoliaServer() {
try {
Class.forName("io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler");
return false; // Force folia feature disabled until bugs fixed
return true;
} catch (ClassNotFoundException e) {
return false;
}
@ -96,13 +96,13 @@ public class SchedulerAdapter {
/**
* Runs a task synchronously for a specific location with a delay
*/
public BukkitTask runTaskLaterForLocation(Location location, Runnable task, long delay) {
public BukkitTask runRepeatingForLocation(Location location, Runnable task, long delay) {
if (isFolia) {
// Folia's RegionScheduler.runDelayed() uses ticks, not milliseconds!
Bukkit.getRegionScheduler().runDelayed(plugin, location, scheduledTask -> task.run(), delay);
Bukkit.getRegionScheduler().runAtFixedRate(plugin, location, scheduledTask -> task.run(), delay, delay);
return new DummyBukkitTask();
} else {
return Bukkit.getScheduler().runTaskLater(plugin, task, delay);
return Bukkit.getScheduler().runTaskTimer(plugin, task, delay, delay);
}
}