From 84e23d5f52f364cb983192e09d06528a22d886b4 Mon Sep 17 00:00:00 2001 From: 1robie <97293924+1robie@users.noreply.github.com> Date: Fri, 10 Oct 2025 13:05:38 +0200 Subject: [PATCH 1/9] feat: add support to create custom items + command --- .../fr/maxlego08/menu/api/ItemManager.java | 40 +++ .../fr/maxlego08/menu/api/MenuPlugin.java | 2 + .../maxlego08/menu/api/mechanic/Mechanic.java | 27 ++ .../menu/api/mechanic/MechanicFactory.java | 57 ++++ .../menu/api/mechanic/MechanicListener.java | 16 ++ .../fr/maxlego08/menu/api/utils/Message.java | 1 + .../java/fr/maxlego08/menu/ZItemManager.java | 248 ++++++++++++++++++ .../java/fr/maxlego08/menu/ZMenuPlugin.java | 10 + .../menu/command/commands/CommandMenu.java | 1 + .../command/commands/CommandMenuGiveItem.java | 44 ++++ .../commands/reload/CommandMenuReload.java | 13 + .../maxlego08/menu/item/CustomItemData.java | 35 +++ .../menu/listener/ItemUpdaterListener.java | 19 ++ .../mechanics/itemjoin/ItemJoinMechanic.java | 33 +++ .../itemjoin/ItemJoinMechanicFactory.java | 23 ++ .../itemjoin/ItemJoinMechanicListener.java | 144 ++++++++++ .../menu/zcore/enums/Permission.java | 1 + 17 files changed, 714 insertions(+) create mode 100644 API/src/main/java/fr/maxlego08/menu/api/ItemManager.java create mode 100644 API/src/main/java/fr/maxlego08/menu/api/mechanic/Mechanic.java create mode 100644 API/src/main/java/fr/maxlego08/menu/api/mechanic/MechanicFactory.java create mode 100644 API/src/main/java/fr/maxlego08/menu/api/mechanic/MechanicListener.java create mode 100644 src/main/java/fr/maxlego08/menu/ZItemManager.java create mode 100644 src/main/java/fr/maxlego08/menu/command/commands/CommandMenuGiveItem.java create mode 100644 src/main/java/fr/maxlego08/menu/item/CustomItemData.java create mode 100644 src/main/java/fr/maxlego08/menu/listener/ItemUpdaterListener.java create mode 100644 src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanic.java create mode 100644 src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanicFactory.java create mode 100644 src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanicListener.java diff --git a/API/src/main/java/fr/maxlego08/menu/api/ItemManager.java b/API/src/main/java/fr/maxlego08/menu/api/ItemManager.java new file mode 100644 index 00000000..c813ec0d --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/ItemManager.java @@ -0,0 +1,40 @@ +package fr.maxlego08.menu.api; + +import fr.maxlego08.menu.api.mechanic.MechanicFactory; +import fr.maxlego08.menu.api.mechanic.MechanicListener; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; + +import java.io.File; +import java.util.Optional; +import java.util.Set; + +public interface ItemManager { + + void loadAll(); + + void loadCustomItems(); + + void loadCustomItem(File file); + + void reloadCustomItems(); + + boolean isCustomItem(String itemId); + + boolean isCustomItem(ItemStack itemStack); + + Optional getItemId(ItemStack itemStack); + + Set getItemIds(); + + void registerListeners(Plugin plugin, String mechanicId, MechanicListener listener); + + void unloadListeners(); + + void registerMechanicFactory(MechanicFactory factory); + + void giveItem(Player player, String itemId); + + void executeCheckInventoryItems(Player player); +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java b/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java index 65b344d1..b14a41e1 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java +++ b/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java @@ -246,4 +246,6 @@ public interface MenuPlugin extends Plugin { ToastHelper getToastHelper(); DialogManager getDialogManager(); + + ItemManager getItemManager(); } diff --git a/API/src/main/java/fr/maxlego08/menu/api/mechanic/Mechanic.java b/API/src/main/java/fr/maxlego08/menu/api/mechanic/Mechanic.java new file mode 100644 index 00000000..3ac7d18f --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/mechanic/Mechanic.java @@ -0,0 +1,27 @@ +package fr.maxlego08.menu.api.mechanic; + +import org.bukkit.configuration.ConfigurationSection; + +public abstract class Mechanic { + private final ConfigurationSection mechanicSection; + private final MechanicFactory mechanicFactory; + private final String itemId; + + public Mechanic(final String itemId, final MechanicFactory mechanicFactory, final ConfigurationSection mechanicSection) { + this.mechanicFactory = mechanicFactory; + this.mechanicSection = mechanicSection; + this.itemId = itemId; + } + + public ConfigurationSection getMechanicSection() { + return mechanicSection; + } + + public MechanicFactory getMechanicFactory() { + return mechanicFactory; + } + + public String getItemId() { + return itemId; + } +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/mechanic/MechanicFactory.java b/API/src/main/java/fr/maxlego08/menu/api/mechanic/MechanicFactory.java new file mode 100644 index 00000000..d27a8acc --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/mechanic/MechanicFactory.java @@ -0,0 +1,57 @@ +package fr.maxlego08.menu.api.mechanic; + +import fr.maxlego08.menu.api.ItemManager; +import fr.maxlego08.menu.api.MenuPlugin; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.inventory.ItemStack; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public abstract class MechanicFactory { + private final MenuPlugin plugin; + private final ItemManager itemManager; + private final String mechanicId; + private final Map mechanicByItemId = new HashMap<>(); + + public MechanicFactory(MenuPlugin plugin, String mechanicId) { + this.plugin = plugin; + this.mechanicId = mechanicId; + this.itemManager = plugin.getItemManager(); + } + + public abstract Mechanic parse(final MenuPlugin plugin, final String itemId, final ConfigurationSection mechanicSection, YamlConfiguration configurationFile, File file, String path); + + public Mechanic getMechanic(String itemId){ + return mechanicByItemId.get(itemId); + }; + + public void addToImplemented(Mechanic mechanic) { + mechanicByItemId.put(mechanic.getItemId(), mechanic); + } + + public boolean isNotImplementedIn(ItemStack itemStack) { + Optional itemId = itemManager.getItemId(itemStack); + return itemId.filter(string -> !mechanicByItemId.containsKey(string)).isPresent(); + } + + public boolean isNotImplementedIn(String itemID) { + return !mechanicByItemId.containsKey(itemID); + } + + public String getMechanicId() { + return mechanicId; + } + + public Set> getAllMechanics(){ + return mechanicByItemId.entrySet(); + } + + public void clearMechanics() { + mechanicByItemId.clear(); + } +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/mechanic/MechanicListener.java b/API/src/main/java/fr/maxlego08/menu/api/mechanic/MechanicListener.java new file mode 100644 index 00000000..cda67d8c --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/mechanic/MechanicListener.java @@ -0,0 +1,16 @@ +package fr.maxlego08.menu.api.mechanic; + +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; + +public abstract class MechanicListener implements Listener { + + /* + * Called when an item with the mechanic is given to a player. + * Return true to cancel the item giving, false to allow it. + */ + public boolean onItemGive(Player player, ItemStack item, String itemId) { + return false; + } +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java b/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java index fbb797de..9c950890 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java @@ -45,6 +45,7 @@ public enum Message implements IMessage { COMMAND_NO_CONSOLE("&cOnly one player can execute this command."), COMMAND_NO_ARG("&cImpossible to find the command with its arguments."), COMMAND_SYNTAX_HELP("&f%syntax% &7ยป &7%description%"), + COMMAND_PLAYER_NOT_FOUND("&cUnable to find the player &f%player%&c."), DOCUMENTATION_INFORMATION("&7Documentation&8: &fhttps://docs.zmenu.dev/"), DOCUMENTATION_INFORMATION_LINK("&7Documentation&8: &f%link%"), ADDONS_INFORMATION("&7Official addons :"), diff --git a/src/main/java/fr/maxlego08/menu/ZItemManager.java b/src/main/java/fr/maxlego08/menu/ZItemManager.java new file mode 100644 index 00000000..af7df373 --- /dev/null +++ b/src/main/java/fr/maxlego08/menu/ZItemManager.java @@ -0,0 +1,248 @@ +package fr.maxlego08.menu; + +import fr.maxlego08.menu.api.ItemManager; +import fr.maxlego08.menu.api.MenuItemStack; +import fr.maxlego08.menu.api.MenuPlugin; +import fr.maxlego08.menu.api.configuration.Config; +import fr.maxlego08.menu.api.mechanic.MechanicFactory; +import fr.maxlego08.menu.api.mechanic.MechanicListener; +import fr.maxlego08.menu.item.CustomItemData; +import fr.maxlego08.menu.mechanics.itemjoin.ItemJoinMechanicFactory; +import fr.maxlego08.menu.zcore.logger.Logger; +import org.bukkit.NamespacedKey; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.plugin.Plugin; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; +import java.util.stream.Stream; + +public class ZItemManager implements ItemManager { + private final NamespacedKey itemIdKey; + private final NamespacedKey ownerKey; + private final MenuPlugin menuPlugin; + + private final Map mechanicListeners = new HashMap<>(); + private final Map mechanicFactories = new HashMap<>(); + + private final Map customItems = new HashMap<>(); + + public ZItemManager(ZMenuPlugin menuPlugin) { + this.itemIdKey = new NamespacedKey(menuPlugin, "item-id"); + this.ownerKey = new NamespacedKey(menuPlugin, "owner"); + this.menuPlugin = menuPlugin; + } + + private void loadMechanics(){ + registerMechanicFactory(new ItemJoinMechanicFactory(this.menuPlugin)); + } + + @Override + public void loadAll() { + this.loadMechanics(); + this.loadCustomItems(); + } + + @Override + public void loadCustomItems() { + File itemsFolder = new File(menuPlugin.getDataFolder(), "items"); + if (!itemsFolder.exists()) { + if (itemsFolder.mkdirs()) { + Logger.info("Items folder created."); + } else { + Logger.info("Failed to create items folder.", Logger.LogType.ERROR); + } + } + try (Stream stream = Files.walk(Paths.get(itemsFolder.getPath()))) { + stream.skip(1).map(Path::toFile).filter(File::isFile).filter(e -> e.getName().endsWith(".yml")).forEach(this::loadCustomItem); + } catch (IOException exception) { + if (Config.enableDebug){ + Logger.info("Error while loading items: " + exception.getMessage(), Logger.LogType.ERROR); + } + } + } + + @Override + public void loadCustomItem(File file) { + YamlConfiguration config = YamlConfiguration.loadConfiguration(file); + for (String itemId : config.getKeys(false)) { + String path = itemId + "."; + MenuItemStack menuItemStack = menuPlugin.loadItemStack(config, path, file); + if (menuItemStack != null) { + Set mechanicIds = new HashSet<>(); + + ConfigurationSection mechanicSection = config.getConfigurationSection(itemId + ".mechanics"); + if (mechanicSection != null) { + path += "mechanics."; + for (String mechanicId : mechanicSection.getKeys(false)) { + MechanicFactory factory = mechanicFactories.get(mechanicId); + if (factory != null) { + factory.parse(this.menuPlugin, itemId, mechanicSection.getConfigurationSection(mechanicId), config, file, path + mechanicId + "."); + mechanicIds.add(mechanicId); + } else { + Logger.info("No MechanicFactory found for mechanicId " + mechanicId + " in item " + itemId, Logger.LogType.WARNING); + } + } + } + + customItems.put(itemId, new CustomItemData(menuItemStack, mechanicIds)); + } else { + if (Config.enableDebug){ + Logger.info("Impossible to load item " + itemId + " from file " + file.getName()); + } + } + } + } + + @Override + public void reloadCustomItems() { + customItems.clear(); + for (MechanicFactory factory : mechanicFactories.values()) { + factory.clearMechanics(); + } + this.loadCustomItems(); + } + + @Override + public boolean isCustomItem(String itemId) { + return customItems.containsKey(itemId); + } + + @Override + public boolean isCustomItem(ItemStack itemStack) { + if (itemStack == null || !itemStack.hasItemMeta() || !itemStack.getItemMeta().getPersistentDataContainer().has(itemIdKey)) { + return false; + } + String itemId = itemStack.getItemMeta().getPersistentDataContainer().get(itemIdKey, PersistentDataType.STRING); + return isCustomItem(itemId); + } + + @Override + public Optional getItemId(ItemStack itemStack) { + if (itemStack == null || !itemStack.hasItemMeta() || !itemStack.getItemMeta().getPersistentDataContainer().has(itemIdKey)) { + return Optional.empty(); + } + String itemId = itemStack.getItemMeta().getPersistentDataContainer().get(itemIdKey, PersistentDataType.STRING); + if (itemId == null || !isCustomItem(itemId)) { + return Optional.empty(); + } + return Optional.of(itemId); + } + + @Override + public void registerListeners(Plugin plugin, String mechanicId, MechanicListener listener) { + if (mechanicListeners.containsKey(mechanicId)) { + Logger.info("Listener for mechanic " + mechanicId + " is already registered.", Logger.LogType.WARNING); + return; + } + plugin.getServer().getPluginManager().registerEvents(listener, plugin); + mechanicListeners.put(mechanicId, listener); + } + + @Override + public void unloadListeners() { + for (Listener listener: mechanicListeners.values()) { + HandlerList.unregisterAll(listener); + } + } + + @Override + public void registerMechanicFactory(MechanicFactory factory) { + if (mechanicFactories.containsKey(factory.getMechanicId())) { + Logger.info("MechanicFactory " + factory.getMechanicId() + " is already registered.", Logger.LogType.WARNING); + return; + } + mechanicFactories.put(factory.getMechanicId(), factory); + } + + @Override + public void giveItem(Player player, String itemId) { + if (!isCustomItem(itemId)) { + Logger.info("Item " + itemId + " is not a custom item.", Logger.LogType.WARNING); + return; + } + + CustomItemData itemData = customItems.get(itemId); + MenuItemStack menuItemStack = itemData.menuItemStack(); + ItemStack itemStack = menuItemStack.build(player); + ItemMeta itemMeta = itemStack.getItemMeta(); + itemMeta.getPersistentDataContainer().set(itemIdKey, PersistentDataType.STRING, itemId); + itemMeta.getPersistentDataContainer().set(ownerKey, PersistentDataType.STRING, player.getUniqueId().toString()); + itemStack.setItemMeta(itemMeta); + + boolean shouldCancel = false; + for (String mechanicId : itemData.mechanicIds()) { + MechanicListener mechanicListener = mechanicListeners.get(mechanicId); + if (mechanicListener != null) { + boolean cancel = mechanicListener.onItemGive(player, itemStack, itemId); + if (cancel) shouldCancel = true; + } + } + + if (shouldCancel) return; + player.getInventory().addItem(itemStack); + } + + @Override + public Set getItemIds() { + return this.customItems.keySet(); + } + + @Override + public void executeCheckInventoryItems(Player player) { + ItemStack[] contents = player.getInventory().getContents(); + for (int i = 0; i < contents.length; i++) { + ItemStack itemStack = contents[i]; + if (itemStack == null || !itemStack.hasItemMeta()) continue; + + ItemMeta meta = itemStack.getItemMeta(); + if (meta == null || !meta.getPersistentDataContainer().has(itemIdKey)) continue; + + String itemId = meta.getPersistentDataContainer().get(itemIdKey, PersistentDataType.STRING); + if (itemId == null) continue; + + if (!isCustomItem(itemId)) { + meta.getPersistentDataContainer().remove(itemIdKey); + meta.getPersistentDataContainer().remove(ownerKey); + itemStack.setItemMeta(meta); + player.getInventory().setItem(i, itemStack); + continue; + } + + CustomItemData itemData = customItems.get(itemId); + if (itemData == null) continue; + + MenuItemStack menuItemStack = itemData.menuItemStack(); + Player owner = this.menuPlugin.getServer().getPlayer(player.getUniqueId()); + if (owner == null) continue; + + ItemStack built = menuItemStack.build(owner); + if (built == null) continue; + + built.setAmount(itemStack.getAmount()); + + ItemMeta builtMeta = built.getItemMeta(); + if (builtMeta == null) continue; + builtMeta.getPersistentDataContainer().set(itemIdKey, PersistentDataType.STRING, itemId); + builtMeta.getPersistentDataContainer().set(ownerKey, PersistentDataType.STRING, owner.getUniqueId().toString()); + + built.setItemMeta(builtMeta); + + if (!built.isSimilar(itemStack)) { + player.getInventory().setItem(i, built); + } + } + } + +} diff --git a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java index f0eb3bd3..066b3cc9 100644 --- a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java +++ b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java @@ -36,6 +36,7 @@ import fr.maxlego08.menu.inventory.VInventoryManager; import fr.maxlego08.menu.inventory.inventories.InventoryDefault; import fr.maxlego08.menu.listener.AdapterListener; +import fr.maxlego08.menu.listener.ItemUpdaterListener; import fr.maxlego08.menu.listener.SwapKeyListener; import fr.maxlego08.menu.loader.materials.ArmorLoader; import fr.maxlego08.menu.loader.materials.Base64Loader; @@ -96,6 +97,7 @@ public class ZMenuPlugin extends ZPlugin implements MenuPlugin { private final InventoriesPlayer inventoriesPlayer = new ZInventoriesPlayer(this); private final PatternManager patternManager = new ZPatternManager(this); private final Enchantments enchantments = new ZEnchantments(); + private final ItemManager itemManager = new ZItemManager(this); private final Map globalPlaceholders = new HashMap<>(); private final ToastHelper toastHelper = new ToastManager(this); private CommandMenu commandMenu; @@ -181,6 +183,7 @@ public void onEnable() { this.addListener(new AdapterListener(this)); this.addListener(this.vinventoryManager); this.addListener(this.inventoriesPlayer); + this.addListener(new ItemUpdaterListener(this.itemManager)); this.addSimpleListener(this.inventoryManager); this.inventoryManager.registerMaterialLoader(new Base64Loader()); @@ -192,6 +195,7 @@ public void onEnable() { this.messageLoader.load(); this.inventoriesPlayer.loadInventories(); this.dataManager.loadPlayers(); + this.itemManager.loadAll(); LocalPlaceholder localPlaceholder = LocalPlaceholder.getInstance(); localPlaceholder.register("argument_", (offlinePlayer, value) -> { @@ -333,6 +337,7 @@ public void onDisable() { if (Token.token != null) { Token.getInstance().save(this.getPersist()); } + this.itemManager.unloadListeners(); // this.packetUtils.onDisable(); this.postDisable(); @@ -382,6 +387,11 @@ public CommandManager getCommandManager() { @Override public DialogManager getDialogManager() {return this.dialogManager;} + @Override + public ItemManager getItemManager() { + return this.itemManager; + } + @Override public StorageManager getStorageManager() { return this.storageManager; diff --git a/src/main/java/fr/maxlego08/menu/command/commands/CommandMenu.java b/src/main/java/fr/maxlego08/menu/command/commands/CommandMenu.java index 95414c88..1238f65c 100644 --- a/src/main/java/fr/maxlego08/menu/command/commands/CommandMenu.java +++ b/src/main/java/fr/maxlego08/menu/command/commands/CommandMenu.java @@ -33,6 +33,7 @@ public CommandMenu(ZMenuPlugin plugin) { this.addSubCommand(new CommandAddons(plugin)); this.addSubCommand(new CommandDumplog(plugin)); this.addSubCommand(new CommandContributors(plugin)); + this.addSubCommand(new CommandMenuGiveItem(plugin)); // Disable website connexion for beta this.addSubCommand(new CommandMenuDownload(plugin)); diff --git a/src/main/java/fr/maxlego08/menu/command/commands/CommandMenuGiveItem.java b/src/main/java/fr/maxlego08/menu/command/commands/CommandMenuGiveItem.java new file mode 100644 index 00000000..0e6dc9be --- /dev/null +++ b/src/main/java/fr/maxlego08/menu/command/commands/CommandMenuGiveItem.java @@ -0,0 +1,44 @@ +package fr.maxlego08.menu.command.commands; + +import fr.maxlego08.menu.ZMenuPlugin; +import fr.maxlego08.menu.api.ItemManager; +import fr.maxlego08.menu.api.utils.Message; +import fr.maxlego08.menu.command.VCommand; +import fr.maxlego08.menu.zcore.enums.Permission; +import fr.maxlego08.menu.zcore.utils.commands.CommandType; +import org.bukkit.entity.Player; + +public class CommandMenuGiveItem extends VCommand { + private final ItemManager itemManager; + public CommandMenuGiveItem(ZMenuPlugin plugin) { + super(plugin); + this.itemManager = plugin.getItemManager(); + this.addSubCommand("giveitem"); + this.setPermission(Permission.ZMENU_GIVE_ITEM); + this.addRequireArg("itemId",(sender,args)-> this.itemManager.getItemIds().stream().toList()); + this.addOptionalArg("player"); + + } + + @Override + protected CommandType perform(ZMenuPlugin plugin) { + String itemId = this.argAsString(0); + Player target = this.argAsPlayer(1, this.player); + if (target == null) { + message(plugin, sender, Message.COMMAND_PLAYER_NOT_FOUND, "%player%", this.argAsString(1)); + return CommandType.DEFAULT; + } + if (!this.itemManager.isCustomItem(itemId)) { + message(plugin, sender, "&cItem with ID %itemId% does not exist.", "%itemId%", itemId); + return CommandType.DEFAULT; + } + this.itemManager.giveItem(target, itemId); + if (target.equals(this.player)) { + message(plugin, sender, "&aYou have received the item %itemId%.", "%itemId%", itemId); + } else { + message(plugin, sender, "&aYou have given the item %itemId% to %player%.", "%itemId%", itemId, "%player%", target.getName()); + message(plugin, target, "&aYou have received the item %itemId% from %sender%.", "%itemId%", itemId, "%sender%", sender.getName()); + } + return CommandType.SUCCESS; + } +} diff --git a/src/main/java/fr/maxlego08/menu/command/commands/reload/CommandMenuReload.java b/src/main/java/fr/maxlego08/menu/command/commands/reload/CommandMenuReload.java index d79f9848..47f8e4cd 100644 --- a/src/main/java/fr/maxlego08/menu/command/commands/reload/CommandMenuReload.java +++ b/src/main/java/fr/maxlego08/menu/command/commands/reload/CommandMenuReload.java @@ -2,12 +2,16 @@ import fr.maxlego08.menu.ZMenuPlugin; import fr.maxlego08.menu.api.InventoryManager; +import fr.maxlego08.menu.api.ItemManager; import fr.maxlego08.menu.api.command.CommandManager; import fr.maxlego08.menu.api.configuration.Config; import fr.maxlego08.menu.api.utils.Message; import fr.maxlego08.menu.command.VCommand; import fr.maxlego08.menu.zcore.enums.Permission; import fr.maxlego08.menu.zcore.utils.commands.CommandType; +import org.bukkit.entity.Player; + +import java.util.Collection; public class CommandMenuReload extends VCommand { @@ -42,6 +46,15 @@ protected CommandType perform(ZMenuPlugin plugin) { CommandManager commandManager = plugin.getCommandManager(); commandManager.loadCommands(); + ItemManager itemManager = plugin.getItemManager(); + itemManager.reloadCustomItems(); + Collection onlinePlayers = plugin.getServer().getOnlinePlayers(); + plugin.getScheduler().runAsync(w->{ + for (Player player : onlinePlayers) { + itemManager.executeCheckInventoryItems(player); + } + }); + plugin.getDataManager().loadDefaultValues(); message(plugin, this.sender, Message.RELOAD, "%inventories%", inventoryManager.getInventories(plugin).size()); diff --git a/src/main/java/fr/maxlego08/menu/item/CustomItemData.java b/src/main/java/fr/maxlego08/menu/item/CustomItemData.java new file mode 100644 index 00000000..f280d2a4 --- /dev/null +++ b/src/main/java/fr/maxlego08/menu/item/CustomItemData.java @@ -0,0 +1,35 @@ +package fr.maxlego08.menu.item; + +import fr.maxlego08.menu.api.MenuItemStack; + +import java.util.Set; + +/** + * Record that holds data for a custom item, including its MenuItemStack and associated mechanic IDs. + * This allows for optimized lookups when giving items or checking mechanics. + * + * @param menuItemStack the ItemStack configuration for this custom item + * @param mechanicIds set of mechanic IDs that are implemented for this item + */ +public record CustomItemData(MenuItemStack menuItemStack, Set mechanicIds) { + + /** + * Check if this item has a specific mechanic implemented. + * + * @param mechanicId the mechanic ID to check + * @return true if the mechanic is implemented for this item + */ + public boolean hasMechanic(String mechanicId) { + return mechanicIds.contains(mechanicId); + } + + /** + * Check if this item has any mechanics. + * + * @return true if at least one mechanic is implemented + */ + public boolean hasMechanics() { + return !mechanicIds.isEmpty(); + } +} + diff --git a/src/main/java/fr/maxlego08/menu/listener/ItemUpdaterListener.java b/src/main/java/fr/maxlego08/menu/listener/ItemUpdaterListener.java new file mode 100644 index 00000000..e12f5c5f --- /dev/null +++ b/src/main/java/fr/maxlego08/menu/listener/ItemUpdaterListener.java @@ -0,0 +1,19 @@ +package fr.maxlego08.menu.listener; + +import fr.maxlego08.menu.api.ItemManager; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerJoinEvent; + +public class ItemUpdaterListener extends ListenerAdapter{ + private final ItemManager itemManager; + + public ItemUpdaterListener(ItemManager itemManager) { + this.itemManager = itemManager; + } + + @Override + protected void onConnect(PlayerJoinEvent event, Player player) { + itemManager.executeCheckInventoryItems(player); + } + +} diff --git a/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanic.java b/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanic.java new file mode 100644 index 00000000..31abeb90 --- /dev/null +++ b/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanic.java @@ -0,0 +1,33 @@ +package fr.maxlego08.menu.mechanics.itemjoin; + +import fr.maxlego08.menu.api.mechanic.Mechanic; +import fr.maxlego08.menu.api.mechanic.MechanicFactory; +import org.bukkit.configuration.ConfigurationSection; + +import java.util.OptionalInt; + +public class ItemJoinMechanic extends Mechanic { + private final boolean grantOnFirstJoin; + private final boolean preventInventoryChanges; + private final OptionalInt fixedSlot; + + public ItemJoinMechanic(String itemId, MechanicFactory mechanicFactory, ConfigurationSection mechanicSection) { + super(itemId, mechanicFactory, mechanicSection); + this.grantOnFirstJoin = mechanicSection.getBoolean("give-first-join", false); + this.preventInventoryChanges = mechanicSection.getBoolean("prevent-inventory-modification", true); + int slot = mechanicSection.getInt("fixed-slot", -1); + this.fixedSlot = slot >= 0 && slot <= 36 ? OptionalInt.of(slot) : OptionalInt.empty(); + } + + public boolean shouldGrantOnFirstJoin() { + return grantOnFirstJoin; + } + + public boolean preventsInventoryChanges() { + return preventInventoryChanges; + } + + public OptionalInt getFixedSlot() { + return fixedSlot; + } +} diff --git a/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanicFactory.java b/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanicFactory.java new file mode 100644 index 00000000..70b7b66f --- /dev/null +++ b/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanicFactory.java @@ -0,0 +1,23 @@ +package fr.maxlego08.menu.mechanics.itemjoin; + +import fr.maxlego08.menu.api.MenuPlugin; +import fr.maxlego08.menu.api.mechanic.Mechanic; +import fr.maxlego08.menu.api.mechanic.MechanicFactory; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; + +public class ItemJoinMechanicFactory extends MechanicFactory { + public ItemJoinMechanicFactory(MenuPlugin plugin) { + super(plugin, "itemjoin"); + plugin.getItemManager().registerListeners(plugin, "itemjoin", new ItemJoinMechanicListener(this, plugin)); + } + + @Override + public Mechanic parse(MenuPlugin plugin, String itemId, ConfigurationSection mechanicSection, YamlConfiguration configurationFile, File file, String path) { + ItemJoinMechanic mechanic = new ItemJoinMechanic(itemId, this, mechanicSection); + this.addToImplemented(mechanic); + return mechanic; + } +} diff --git a/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanicListener.java b/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanicListener.java new file mode 100644 index 00000000..f6fbce15 --- /dev/null +++ b/src/main/java/fr/maxlego08/menu/mechanics/itemjoin/ItemJoinMechanicListener.java @@ -0,0 +1,144 @@ +package fr.maxlego08.menu.mechanics.itemjoin; + +import fr.maxlego08.menu.api.ItemManager; +import fr.maxlego08.menu.api.MenuPlugin; +import fr.maxlego08.menu.api.mechanic.Mechanic; +import fr.maxlego08.menu.api.mechanic.MechanicListener; +import org.bukkit.Material; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerSwapHandItemsEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.Map; +import java.util.Optional; + +public class ItemJoinMechanicListener extends MechanicListener { + private final ItemJoinMechanicFactory itemJoinMechanicFactory; + private final ItemManager itemManager; + + public ItemJoinMechanicListener(ItemJoinMechanicFactory itemJoinMechanicFactory, MenuPlugin plugin) { + this.itemJoinMechanicFactory = itemJoinMechanicFactory; + this.itemManager = plugin.getItemManager(); + } + + /** + * Gets the ItemJoinMechanic for the given item, if it exists and prevents inventory changes. + * + * @param item the item to check + * @return Optional containing the mechanic if it prevents inventory changes, empty otherwise + */ + private Optional getProtectedMechanic(ItemStack item) { + if (item == null) return Optional.empty(); + + Optional itemId = itemManager.getItemId(item); + if (itemId.isEmpty()) return Optional.empty(); + + Mechanic mechanic = itemJoinMechanicFactory.getMechanic(itemId.get()); + if (mechanic instanceof ItemJoinMechanic itemJoinMechanic) { + if (itemJoinMechanic.preventsInventoryChanges() && itemJoinMechanic.getFixedSlot().isPresent()) { + return Optional.of(itemJoinMechanic); + } + } + return Optional.empty(); + } + + /** + * Checks if the item should be protected from inventory changes. + * + * @param item the item to check + * @return true if the item is protected, false otherwise + */ + private boolean isProtectedItem(ItemStack item) { + return getProtectedMechanic(item).isPresent(); + } + + @EventHandler + public void onConnect(PlayerJoinEvent event) { + Player player = event.getPlayer(); + if (!player.hasPlayedBefore()) { + for (Map.Entry entry : itemJoinMechanicFactory.getAllMechanics()) { + if (entry.getValue() instanceof ItemJoinMechanic itemJoinMechanic) { + if (itemJoinMechanic.shouldGrantOnFirstJoin()) { + this.itemManager.giveItem(player, entry.getKey()); + } + } + } + } + } + + @EventHandler + public void onPlayerDrop(PlayerDropItemEvent event) { + if (isProtectedItem(event.getItemDrop().getItemStack())) { + event.setCancelled(true); + } + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (event.getInventory().getType() != InventoryType.CRAFTING) { + return; + } + + Optional mechanic = getProtectedMechanic(event.getCurrentItem()); + if (mechanic.isPresent() && event.getSlot() == mechanic.get().getFixedSlot().getAsInt()) { + event.setCancelled(true); + } + } + + @EventHandler + public void onDrag(InventoryDragEvent event) { + if (event.getInventory().getType() != InventoryType.CRAFTING) { + return; + } + + for (ItemStack draggedItem : event.getNewItems().values()) { + Optional mechanic = getProtectedMechanic(draggedItem); + if (mechanic.isPresent() && event.getRawSlots().contains(mechanic.get().getFixedSlot().getAsInt())) { + event.setCancelled(true); + return; + } + } + } + + @EventHandler + public void onSwapHand(PlayerSwapHandItemsEvent event) { + if (isProtectedItem(event.getOffHandItem())) { + event.setCancelled(true); + } + } + + @EventHandler + public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { + if (!(event.getRightClicked() instanceof ItemFrame frame)) return; + + ItemStack frameItem = frame.getItem(); + ItemStack itemInHand = event.getPlayer().getInventory().getItemInMainHand(); + + if (frameItem.getType() == Material.AIR && itemInHand.getType() != Material.AIR) { + if (isProtectedItem(itemInHand)) { + event.setCancelled(true); + } + } + } + + @Override + public boolean onItemGive(Player player, ItemStack item, String itemId) { + Mechanic mechanic = itemJoinMechanicFactory.getMechanic(itemId); + if (mechanic instanceof ItemJoinMechanic itemJoinMechanic) { + if (itemJoinMechanic.preventsInventoryChanges() && itemJoinMechanic.getFixedSlot().isPresent()) { + int slot = itemJoinMechanic.getFixedSlot().getAsInt(); + player.getInventory().setItem(slot, item); + return true; + } + } + return false; + } +} diff --git a/src/main/java/fr/maxlego08/menu/zcore/enums/Permission.java b/src/main/java/fr/maxlego08/menu/zcore/enums/Permission.java index fb36e545..bf19f81d 100644 --- a/src/main/java/fr/maxlego08/menu/zcore/enums/Permission.java +++ b/src/main/java/fr/maxlego08/menu/zcore/enums/Permission.java @@ -21,6 +21,7 @@ public enum Permission { ZMENU_RELOAD_DIALOG, ZMENU_DUMPLOG, ZMENU_CONTRIBUTORS, + ZMENU_GIVE_ITEM, ZMENU_ADDONS, ZMENU_DOCUMENTATION; From ed3e5429162a953dd43b7995379ce7e94b3c481e Mon Sep 17 00:00:00 2001 From: 1robie <97293924+1robie@users.noreply.github.com> Date: Fri, 10 Oct 2025 13:17:46 +0200 Subject: [PATCH 2/9] feat: move messages in GiveItemCommand to Message.java and add a default item --- .../java/fr/maxlego08/menu/api/utils/Message.java | 6 +++++- src/main/java/fr/maxlego08/menu/ZMenuPlugin.java | 2 ++ .../menu/command/commands/CommandMenuGiveItem.java | 8 ++++---- src/main/resources/items/default-items.yml | 13 +++++++++++++ 4 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 src/main/resources/items/default-items.yml diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java b/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java index 9c950890..8faede98 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java @@ -98,6 +98,7 @@ public enum Message implements IMessage { DESCRIPTION_ADDONS("List of official addons"), DESCRIPTION_DUMPLOG("Generate a dumplog file for support"), DESCRIPTION_CONTRIBUTORS("List of authors and contributors of the plugin"), + DESCRIPTION_GIVE_ITEM("Give a custom item to a player"), RELOAD("&aYou have just reloaded the configuration files. &8(&7%inventories% inventories&8)"), RELOAD_INVENTORY("&aYou have just reloaded the inventories files. &8(&7%inventories% inventories&8)"), @@ -111,6 +112,10 @@ public enum Message implements IMessage { DUMPLOG_SUCCESS("&aYou have just generated a dumplog file, please send this link to support: &f%url%"), DUMPLOG_ERROR("&cAn error occurred while generating the dumplog file, %error%."), + GIVE_ITEM_NOT_FOUND("&cItem with ID &f%itemId%&c does not exist."), + GIVE_ITEM_SUCCESS_SELF("&aYou have received the item &f%itemId%&a."), + GIVE_ITEM_SUCCESS_OTHER("&aYou have given the item &f%itemId%&a to &3%player%&a."), + PLAYERS_DATA_CLEAR_ALL("&aYou have just deleted the datas of all the players."), PLAYERS_DATA_CLEAR_PLAYER("&aYou have just deleted the player's data &f%player%&a."), @@ -255,4 +260,3 @@ public void setItemStack(ItemStack itemStack) { } } - diff --git a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java index 066b3cc9..b65c6c49 100644 --- a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java +++ b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java @@ -322,6 +322,8 @@ private List getInventoriesFiles() { files.add("dialogs/server_link-dialog.yml"); } + files.add("items/default-items.yml"); + return files; } diff --git a/src/main/java/fr/maxlego08/menu/command/commands/CommandMenuGiveItem.java b/src/main/java/fr/maxlego08/menu/command/commands/CommandMenuGiveItem.java index 0e6dc9be..c5f99562 100644 --- a/src/main/java/fr/maxlego08/menu/command/commands/CommandMenuGiveItem.java +++ b/src/main/java/fr/maxlego08/menu/command/commands/CommandMenuGiveItem.java @@ -14,6 +14,7 @@ public CommandMenuGiveItem(ZMenuPlugin plugin) { super(plugin); this.itemManager = plugin.getItemManager(); this.addSubCommand("giveitem"); + this.setDescription(Message.DESCRIPTION_GIVE_ITEM); this.setPermission(Permission.ZMENU_GIVE_ITEM); this.addRequireArg("itemId",(sender,args)-> this.itemManager.getItemIds().stream().toList()); this.addOptionalArg("player"); @@ -29,15 +30,14 @@ protected CommandType perform(ZMenuPlugin plugin) { return CommandType.DEFAULT; } if (!this.itemManager.isCustomItem(itemId)) { - message(plugin, sender, "&cItem with ID %itemId% does not exist.", "%itemId%", itemId); + message(plugin, sender, Message.GIVE_ITEM_NOT_FOUND, "%itemId%", itemId); return CommandType.DEFAULT; } this.itemManager.giveItem(target, itemId); if (target.equals(this.player)) { - message(plugin, sender, "&aYou have received the item %itemId%.", "%itemId%", itemId); + message(plugin, sender, Message.GIVE_ITEM_SUCCESS_SELF, "%itemId%", itemId); } else { - message(plugin, sender, "&aYou have given the item %itemId% to %player%.", "%itemId%", itemId, "%player%", target.getName()); - message(plugin, target, "&aYou have received the item %itemId% from %sender%.", "%itemId%", itemId, "%sender%", sender.getName()); + message(plugin, sender, Message.GIVE_ITEM_SUCCESS_OTHER, "%itemId%", itemId, "%player%", target.getName()); } return CommandType.SUCCESS; } diff --git a/src/main/resources/items/default-items.yml b/src/main/resources/items/default-items.yml new file mode 100644 index 00000000..bd320283 --- /dev/null +++ b/src/main/resources/items/default-items.yml @@ -0,0 +1,13 @@ +#: +# name: ... # Same format use for items in menu +# mechanics: +# : +# ... # Depending on the mechanic + + +first-item: + name: "&aFirst Item" + lore: + - "&7This is the first item" + - "&7in the default items file." + \ No newline at end of file From e5080eb5be5d57bb7526d8e8cf59c077f422a450 Mon Sep 17 00:00:00 2001 From: 1robie <97293924+1robie@users.noreply.github.com> Date: Fri, 10 Oct 2025 13:25:01 +0200 Subject: [PATCH 3/9] feat: fix owner + space after implement --- src/main/java/fr/maxlego08/menu/ZItemManager.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/fr/maxlego08/menu/ZItemManager.java b/src/main/java/fr/maxlego08/menu/ZItemManager.java index af7df373..ff7de597 100644 --- a/src/main/java/fr/maxlego08/menu/ZItemManager.java +++ b/src/main/java/fr/maxlego08/menu/ZItemManager.java @@ -28,7 +28,7 @@ import java.util.*; import java.util.stream.Stream; -public class ZItemManager implements ItemManager { +public class ZItemManager implements ItemManager{ private final NamespacedKey itemIdKey; private final NamespacedKey ownerKey; private final MenuPlugin menuPlugin; @@ -224,7 +224,9 @@ public void executeCheckInventoryItems(Player player) { if (itemData == null) continue; MenuItemStack menuItemStack = itemData.menuItemStack(); - Player owner = this.menuPlugin.getServer().getPlayer(player.getUniqueId()); + String ownerUuid = meta.getPersistentDataContainer().get(ownerKey, PersistentDataType.STRING); + if (ownerUuid == null) continue; + Player owner = this.menuPlugin.getServer().getPlayer(UUID.fromString(ownerUuid)); if (owner == null) continue; ItemStack built = menuItemStack.build(owner); From 8434b5d0077f667f35895468b6d72799c46f43ee Mon Sep 17 00:00:00 2001 From: 1robie <97293924+1robie@users.noreply.github.com> Date: Fri, 10 Oct 2025 13:25:34 +0200 Subject: [PATCH 4/9] refactor: remove unnecessary newline --- API/src/main/java/fr/maxlego08/menu/api/utils/Message.java | 1 - 1 file changed, 1 deletion(-) diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java b/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java index 8faede98..a3bca315 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java @@ -258,5 +258,4 @@ public ItemStack getItemStack() { public void setItemStack(ItemStack itemStack) { this.itemStack = itemStack; } - } From 851a2e018b1a27285d5b386f3273e626a8462b31 Mon Sep 17 00:00:00 2001 From: 1robie <97293924+1robie@users.noreply.github.com> Date: Thu, 16 Oct 2025 19:34:21 +0200 Subject: [PATCH 5/9] chore: update version --- build.gradle.kts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index fe0ecb1e..ca968ac4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,3 @@ -import org.gradle.kotlin.dsl.invoke - plugins { `java-library` id("com.gradleup.shadow") version "9.0.0" @@ -7,7 +5,7 @@ plugins { } group = "fr.maxlego08.menu" -version = "1.1.0.2" +version = "1.1.0.4" extra.set("targetFolder", file("target/")) extra.set("apiFolder", file("target-api/")) From 3ef1e32c4b213da0006a3c357d036fb019b04e16 Mon Sep 17 00:00:00 2001 From: 1robie <97293924+1robie@users.noreply.github.com> Date: Thu, 16 Oct 2025 19:37:34 +0200 Subject: [PATCH 6/9] fix: resolve false positive detection caused by annotation --- .../menu/api/configuration/Config.java | 5 - .../api/configuration/ConfigManagerInt.java | 4 +- .../annotation/ConfigDialog.java | 20 ---- .../dialog/ConfigDialogBuilder.java | 109 ++++++++++++++++++ .../maxlego08/menu/config/ConfigManager.java | 40 +++---- .../java/fr/maxlego08/menu/ZMenuPlugin.java | 5 +- 6 files changed, 133 insertions(+), 50 deletions(-) delete mode 100644 API/src/main/java/fr/maxlego08/menu/api/configuration/annotation/ConfigDialog.java create mode 100644 API/src/main/java/fr/maxlego08/menu/api/configuration/dialog/ConfigDialogBuilder.java diff --git a/API/src/main/java/fr/maxlego08/menu/api/configuration/Config.java b/API/src/main/java/fr/maxlego08/menu/api/configuration/Config.java index 88b9b6ba..55daf195 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/configuration/Config.java +++ b/API/src/main/java/fr/maxlego08/menu/api/configuration/Config.java @@ -1,6 +1,5 @@ package fr.maxlego08.menu.api.configuration; -import fr.maxlego08.menu.api.configuration.annotation.ConfigDialog; import fr.maxlego08.menu.api.configuration.annotation.ConfigOption; import fr.maxlego08.menu.api.configuration.annotation.ConfigUpdate; import fr.maxlego08.menu.api.enums.DialogInputType; @@ -16,10 +15,6 @@ import java.util.Objects; import java.util.stream.Collectors; -@ConfigDialog( - name = "zMenu Config", - externalTitle = "zMenu Configuration" -) public class Config { // Enable debug, allows you to display errors in the console that would normally be hidden. diff --git a/API/src/main/java/fr/maxlego08/menu/api/configuration/ConfigManagerInt.java b/API/src/main/java/fr/maxlego08/menu/api/configuration/ConfigManagerInt.java index f11083d9..fcda68b0 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/configuration/ConfigManagerInt.java +++ b/API/src/main/java/fr/maxlego08/menu/api/configuration/ConfigManagerInt.java @@ -1,12 +1,14 @@ package fr.maxlego08.menu.api.configuration; +import fr.maxlego08.menu.api.configuration.dialog.ConfigDialogBuilder; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; import java.util.List; public interface ConfigManagerInt { - void registerConfig(Class configClass, Plugin plugin); + void registerConfig(@NotNull ConfigDialogBuilder configDialogBuilder, @NotNull Class configClass, @NotNull Plugin plugin); List getRegisteredConfigs(); void openConfig(String pluginName, Player player); void openConfig(Plugin plugin, Player player); diff --git a/API/src/main/java/fr/maxlego08/menu/api/configuration/annotation/ConfigDialog.java b/API/src/main/java/fr/maxlego08/menu/api/configuration/annotation/ConfigDialog.java deleted file mode 100644 index 13ee845a..00000000 --- a/API/src/main/java/fr/maxlego08/menu/api/configuration/annotation/ConfigDialog.java +++ /dev/null @@ -1,20 +0,0 @@ -package fr.maxlego08.menu.api.configuration.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface ConfigDialog { - String name() default ""; - String externalTitle() default ""; - String yesText() default "Confirm"; - String noText() default "Cancel"; - int yesWidth() default 150; - int noWidth() default 150; - String booleanConfirmText() default "%key% : %value% | %text%"; - String numberRangeConfirmText() default "%key% : %value%"; - String textConfirmText() default "%key% : %text%"; -} diff --git a/API/src/main/java/fr/maxlego08/menu/api/configuration/dialog/ConfigDialogBuilder.java b/API/src/main/java/fr/maxlego08/menu/api/configuration/dialog/ConfigDialogBuilder.java new file mode 100644 index 00000000..ee1725fe --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/configuration/dialog/ConfigDialogBuilder.java @@ -0,0 +1,109 @@ +package fr.maxlego08.menu.api.configuration.dialog; + +public class ConfigDialogBuilder { + private final String name; + private final String externalTitle; + private String yesText = "Confirm"; + private String yesTooltip = ""; + private int yesWidth = 150; + private String noText = "Cancel"; + private String noTooltip = ""; + private int noWidth = 150; + private String booleanConfirmText = "%key% : %value% | %text%"; + private String numberRangeConfirmText= "%key% : %value%"; + private String textConfirmText="%key% : %text%"; + + public ConfigDialogBuilder(String name, String externalTitle) { + this.name = name; + this.externalTitle = externalTitle; + } + + public ConfigDialogBuilder yesText(String yesText) { + this.yesText = yesText; + return this; + } + + public ConfigDialogBuilder yesTooltip(String yesTooltip) { + this.yesTooltip = yesTooltip; + return this; + } + + public ConfigDialogBuilder yesWidth(int yesWidth) { + this.yesWidth = yesWidth; + return this; + } + + public ConfigDialogBuilder noText(String noText) { + this.noText = noText; + return this; + } + + public ConfigDialogBuilder noTooltip(String noTooltip) { + this.noTooltip = noTooltip; + return this; + } + + public ConfigDialogBuilder noWidth(int noWidth) { + this.noWidth = noWidth; + return this; + } + + public ConfigDialogBuilder booleanConfirmText(String booleanConfirmText) { + this.booleanConfirmText = booleanConfirmText; + return this; + } + + public ConfigDialogBuilder numberRangeConfirmText(String numberRangeConfirmText) { + this.numberRangeConfirmText = numberRangeConfirmText; + return this; + } + + public ConfigDialogBuilder textConfirmText(String textConfirmText) { + this.textConfirmText = textConfirmText; + return this; + } + + public String getName() { + return name; + } + + public String getExternalTitle() { + return externalTitle; + } + + public String getYesText() { + return yesText; + } + + public String getYesTooltip() { + return yesTooltip; + } + + public int getYesWidth() { + return yesWidth; + } + + public String getNoText() { + return noText; + } + + public String getNoTooltip() { + return noTooltip; + } + + public int getNoWidth() { + return noWidth; + } + + public String getBooleanConfirmText() { + return booleanConfirmText; + } + + public String getNumberRangeConfirmText() { + return numberRangeConfirmText; + } + + public String getTextConfirmText() { + return textConfirmText; + } +} diff --git a/Hooks/Paper/src/main/java/fr/maxlego08/menu/config/ConfigManager.java b/Hooks/Paper/src/main/java/fr/maxlego08/menu/config/ConfigManager.java index 4d912020..8b408fe1 100644 --- a/Hooks/Paper/src/main/java/fr/maxlego08/menu/config/ConfigManager.java +++ b/Hooks/Paper/src/main/java/fr/maxlego08/menu/config/ConfigManager.java @@ -3,9 +3,9 @@ import fr.maxlego08.menu.api.MenuPlugin; import fr.maxlego08.menu.api.configuration.Config; import fr.maxlego08.menu.api.configuration.ConfigManagerInt; -import fr.maxlego08.menu.api.configuration.annotation.ConfigDialog; import fr.maxlego08.menu.api.configuration.annotation.ConfigOption; import fr.maxlego08.menu.api.configuration.annotation.ConfigUpdate; +import fr.maxlego08.menu.api.configuration.dialog.ConfigDialogBuilder; import fr.maxlego08.menu.api.enums.DialogInputType; import fr.maxlego08.menu.api.enums.DialogType; import fr.maxlego08.menu.api.utils.dialogs.record.ZDialogInventoryBuild; @@ -27,6 +27,7 @@ import net.kyori.adventure.text.event.ClickCallback; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; import java.util.ArrayList; @@ -57,43 +58,35 @@ private void initializeDefaultProcessors() { } @Override - public void registerConfig(Class configClass, Plugin plugin) { - ConfigDialog configDialog = validateConfigClass(configClass); - + public void registerConfig(@NotNull ConfigDialogBuilder configDialogBuilder, @NotNull Class configClass, @NotNull Plugin plugin) { String configName = plugin.getName() + ":" + configClass.getSimpleName(); ConfigFieldContext context = processConfigFields(configClass); - ZDialogInventoryDeveloper dialogInventory = createDialogInventory(configDialog, configName, context.getUpdateConsumer()); + ZDialogInventoryDeveloper dialogInventory = createDialogInventory(configDialogBuilder, configName, context.getUpdateConsumer()); applyContextToDialog(dialogInventory, context); zDialogInventoryDev.put(plugin.getName(), dialogInventory); } - private ConfigDialog validateConfigClass(Class configClass) { - ConfigDialog configDialog = configClass.getAnnotation(ConfigDialog.class); - if (configDialog == null) { - throw new IllegalArgumentException("The class " + configClass.getName() + " must be annotated with @ConfigDialog"); - } - return configDialog; - } - - private ZDialogInventoryDeveloper createDialogInventory(ConfigDialog configDialog, String configName, Consumer updateConsumer) { + private ZDialogInventoryDeveloper createDialogInventory(ConfigDialogBuilder configDialog, String configName, Consumer updateConsumer) { ZDialogInventoryDeveloper dialogInventory = new ZDialogInventoryDeveloper( this.menuPlugin, - configDialog.name(), + configDialog.getName(), configName, - configDialog.externalTitle(), + configDialog.getExternalTitle(), updateConsumer ); dialogInventory.setDialogType(DialogType.CONFIRMATION); - dialogInventory.setBooleanConfirmText(configDialog.booleanConfirmText()); - dialogInventory.setNumberRangeConfirmText(configDialog.numberRangeConfirmText()); - dialogInventory.setStringConfirmText(configDialog.textConfirmText()); - dialogInventory.setYesText(configDialog.yesText()); - dialogInventory.setNoText(configDialog.noText()); - dialogInventory.setYesWidth(configDialog.yesWidth()); - dialogInventory.setNoWidth(configDialog.noWidth()); + dialogInventory.setBooleanConfirmText(configDialog.getBooleanConfirmText()); + dialogInventory.setNumberRangeConfirmText(configDialog.getNumberRangeConfirmText()); + dialogInventory.setStringConfirmText(configDialog.getTextConfirmText()); + dialogInventory.setYesText(configDialog.getYesText()); + dialogInventory.setYesWidth(configDialog.getYesWidth()); + dialogInventory.setYesTooltip(configDialog.getYesTooltip()); + dialogInventory.setNoText(configDialog.getNoText()); + dialogInventory.setNoWidth(configDialog.getNoWidth()); + dialogInventory.setNoTooltip(configDialog.getNoTooltip()); dialogInventory.setPause(true); dialogInventory.setCanCloseWithEscape(false); @@ -198,6 +191,7 @@ public void openConfig(String pluginName, Player player) { } catch (Exception e) { if (Config.enableDebug) { Logger.info("Failed to open configuration dialog for player: " + player.getName() + " error :" + e.getMessage(), Logger.LogType.ERROR); + e.printStackTrace(); } } } diff --git a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java index b65c6c49..00527dfc 100644 --- a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java +++ b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java @@ -5,6 +5,7 @@ import fr.maxlego08.menu.api.*; import fr.maxlego08.menu.api.command.CommandManager; import fr.maxlego08.menu.api.configuration.Config; +import fr.maxlego08.menu.api.configuration.dialog.ConfigDialogBuilder; import fr.maxlego08.menu.api.dupe.DupeManager; import fr.maxlego08.menu.api.enchantment.Enchantments; import fr.maxlego08.menu.api.font.FontImage; @@ -172,7 +173,9 @@ public void onEnable() { ConfigManager configManager = new ConfigManager(this); this.dialogManager = new ZDialogManager(this, configManager); servicesManager.register(DialogManager.class, this.dialogManager, this, ServicePriority.Highest); - configManager.registerConfig(Config.class, this); + ConfigDialogBuilder configDialogBuilder = new ConfigDialogBuilder("zMenu Config", "zMenu Configuration"); + Logger.info(configDialogBuilder.getName()); + configManager.registerConfig(configDialogBuilder,Config.class, this); } this.registerInventory(EnumInventory.INVENTORY_DEFAULT, new InventoryDefault()); From 31b6e5c9d0384757c4e6424997147407be33a7b7 Mon Sep 17 00:00:00 2001 From: 1robie <97293924+1robie@users.noreply.github.com> Date: Sat, 18 Oct 2025 22:36:53 +0200 Subject: [PATCH 7/9] chore: update Paper and Spigot API dependencies to 1.21.10 --- Hooks/Paper/build.gradle.kts | 2 +- build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Hooks/Paper/build.gradle.kts b/Hooks/Paper/build.gradle.kts index 00904e55..a9492a11 100644 --- a/Hooks/Paper/build.gradle.kts +++ b/Hooks/Paper/build.gradle.kts @@ -7,5 +7,5 @@ repositories { dependencies { compileOnly(projects.api) compileOnly("net.kyori:adventure-text-minimessage:4.21.0") - compileOnly("io.papermc.paper:paper-api:1.21.8-R0.1-SNAPSHOT") + compileOnly("io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT") } \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 119c4767..dd75c905 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -80,7 +80,7 @@ allprojects { dependencies { if (project.name != "Paper") { - compileOnly("org.spigotmc:spigot-api:1.21.8-R0.1-SNAPSHOT") + compileOnly("org.spigotmc:spigot-api:1.21.10-R0.1-SNAPSHOT") } compileOnly("com.mojang:authlib:1.5.26") compileOnly("me.clip:placeholderapi:2.11.6") From 7e29cd3870908ec7ebed73baf8a6f3d469d79dc5 Mon Sep 17 00:00:00 2001 From: 1robie Date: Sat, 1 Nov 2025 21:22:08 +0100 Subject: [PATCH 8/9] feat: add player commands as OP action and BreweryX support - Add OpGrantMethod enum with ATTACHMENT and SET_OP strategies - Add enable-player-commands-as-op-action config (disabled by default) - Add op-grant-method config to choose elevation strategy - Add BreweryX material format: "breweryx::" --- .../menu/api/configuration/Config.java | 26 +++++++++++++- .../menu/api/utils/OpGrantMethod.java | 34 +++++++++++++++++++ Hooks/BreweryX/build.gradle.kts | 10 ++++++ .../maxlego08/menu/hooks/BreweryXLoader.java | 26 ++++++++++++++ .../fr/maxlego08/menu/ZInventoryManager.java | 6 ++-- .../java/fr/maxlego08/menu/ZMenuPlugin.java | 3 ++ .../actions/PlayerCommandAsOPAction.java | 11 ++---- .../menu/zcore/utils/nms/NmsVersion.java | 2 ++ .../menu/zcore/utils/plugins/Plugins.java | 4 ++- src/main/resources/config.yml | 12 +++++++ src/main/resources/plugin.yml | 1 + 11 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 API/src/main/java/fr/maxlego08/menu/api/utils/OpGrantMethod.java create mode 100644 Hooks/BreweryX/build.gradle.kts create mode 100644 Hooks/BreweryX/src/main/java/fr/maxlego08/menu/hooks/BreweryXLoader.java diff --git a/API/src/main/java/fr/maxlego08/menu/api/configuration/Config.java b/API/src/main/java/fr/maxlego08/menu/api/configuration/Config.java index 55daf195..1038a7b3 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/configuration/Config.java +++ b/API/src/main/java/fr/maxlego08/menu/api/configuration/Config.java @@ -3,6 +3,8 @@ import fr.maxlego08.menu.api.configuration.annotation.ConfigOption; import fr.maxlego08.menu.api.configuration.annotation.ConfigUpdate; import fr.maxlego08.menu.api.enums.DialogInputType; +import fr.maxlego08.menu.api.utils.OpGrantMethod; +import fr.maxlego08.menu.zcore.logger.Logger; import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.event.inventory.ClickType; @@ -271,6 +273,17 @@ public class Config { ) public static boolean enablePlayerOpenInventoryLogs = true; + @ConfigOption( + key = "enablePlayerCommandsAsOPAction", + type = DialogInputType.BOOLEAN, + trueText = "Enabled", + falseText = "Disabled", + label = "Enable player commands as OP action (requires server restart)" + ) + public static boolean enablePlayerCommandsAsOPAction = false; + + public static OpGrantMethod opGrantMethod = OpGrantMethod.ATTACHMENT; + /** * static Singleton instance. */ @@ -349,6 +362,14 @@ public void load(FileConfiguration configuration) { enableDownloadCommand = configuration.getBoolean(ConfigPath.ENABLE_DOWNLOAD_COMMAND.getPath()); enablePlayerOpenInventoryLogs = configuration.getBoolean(ConfigPath.ENABLE_PLAYER_OPEN_INVENTORY_LOGS.getPath()); + + enablePlayerCommandsAsOPAction = configuration.getBoolean(ConfigPath.ENABLE_PLAYER_COMMANDS_AS_OP_ACTION.getPath()); + try { + opGrantMethod = OpGrantMethod.valueOf(configuration.getString(ConfigPath.OP_GRANT_METHOD.getPath(), OpGrantMethod.ATTACHMENT.name()).toUpperCase()); + } catch (IllegalArgumentException e) { + Logger.info("Invalid op grant method in config, defaulting to ATTACHMENT."); + opGrantMethod = OpGrantMethod.ATTACHMENT; + } } public void save(FileConfiguration configuration, File file) { @@ -438,7 +459,10 @@ private enum ConfigPath { ENABLE_CACHE_PLACEHOLDER_API("enable-cache-placeholder-api"), ENABLE_DOWNLOAD_COMMAND("enable-download-command"), - ENABLE_PLAYER_OPEN_INVENTORY_LOGS("enable-player-open-inventory-logs") + ENABLE_PLAYER_OPEN_INVENTORY_LOGS("enable-player-open-inventory-logs"), + + ENABLE_PLAYER_COMMANDS_AS_OP_ACTION("enable-player-commands-as-op-action"), + OP_GRANT_METHOD("op-grant-method") ; private final String path; diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/OpGrantMethod.java b/API/src/main/java/fr/maxlego08/menu/api/utils/OpGrantMethod.java new file mode 100644 index 00000000..6e309919 --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/OpGrantMethod.java @@ -0,0 +1,34 @@ +package fr.maxlego08.menu.api.utils; + +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +public enum OpGrantMethod { + ATTACHMENT { + @Override + public void execute(@NotNull Player player, @NotNull Plugin plugin, @NotNull Runnable action) { + var attachment = player.addAttachment(plugin); + try { + attachment.setPermission("*", true); + action.run(); + } finally { + player.removeAttachment(attachment); + } + } + }, + SET_OP { + @Override + public void execute(@NotNull Player player, @NotNull Plugin plugin, @NotNull Runnable action) { + boolean wasOp = player.isOp(); + try { + player.setOp(true); + action.run(); + } finally { + player.setOp(wasOp); + } + } + }; + + public abstract void execute(Player player, Plugin plugin, Runnable action); +} diff --git a/Hooks/BreweryX/build.gradle.kts b/Hooks/BreweryX/build.gradle.kts new file mode 100644 index 00000000..08ef97ef --- /dev/null +++ b/Hooks/BreweryX/build.gradle.kts @@ -0,0 +1,10 @@ +group = "Hooks:BreweryX" + +repositories { + maven("https://repo.jsinco.dev/releases") +} + +dependencies { + compileOnly(projects.api) + compileOnly("com.dre.brewery:BreweryX:3.6.0") +} diff --git a/Hooks/BreweryX/src/main/java/fr/maxlego08/menu/hooks/BreweryXLoader.java b/Hooks/BreweryX/src/main/java/fr/maxlego08/menu/hooks/BreweryXLoader.java new file mode 100644 index 00000000..bed62951 --- /dev/null +++ b/Hooks/BreweryX/src/main/java/fr/maxlego08/menu/hooks/BreweryXLoader.java @@ -0,0 +1,26 @@ +package fr.maxlego08.menu.hooks; + +import com.dre.brewery.api.BreweryApi; +import fr.maxlego08.menu.api.loader.MaterialLoader; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class BreweryXLoader extends MaterialLoader { + public BreweryXLoader(){ + super("breweryx"); + } + + @Override + public ItemStack load(Player player, YamlConfiguration configuration, String path, String materialString) { + String[] parts = materialString.split(":",2); + int quality; + try { + quality = Integer.parseInt(parts[1]); + } catch (Exception e){ + quality = 1; + } + + return BreweryApi.createBrewItem(parts[0],quality,player); + } +} diff --git a/src/main/java/fr/maxlego08/menu/ZInventoryManager.java b/src/main/java/fr/maxlego08/menu/ZInventoryManager.java index 21d16e12..7ded528e 100644 --- a/src/main/java/fr/maxlego08/menu/ZInventoryManager.java +++ b/src/main/java/fr/maxlego08/menu/ZInventoryManager.java @@ -20,8 +20,8 @@ import fr.maxlego08.menu.api.loader.NoneLoader; import fr.maxlego08.menu.api.utils.*; import fr.maxlego08.menu.button.buttons.ZNoneButton; -import fr.maxlego08.menu.button.loader.BackLoader; import fr.maxlego08.menu.button.loader.*; +import fr.maxlego08.menu.button.loader.BackLoader; import fr.maxlego08.menu.command.validators.*; import fr.maxlego08.menu.hooks.dialogs.loader.body.ItemBodyLoader; import fr.maxlego08.menu.hooks.dialogs.loader.body.PlainMessageBodyLoader; @@ -331,7 +331,9 @@ public void loadButtons() { buttonManager.registerAction(new DataLoader(this.plugin)); buttonManager.registerAction(new fr.maxlego08.menu.loader.actions.InventoryLoader(this.plugin)); buttonManager.registerAction(new ChatLoader()); - buttonManager.registerAction(new PlayerCommandAsOPLoader()); + if (Config.enablePlayerCommandsAsOPAction){ // Disabled by default for security reasons + buttonManager.registerAction(new PlayerCommandAsOPLoader()); + } buttonManager.registerAction(new PlayerCommandLoader()); buttonManager.registerAction(new ConsoleCommandLoader()); buttonManager.registerAction(new fr.maxlego08.menu.loader.actions.BackLoader(this.plugin)); diff --git a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java index 00527dfc..ec800f59 100644 --- a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java +++ b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java @@ -299,6 +299,9 @@ private void registerHooks() { this.inventoryManager.registerMaterialLoader(new MythicMobsItemsLoader()); this.addListener(new MythicManager(this)); } + if (this.isActive(Plugins.BREWERYX)){ + this.inventoryManager.registerMaterialLoader(new BreweryXLoader()); + } } private List getInventoriesFiles() { diff --git a/src/main/java/fr/maxlego08/menu/requirement/actions/PlayerCommandAsOPAction.java b/src/main/java/fr/maxlego08/menu/requirement/actions/PlayerCommandAsOPAction.java index c9f7b72e..c579214d 100644 --- a/src/main/java/fr/maxlego08/menu/requirement/actions/PlayerCommandAsOPAction.java +++ b/src/main/java/fr/maxlego08/menu/requirement/actions/PlayerCommandAsOPAction.java @@ -1,6 +1,7 @@ package fr.maxlego08.menu.requirement.actions; import fr.maxlego08.menu.api.button.Button; +import fr.maxlego08.menu.api.configuration.Config; import fr.maxlego08.menu.api.engine.InventoryEngine; import fr.maxlego08.menu.api.utils.Placeholders; import org.bukkit.Bukkit; @@ -21,14 +22,8 @@ protected void execute(Player player, Button button, InventoryEngine inventory, var scheduler = inventory.getPlugin().getScheduler(); scheduler.runAtEntity(player, w -> papi(placeholders.parse(this.parseAndFlattenCommands(this.commands, player)), player).forEach(command -> { command = command.replace("%player%", player.getName()); - var plugin = inventory.getPlugin(); - var attachment = player.addAttachment(plugin); - try { - attachment.setPermission("*", true); - Bukkit.dispatchCommand(player, command); - } finally { - player.removeAttachment(attachment); - } + String finalCommand = command; + Config.opGrantMethod.execute(player, inventory.getPlugin(), () -> Bukkit.dispatchCommand(player, finalCommand)); })); } } diff --git a/src/main/java/fr/maxlego08/menu/zcore/utils/nms/NmsVersion.java b/src/main/java/fr/maxlego08/menu/zcore/utils/nms/NmsVersion.java index cec835dd..831941cd 100644 --- a/src/main/java/fr/maxlego08/menu/zcore/utils/nms/NmsVersion.java +++ b/src/main/java/fr/maxlego08/menu/zcore/utils/nms/NmsVersion.java @@ -55,6 +55,8 @@ public enum NmsVersion { V_1_21_6(1216), V_1_21_7(1217), V_1_21_8(1218), + V_1_21_9(1219), + V_1_21_10(1220) ; diff --git a/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java b/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java index 6709e388..3c07b830 100644 --- a/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java +++ b/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java @@ -26,7 +26,9 @@ public enum Plugins { MAGICCOSMETICS("MagicCosmetics"), NEXTGENS("NextGens"), MYTHICMOBS("MythicMobs"), - ZMENUPLUS("zMenuPlus"); + ZMENUPLUS("zMenuPlus"), + BREWERYX("BreweryX") + ; private final String name; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 221d6caf..833b6fda 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -66,6 +66,18 @@ enable-mini-message-format: true # Prevents players from executing commands from the console. Useful for "fake" commands not registered in Spigot. enable-player-command-in-chat: false +# Enable player commands as OP action. +# WARNING: This is a security-sensitive feature. Only enable if you understand the risks. +# Allows buttons to execute commands with elevated permissions (OP or permission *). +# Default is disabled for security reasons. Requires server restart to take effect. +enable-player-commands-as-op-action: false + +# Method used to grant temporary elevated permissions. +# ATTACHMENT: Grants permission * temporarily (safer, recommended) +# SET_OP: Temporarily sets player as OP (use with caution) +# This setting only applies when enable-player-commands-as-op-action is true. +op-grant-method: ATTACHMENT + # Enable FastEvent system. # Replaces some Bukkit events with a faster alternative. Enables better performance at the cost of API changes. # Refer to documentation before enabling this. diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index e3f4215c..4bfb147c 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -21,6 +21,7 @@ softdepend: - ExecutableItems - ExecutableBlocks - MythicMobs + - BreweryX folia-supported: true loadbefore: - SuperiorSkyblock2 From cdf5b34e0641e232e478e6e1803fbf37bbd20dfd Mon Sep 17 00:00:00 2001 From: 1robie Date: Sat, 1 Nov 2025 21:45:22 +0100 Subject: [PATCH 9/9] feat: add BOTH method for op-granting in config and OpGrantMethod --- .../menu/api/utils/OpGrantMethod.java | 18 +++++++++++++++++- src/main/resources/config.yml | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/OpGrantMethod.java b/API/src/main/java/fr/maxlego08/menu/api/utils/OpGrantMethod.java index 6e309919..481e9fb9 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/utils/OpGrantMethod.java +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/OpGrantMethod.java @@ -28,7 +28,23 @@ public void execute(@NotNull Player player, @NotNull Plugin plugin, @NotNull Run player.setOp(wasOp); } } - }; + }, + BOTH { + @Override + public void execute(@NotNull Player player, @NotNull Plugin plugin, @NotNull Runnable action) { + boolean wasOp = player.isOp(); + var attachment = player.addAttachment(plugin); + try { + player.setOp(true); + attachment.setPermission("*", true); + action.run(); + } finally { + player.removeAttachment(attachment); + player.setOp(wasOp); + } + } + } + ; public abstract void execute(Player player, Plugin plugin, Runnable action); } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 833b6fda..9d7b87be 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -75,6 +75,7 @@ enable-player-commands-as-op-action: false # Method used to grant temporary elevated permissions. # ATTACHMENT: Grants permission * temporarily (safer, recommended) # SET_OP: Temporarily sets player as OP (use with caution) +# BOTH: Combines both methods (maximum compatibility, use with extreme caution) # This setting only applies when enable-player-commands-as-op-action is true. op-grant-method: ATTACHMENT