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/configuration/Config.java b/API/src/main/java/fr/maxlego08/menu/api/configuration/Config.java index f7e6423d..ade113eb 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,9 +1,10 @@ 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; +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; @@ -274,6 +275,18 @@ public class Config { label = "Enable player open inventory logs" ) 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; + public static boolean enableToast = true; @ConfigUpdate public static boolean updated = false; @@ -351,6 +364,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; + } enableToast = configuration.getBoolean(ConfigPath.ENABLE_TOAST.getPath()); } @@ -443,7 +464,11 @@ private enum ConfigPath { ENABLE_DOWNLOAD_COMMAND("enable-download-command"), 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"), ENABLE_TOAST("enable-toast"); + private final String path; ConfigPath(String path) { 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/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..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 @@ -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 :"), @@ -97,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)"), @@ -110,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."), @@ -252,6 +258,4 @@ public ItemStack getItemStack() { public void setItemStack(ItemStack itemStack) { this.itemStack = itemStack; } - } - 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..481e9fb9 --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/OpGrantMethod.java @@ -0,0 +1,50 @@ +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); + } + } + }, + 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/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/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/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/build.gradle.kts b/build.gradle.kts index c35bba9b..820e1e33 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" @@ -82,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") diff --git a/src/main/java/fr/maxlego08/menu/ZInventoryManager.java b/src/main/java/fr/maxlego08/menu/ZInventoryManager.java index 9e891466..55d536bc 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; @@ -357,7 +357,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/ZItemManager.java b/src/main/java/fr/maxlego08/menu/ZItemManager.java new file mode 100644 index 00000000..ff7de597 --- /dev/null +++ b/src/main/java/fr/maxlego08/menu/ZItemManager.java @@ -0,0 +1,250 @@ +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(); + 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); + 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 62d49874..0a52c718 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; @@ -36,6 +37,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 +98,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; @@ -170,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()); @@ -181,6 +186,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 +198,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) -> { @@ -325,6 +332,9 @@ private void registerHooks() { this.addListener(new MythicManager(this)); this.getLogger().info("Registered MythicMobs material loader and listener"); } + if (this.isActive(Plugins.BREWERYX)){ + this.inventoryManager.registerMaterialLoader(new BreweryXLoader()); + } } @@ -352,6 +362,8 @@ private List getInventoriesFiles() { files.add("dialogs/server_link-dialog.yml"); } + files.add("items/default-items.yml"); + return files; } @@ -367,6 +379,7 @@ public void onDisable() { if (Token.token != null) { Token.getInstance().save(this.getPersist()); } + this.itemManager.unloadListeners(); // this.packetUtils.onDisable(); this.postDisable(); @@ -416,6 +429,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..c5f99562 --- /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.setDescription(Message.DESCRIPTION_GIVE_ITEM); + 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, Message.GIVE_ITEM_NOT_FOUND, "%itemId%", itemId); + return CommandType.DEFAULT; + } + this.itemManager.giveItem(target, itemId); + if (target.equals(this.player)) { + message(plugin, sender, Message.GIVE_ITEM_SUCCESS_SELF, "%itemId%", itemId); + } else { + message(plugin, sender, Message.GIVE_ITEM_SUCCESS_OTHER, "%itemId%", itemId, "%player%", target.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 542105a9..bc77dd01 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,6 +2,7 @@ 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; @@ -9,6 +10,9 @@ import fr.maxlego08.menu.zcore.enums.Permission; import fr.maxlego08.menu.zcore.utils.ZUtils; import fr.maxlego08.menu.zcore.utils.commands.CommandType; +import org.bukkit.entity.Player; + +import java.util.Collection; public class CommandMenuReload extends VCommand { @@ -45,6 +49,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/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/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; 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 b64811af..8ffbe2cc 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..9d7b87be 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -66,6 +66,19 @@ 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) +# 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 + # 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/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 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