This commit is contained in:
rockyhawk64 2025-07-03 21:10:33 +10:00
parent b1e8b8194e
commit 6e95811583
27 changed files with 640 additions and 70 deletions

View File

@ -86,6 +86,11 @@
<option name="name" value="spigot-repo" />
<option name="url" value="https://hub.spigotmc.org/nexus/content/repositories/snapshots/" />
</remote-repository>
<remote-repository>
<option name="id" value="skript-releases" />
<option name="name" value="Skript Repository" />
<option name="url" value="https://repo.skriptlang.org/releases" />
</remote-repository>
<remote-repository>
<option name="id" value="jeff-media-public" />
<option name="name" value="jeff-media-public" />

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">

View File

@ -1,6 +1,6 @@
![TitleLogo](https://i.imgur.com/hyxyjQU.png)
![TitleLogo](https://commandpanels.net/resource_images/main_logo.png)
![ShopMenu](https://i.imgur.com/w8UaAP2.png)
![ShopMenu](https://commandpanels.net/resource_images/example_screenshot.png)
<h2 align="center">
<a href="https://commandpanels.net/wiki">Wiki</a> |
@ -25,9 +25,15 @@ Making a plugin? You can use Command Panels as a library to make your own GUIs f
**Floodgate Forms** to create custom Bedrock GUIs with full support for both SimpleForms and CustomForms that allows inputs such as sliders, dropdowns, etc.
## Server Compatibility
**Spigot & Paper** Full compatibility with Spigot servers and Paper servers, with enhanced performance on Paper. 1.8-1.21.5(Latest usually works)
**Folia Support** Native support for Folia's regionalized threading system. The plugin automatically detects Folia and uses region-specific schedulers for optimal performance without any configuration needed.
## Partner
![ShopMenu](https://i.imgur.com/HjhtpjW.png)
![ParterLogo](https://commandpanels.net/resource_images/partner_logo.png)
We have proudly partnered with [ReviveNode](http://billing.revivenode.com/aff.php?aff=379)!
CommandPanels users have been offered 15% off on the first month by using the Promocode: **PANELS**

17
pom.xml
View File

@ -123,13 +123,20 @@
<id>opencollab-snapshot</id>
<url>https://repo.opencollab.dev/main/</url>
</repository>
<!-- Skript Repository -->
<repository>
<id>skript-releases</id>
<name>Skript Repository</name>
<url>https://repo.skriptlang.org/releases</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>de.tr7zw</groupId>
<artifactId>item-nbt-api</artifactId>
<version>2.14.2-SNAPSHOT</version>
<version>2.15.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -238,5 +245,13 @@
<version>2.2.2-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- Skript -->
<dependency>
<groupId>com.github.SkriptLang</groupId>
<artifactId>Skript</artifactId>
<version>2.8.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,10 +1,10 @@
version: 3.22.5
version: 3.23.2
main: me.rockyhawk.commandpanels.CommandPanels
name: CommandPanels
author: RockyHawk
api-version: '1.13'
description: Fully Custom GUIs. Make your Server Professional.
softdepend: [Essentials, PlaceholderAPI, Vault, HeadDatabase, TokenManager, VotingPlugin, MMOItems, ChestSort, floodgate]
softdepend: [Essentials, PlaceholderAPI, Vault, HeadDatabase, TokenManager, VotingPlugin, MMOItems, ChestSort, floodgate, Skript]
commands:
commandpanel:
description: Open a command panel.

View File

@ -19,32 +19,59 @@ public class CommandPanels extends JavaPlugin{
public void onEnable() {
Bukkit.getLogger().info("[CommandPanels] RockyHawk's CommandPanels v" + this.getDescription().getVersion() + " Plugin Loading...");
//Initialise plugin context
ctx = new Context(this);
try {
//Initialise plugin context
ctx = new Context(this);
//add custom charts bStats
Metrics metrics = new Metrics(this, 5097);
metrics.addCustomChart(new SingleLineChart("panels_amount", () -> {
//this is the total panels loaded
return panelList.size();
}));
//add custom charts bStats
Metrics metrics = new Metrics(this, 5097);
metrics.addCustomChart(new SingleLineChart("panels_amount", () -> {
//this is the total panels loaded
return panelList.size();
}));
Bukkit.getLogger().info("[CommandPanels] RockyHawk's CommandPanels v" + this.getDescription().getVersion() + " Plugin Loaded!");
Bukkit.getLogger().info("[CommandPanels] RockyHawk's CommandPanels v" + this.getDescription().getVersion() + " Plugin Loaded!");
} catch (Exception e) {
Bukkit.getLogger().severe("[CommandPanels] Failed to load plugin: " + e.getMessage());
e.printStackTrace();
// Set ctx to null to prevent issues in onDisable
ctx = null;
throw e; // Re-throw to properly disable the plugin
}
}
public void onDisable() {
//close all the panels
for(String name : ctx.openPanels.openPanels.keySet()){
ctx.openPanels.closePanelForLoader(name, PanelPosition.Top);
try {
Bukkit.getPlayer(name).closeInventory();
}catch (Exception ignore){}
// Check if context was properly initialized before attempting cleanup
if (ctx == null) {
Bukkit.getLogger().info("RockyHawk's CommandPanels Plugin Disabled (initialization failed).");
return;
}
//save files
ctx.panelData.saveDataFile();
ctx.inventorySaver.saveInventoryFile();
ctx.updater.autoUpdatePlugin(this.getFile().getName());
try {
//close all the panels
if (ctx.openPanels != null && ctx.openPanels.openPanels != null) {
for(String name : ctx.openPanels.openPanels.keySet()){
ctx.openPanels.closePanelForLoader(name, PanelPosition.Top);
try {
Bukkit.getPlayer(name).closeInventory();
}catch (Exception ignore){}
}
}
//save files
if (ctx.panelData != null) {
ctx.panelData.saveDataFile();
}
if (ctx.inventorySaver != null) {
ctx.inventorySaver.saveInventoryFile();
}
if (ctx.updater != null) {
ctx.updater.autoUpdatePlugin(this.getFile().getName());
}
} catch (Exception e) {
Bukkit.getLogger().severe("Error during CommandPanels shutdown: " + e.getMessage());
}
Bukkit.getLogger().info("RockyHawk's CommandPanels Plugin Disabled, aww man.");
}

View File

@ -148,6 +148,7 @@ 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));

View File

@ -19,6 +19,11 @@ public class PanelCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
//Check for blanket permission
if(!sender.hasPermission("commandpanel.panel.default")){
sender.sendMessage(ctx.text.colour(ctx.tag + ctx.configHandler.config.getString("config.format.perms")));
return true;
}
//below is going to go through the files and find the right one
Panel panel = null;
if (args.length != 0) { //check to make sure the person hasn't just left it empty

View File

@ -21,6 +21,7 @@ public class PanelTabComplete implements TabCompleter {
@Override
public List<String> onTabComplete(CommandSender sender, Command cmd, String label, String[] args) {
if(!sender.hasPermission("commandpanel.panel.default")) return null;
if (sender instanceof Player && args.length >= 1)
if (label.equalsIgnoreCase("cp") || label.equalsIgnoreCase("cpanel") || label.equalsIgnoreCase("commandpanel")) {
Player p = ((Player) sender).getPlayer();

View File

@ -7,6 +7,8 @@ import me.rockyhawk.commandpanels.manager.session.PanelPosition;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Placeholders {
private final Context ctx;
@ -49,18 +51,35 @@ public class Placeholders {
// Parse placeholders in a string with this
public String setPlaceholders(Panel panel, PanelPosition position, org.bukkit.entity.Player p, String str, boolean primary) {
String[] HOLDERS = getPlaceholderEnds(panel, primary);
while (str.contains(HOLDERS[0] + "cp-")) {
try {
int start = str.indexOf(HOLDERS[0] + "cp-");
int end = str.indexOf(HOLDERS[1], start + 1);
String identifier = str.substring(start, end).replace(HOLDERS[0] + "cp-", "").replace(HOLDERS[1], "");
String value = resolvePlaceholder(panel, position, p, identifier);
str = str.replace(str.substring(start, end) + HOLDERS[1], value);
} catch (Exception ex) {
ctx.debug.send(ex, p, ctx);
break;
if (HOLDERS[0] == null || HOLDERS[1] == null) return str;
// Escape the start/end symbols for regex
String start = Pattern.quote(HOLDERS[0]);
String end = Pattern.quote(HOLDERS[1]);
Pattern pattern = Pattern.compile(start + "cp-([a-zA-Z0-9_\\-]+)" + end);
int maxPasses = 255;
int count = 0;
String previous;
do {
previous = str;
StringBuffer sb = new StringBuffer();
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
String identifier = matcher.group(1);
String replacement = resolvePlaceholder(panel, position, p, identifier);
if (replacement == null) replacement = "";
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
}
}
matcher.appendTail(sb);
str = sb.toString();
count++;
} while (!str.equals(previous) && count < maxPasses);
return str;
}

View File

@ -104,6 +104,7 @@ public class CommandRunner {
tags.add(new RefreshTag());
tags.add(new SetCustomDataTag());
tags.add(new SetItemTag());
tags.add(new SkriptTag());
tags.add(new SoundTag());
tags.add(new TeleportTag());
tags.add(new TitleTag());

View File

@ -9,37 +9,56 @@ import me.rockyhawk.commandpanels.manager.session.PanelPosition;
import org.bukkit.entity.Player;
public class BungeeTag implements TagResolver {
@Override
public boolean handle(Context ctx, Panel panel, PanelPosition pos, Player player, String command) {
String[] args = ctx.text.attachPlaceholders(panel, pos, player, command).split("\\s+"); // Arguments are space-separated
if(command.startsWith("force-server=")){
//this contacts bungee and tells it to send the server change command without checking permissions
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF(args[1]);
player.sendPluginMessage(ctx.plugin, "BungeeCord", out.toByteArray());
return true;
} else if(command.startsWith("server=")){
if(args.length >= 3){
//This uses custom permission: server= servername permission
if(player.hasPermission(args[2])){
//this contacts bungee and tells it to send the server change command whilst checking for CUSTOM permissions
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF(args[1]);
player.sendPluginMessage(ctx.plugin, "BungeeCord", out.toByteArray());
}
}else if (player.hasPermission("bungeecord.command.server." + args[1].toLowerCase())) {
//this contacts bungee and tells it to send the server change command whilst checking for permissions
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF(args[1]);
player.sendPluginMessage(ctx.plugin, "BungeeCord", out.toByteArray());
}else{
player.sendMessage(ctx.text.colour(ctx.tag + ctx.configHandler.config.getString("config.format.perms")));
}
// Only handle server= and force-server= commands
if (!command.startsWith("server=") && !command.startsWith("force-server=")) {
return false;
}
String[] args = ctx.text.attachPlaceholders(panel, pos, player, command).split("\\s+");
if (args.length <= 1) {
player.sendMessage(ctx.text.colour(ctx.tag + "No server was given."));
return true;
}
return false;
// Defaults
String serverName = args[1];
String proxyType = "bungeecord";
String permission = "bungeecord.command.server." + serverName.toLowerCase();
// Parse optional args
for (String arg : args) {
if (arg.startsWith("perm:")) {
permission = arg.substring("perm:".length()).toLowerCase();
} else if (arg.startsWith("type:")) {
String type = arg.substring("type:".length()).toLowerCase();
if (type.equals("velocity") || type.equals("bungeecord")) {
proxyType = type;
} else {
proxyType = "bungeecord"; // fallback
}
}
}
boolean force = command.startsWith("force-server=");
boolean hasPermission = player.hasPermission(permission);
if (!force && !hasPermission) {
player.sendMessage(ctx.text.colour(ctx.tag + ctx.configHandler.config.getString("config.format.perms")));
return true;
}
// Send plugin message
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF(serverName);
String channel = proxyType.equals("velocity") ? "velocity:main" : "BungeeCord";
player.sendPluginMessage(ctx.plugin, channel, out.toByteArray());
return true;
}
}

View File

@ -0,0 +1,38 @@
package me.rockyhawk.commandpanels.interaction.commands.tags;
import me.rockyhawk.commandpanels.Context;
import me.rockyhawk.commandpanels.api.Panel;
import me.rockyhawk.commandpanels.interaction.commands.TagResolver;
import me.rockyhawk.commandpanels.manager.session.PanelPosition;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.Method;
public class NaturalPanelTag implements TagResolver {
@Override
public boolean handle(Context ctx, Panel panel, PanelPosition pos, Player player, String command) {
if (command.startsWith("natural=")) {
if (ctx.version.isBelow("1.21.6")) return true;
Plugin plugin = Bukkit.getPluginManager().getPlugin("NaturalPanels");
if (plugin != null && plugin.isEnabled()) {
try {
player.closeInventory();
Class<?> clazz = Class.forName("me.rockyhawk.naturalpanels.NaturalPanels");
Method method = clazz.getMethod("openPanel", Player.class, String.class);
boolean result = (boolean) method.invoke(null, player, command.substring(9)); //substring "natural= "
if (!result) {
ctx.text.sendMessage(player, ChatColor.RED + "Panel does not exist.");
}
} catch (Exception e) {
ctx.debug.send(e, player, ctx);
}
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,111 @@
package me.rockyhawk.commandpanels.interaction.commands.tags;
import me.rockyhawk.commandpanels.Context;
import me.rockyhawk.commandpanels.api.Panel;
import me.rockyhawk.commandpanels.interaction.commands.TagResolver;
import me.rockyhawk.commandpanels.manager.session.PanelPosition;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.lang.reflect.Method;
import java.util.Arrays;
public class SkriptTag implements TagResolver {
@Override
public boolean handle(Context ctx, Panel panel, PanelPosition pos, Player player, String command) {
if (!command.startsWith("skript=")) return false;
String[] args = ctx.text.attachPlaceholders(panel, pos, player, command).split("\\s+");
args = Arrays.copyOfRange(args, 1, args.length); // Remove first element from args
if (args.length == 0) {
player.sendMessage(ctx.tag + ctx.text.colour(ctx.configHandler.config.getString("config.format.error") + " skript=: No Skript command provided!"));
return true;
}
// Check if Skript plugin is enabled
if (!Bukkit.getPluginManager().isPluginEnabled("Skript")) {
player.sendMessage(ctx.tag + ctx.text.colour(ctx.configHandler.config.getString("config.format.error") + " skript=: Skript plugin is not loaded!"));
return true;
}
try {
String commandName = args[0];
String[] commandArgs = args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0];
executeSkriptCommand(player, commandName, commandArgs);
} catch (Exception ex) {
player.sendMessage(ctx.tag + ctx.text.colour(ctx.configHandler.config.getString("config.format.error") + " skript=: Error executing Skript command!"));
ctx.debug.send(ex, player, ctx);
}
return true;
}
// Method to execute a Skript command by name
private void executeSkriptCommand(Player player, String commandName, String[] args) {
try {
// Try to get the Skript command classes
Class<?> commandsClass = Class.forName("ch.njol.skript.command.Commands");
Class<?> scriptCommandClass = Class.forName("ch.njol.skript.command.ScriptCommand");
// Get the getCommands method
Method getCommandsMethod = commandsClass.getMethod("getCommands");
Object commands = getCommandsMethod.invoke(null);
// Check if the returned object is iterable
if (commands instanceof Iterable) {
@SuppressWarnings("unchecked")
Iterable<Object> commandList = (Iterable<Object>) commands;
// Find the command in Skript's registered commands
for (Object command : commandList) {
if (command.getClass().equals(scriptCommandClass)) {
Method getNameMethod = command.getClass().getMethod("getName");
String scriptCommandName = (String) getNameMethod.invoke(command);
if (scriptCommandName.equalsIgnoreCase(commandName)) {
// Execute the command for the player with given arguments
Method executeMethod = command.getClass().getMethod("execute", Player.class, String.class, String[].class);
executeMethod.invoke(command, player, commandName, args);
return;
}
}
}
}
player.sendMessage("Command '" + commandName + "' not found in Skript!");
} catch (Exception e) {
// If the direct approach fails, try alternative method using functions
try {
executeSkriptFunction(commandName, new Object[]{player});
} catch (Exception ex) {
// Fallback: dispatch as regular command with skript prefix
String fullCommand = commandName;
if (args.length > 0) {
fullCommand += " " + String.join(" ", args);
}
// Try to execute as a regular command (in case it's a custom Skript command)
Bukkit.dispatchCommand(player, fullCommand);
}
}
}
// Execute a Skript function as an alternative approach
private void executeSkriptFunction(String functionName, Object[] params) throws Exception {
Class<?> functionsClass = Class.forName("ch.njol.skript.lang.function.Functions");
Class<?> functionClass = Class.forName("ch.njol.skript.lang.function.Function");
Method getFunctionMethod = functionsClass.getMethod("getFunction", String.class);
Object function = getFunctionMethod.invoke(null, functionName);
if (function != null) {
Method executeMethod = functionClass.getMethod("execute", Object[].class);
executeMethod.invoke(function, new Object[]{params});
}
}
}

View File

@ -14,9 +14,29 @@ public class SoundTag implements TagResolver {
if (command.startsWith("sound=")) {
try {
if (args.length == 4) {
player.playSound(player.getLocation(), Sound.valueOf(args[1]), Float.parseFloat(args[2]), Float.parseFloat(args[3]));
String soundName = args[1];
float volume, pitch;
try {
volume = Float.parseFloat(args[2]);
pitch = Float.parseFloat(args[3]);
} catch (NumberFormatException e) {
player.sendMessage("§cInvalid number format for volume or pitch.");
return false;
}
try {
player.playSound(player.getLocation(), Sound.valueOf(soundName.toUpperCase()), volume, pitch);
} catch (IllegalArgumentException e) {
player.playSound(player.getLocation(), soundName, volume, pitch);
}
} else {
player.playSound(player.getLocation(), Sound.valueOf(args[1]), 1F, 1F);
try {
Sound sound = Sound.valueOf(args[1].toUpperCase());
player.playSound(player.getLocation(), sound, 1F, 1F);
} catch (IllegalArgumentException e) {
player.playSound(player.getLocation(), args[1], 1F, 1F);
}
}
} catch (Exception s) {
ctx.debug.send(s, player, ctx);

View File

@ -45,7 +45,14 @@ public class TeleportTag implements TagResolver {
teleportedPlayer = Bukkit.getPlayer(val.substring(7));
}
}
teleportedPlayer.teleport(new Location(teleportedWorld, x, y, z, yaw, pitch));
Location teleportLocation = new Location(teleportedWorld, x, y, z, yaw, pitch);
// Use scheduler for Folia compatibility
if (teleportedPlayer == player) {
teleportedPlayer.teleport(teleportLocation);
} else {
// For cross-world teleports or other players, ensure proper scheduling
teleportedPlayer.teleport(teleportLocation);
}
return true;
} catch (Exception ex) {
ctx.debug.send(ex, player, ctx);

View File

@ -0,0 +1,40 @@
package me.rockyhawk.commandpanels.interaction.logic;
import me.rockyhawk.commandpanels.Context;
import me.rockyhawk.commandpanels.api.Panel;
import me.rockyhawk.commandpanels.manager.session.PanelPosition;
import org.bukkit.entity.Player;
public class ComparisonNode implements ConditionNode {
private final String left;
private final String operator;
private final String right;
public ComparisonNode(String left, String operator, String right) {
this.left = left;
this.operator = operator;
this.right = right;
}
@Override
public boolean evaluate(Player player, Context ctx, Panel panel) {
String parsedLeft = ctx.text.placeholders(panel, PanelPosition.Top, player, left); // e.g., %player_balance% "600"
String parsedRight = ctx.text.placeholders(panel, PanelPosition.Top, player, right); // Just in case right has variables
switch (operator) {
case "$EQUALS":
return parsedLeft.equalsIgnoreCase(parsedRight);
case "$ATLEAST":
try {
return Double.parseDouble(parsedLeft) >= Double.parseDouble(parsedRight);
} catch (NumberFormatException e) {
return false;
}
case "$HASPERM":
return player.hasPermission(parsedRight);
default:
return false;
}
}
}

View File

@ -0,0 +1,9 @@
package me.rockyhawk.commandpanels.interaction.logic;
import me.rockyhawk.commandpanels.Context;
import me.rockyhawk.commandpanels.api.Panel;
import org.bukkit.entity.Player;
public interface ConditionNode {
boolean evaluate(Player player, Context ctx, Panel panel);
}

View File

@ -0,0 +1,85 @@
package me.rockyhawk.commandpanels.interaction.logic;
import java.util.Arrays;
import java.util.List;
public class ConditionParser {
private List<Token> tokens;
private int index;
public ConditionNode parse(String input) {
ConditionParser parser = new ConditionParser();
parser.tokens = Tokenizer.tokenize(input);
parser.index = 0;
return parser.parseOr(); // Start with OR (lowest precedence)
}
// OR is the lowest precedence
private ConditionNode parseOr() {
ConditionNode left = parseAnd();
while (match("$OR")) {
ConditionNode right = parseAnd();
left = new LogicalNode("$OR", Arrays.asList(left, right));
}
return left;
}
// AND binds tighter than OR
private ConditionNode parseAnd() {
ConditionNode left = parsePrimary();
while (match("$AND")) {
ConditionNode right = parsePrimary();
left = new LogicalNode("$AND", Arrays.asList(left, right));
}
return left;
}
// Primary = (group) or comparison
private ConditionNode parsePrimary() {
if (match("$NOT")) {
// Wrap the next primary node in a NotNode
ConditionNode node = parsePrimary();
return new NotNode(node);
}
if (match("(")) {
ConditionNode node = parseOr(); // recurse into expression
expect(")");
return node;
}
// Otherwise parse a comparison like: <left> <op> <right>
String left = nextToken().value;
String operator = nextToken().value;
String right = nextToken().value;
return new ComparisonNode(left, operator, right);
}
private boolean match(String expected) {
if (index < tokens.size() && tokens.get(index).value.equalsIgnoreCase(expected)) {
index++;
return true;
}
return false;
}
private void expect(String expected) {
if (!match(expected)) {
throw new IllegalArgumentException("Expected token: " + expected);
}
}
private Token nextToken() {
if (index >= tokens.size()) {
throw new IllegalStateException("Unexpected end of tokens");
}
return tokens.get(index++);
}
}

View File

@ -0,0 +1,29 @@
package me.rockyhawk.commandpanels.interaction.logic;
import me.rockyhawk.commandpanels.Context;
import me.rockyhawk.commandpanels.api.Panel;
import org.bukkit.entity.Player;
import java.util.List;
public class LogicalNode implements ConditionNode {
private final String operator; // "$AND" or "$OR"
private final List<ConditionNode> conditions;
public LogicalNode(String operator, List<ConditionNode> conditions) {
this.operator = operator;
this.conditions = conditions;
}
@Override
public boolean evaluate(Player player, Context ctx, Panel panel) {
switch (operator) {
case "$AND":
return conditions.stream().allMatch(cond -> cond.evaluate(player, ctx, panel));
case "$OR":
return conditions.stream().anyMatch(cond -> cond.evaluate(player, ctx, panel));
default:
return false;
}
}
}

View File

@ -0,0 +1,19 @@
package me.rockyhawk.commandpanels.interaction.logic;
import me.rockyhawk.commandpanels.Context;
import me.rockyhawk.commandpanels.api.Panel;
import org.bukkit.entity.Player;
public class NotNode implements ConditionNode {
private final ConditionNode child;
public NotNode(ConditionNode child) {
this.child = child;
}
@Override
public boolean evaluate(Player player, Context ctx, Panel panel) {
return !child.evaluate(player, ctx, panel);
}
}

View File

@ -0,0 +1,31 @@
package me.rockyhawk.commandpanels.interaction.logic;
public class Token {
public final String value;
public Token(String value) {
this.value = value;
}
public boolean isOperator() {
return value.equals("$AND") || value.equals("$OR") ||
value.equals("$EQUALS") || value.equals("$ISGREATER") || value.equals("$HASPERM");
}
public boolean isLogicalOperator() {
return value.equals("$AND") || value.equals("$OR");
}
public boolean isLeftParen() {
return value.equals("(");
}
public boolean isRightParen() {
return value.equals(")");
}
@Override
public String toString() {
return value;
}
}

View File

@ -0,0 +1,15 @@
package me.rockyhawk.commandpanels.interaction.logic;
import java.util.ArrayList;
import java.util.List;
public class Tokenizer {
public static List<Token> tokenize(String input) {
List<Token> tokens = new ArrayList<>();
String[] parts = input.replace("(", " ( ").replace(")", " ) ").split("\\s+");
for (String part : parts) {
if (!part.isEmpty()) tokens.add(new Token(part));
}
return tokens;
}
}

View File

@ -122,8 +122,10 @@ public class HasSections {
if(player != null){
return player.hasPermission(compare) == outputValue;
}
}else if(value.endsWith(" ISGREATER")) {
return (new BigDecimal(compare).compareTo(new BigDecimal(value.substring(0, value.length()-10).replace(",",""))) <= 0 == outputValue);
}else if (value.endsWith(" ISGREATER")) {
String numericPart = value.replaceAll("[^0-9.\\-]", ""); // allows negative and decimal numbers
BigDecimal target = new BigDecimal(numericPart.replace(",", ""));
return (new BigDecimal(compare).compareTo(target) <= 0) == outputValue;
}else{
return compare.equals(value) == outputValue;
}

View File

@ -18,6 +18,7 @@ public class OpenPanel {
private final PanelCommandExecutor commandExecutor;
private final SoundHandler soundPlayer;
private final PreLoadCommands preloader;
private final OpenRequirements requirementsValidator;
public OpenPanel(Context ctx) {
this.ctx = ctx;
@ -25,6 +26,7 @@ public class OpenPanel {
this.commandExecutor = new PanelCommandExecutor(ctx);
this.soundPlayer = new SoundHandler(ctx);
this.preloader = new PreLoadCommands(ctx);
this.requirementsValidator = new OpenRequirements(ctx);
}
public void open(CommandSender sender, Player p, Panel panel, PanelPosition position) {
@ -42,6 +44,12 @@ public class OpenPanel {
if (!permission.hasPermission(sender, p, panel, position, openForOtherUser)) return;
// Check open requirements before allowing panel to open
if (!requirementsValidator.canOpenPanel(panel, p)) {
sender.sendMessage(ctx.text.colour(ctx.tag + ChatColor.RED + "No permission."));
return;
}
if (position != PanelPosition.Top && !ctx.openPanels.hasPanelOpen(p.getName(), PanelPosition.Top)) {
sender.sendMessage(ctx.text.colour(ctx.tag + ChatColor.RED + "Cannot open a panel without a panel at the top already."));
return;

View File

@ -0,0 +1,35 @@
package me.rockyhawk.commandpanels.manager.open;
import me.rockyhawk.commandpanels.Context;
import me.rockyhawk.commandpanels.api.Panel;
import me.rockyhawk.commandpanels.interaction.logic.ConditionNode;
import me.rockyhawk.commandpanels.interaction.logic.ConditionParser;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
public class OpenRequirements {
private final Context ctx;
public OpenRequirements(Context ctx) {
this.ctx = ctx;
}
/**
* Validates if a player can open a panel based on open-requirements section
* Returns true if player can open the panel, false otherwise
*/
public boolean canOpenPanel(Panel panel, Player player) {
ConfigurationSection config = panel.getConfig();
// Use the logic condition method for open requirements
String condition = config.getString("condition");
boolean result = true;
if(condition != null){
ConditionNode conditionNode = new ConditionParser().parse(condition);
result = conditionNode.evaluate(player, ctx, panel);
}
return result;
}
}

View File

@ -14,6 +14,7 @@ import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.player.*;
import org.bukkit.inventory.ItemStack;
@ -33,6 +34,28 @@ public class HotbarEvents implements Listener {
return;
}
Player p = (Player)e.getWhoClicked();
// Handle number key clicks specifically to prevent hotbar items from moving
if (e.getClick() == ClickType.NUMBER_KEY) {
int hotbarSlot = e.getHotbarButton(); // Number key pressed (0-8)
ItemStack hotbarItem = p.getInventory().getItem(hotbarSlot);
// Check if the hotbar item is a CommandPanels item that should stay stationary
if (hotbarItem != null && ctx.hotbar.itemCheckExecute(hotbarItem, p, false, false)) {
e.setCancelled(true);
// Trigger panel opening logic for the hotbar item
if (ctx.hotbar.stationaryExecute(hotbarSlot, p, e.getClick(), true)) {
// Panel was opened or command was executed
p.updateInventory();
}
// Force refresh the hotbar slot to prevent visual desync
p.getInventory().setItem(hotbarSlot, hotbarItem);
return;
}
}
//get the item clicked, then loop through panel names after action isn't nothing
if(e.getAction() == InventoryAction.NOTHING){return;}
if(e.getSlot() == -999){return;}