From 147173c9b67a260d44cef2b37ffb865fb7f397ed Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Sun, 17 Nov 2024 01:11:01 +0100 Subject: [PATCH 01/25] Added has permissions requirement type --- .../deluxemenus/config/DeluxeMenusConfig.java | 13 +++++++ .../HasPermissionsRequirement.java | 34 +++++++++++++++++++ .../requirement/RequirementType.java | 6 ++++ 3 files changed, 53 insertions(+) create mode 100644 src/main/java/com/extendedclip/deluxemenus/requirement/HasPermissionsRequirement.java diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index d7e24d72..1f925880 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -1142,6 +1142,19 @@ private RequirementList getRequirements(FileConfiguration c, String path) { ); } break; + case HAS_PERMISSIONS: + case DOES_NOT_HAVE_PERMISSIONS: + if (c.contains(rPath + ".permissions")) { + invert = type == RequirementType.DOES_NOT_HAVE_PERMISSIONS; + req = new HasPermissionsRequirement(c.getStringList(rPath + ".permissions"), c.getInt(rPath + ".minimum", -1), invert); + } else { + DeluxeMenus.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Has Permission requirement at path: " + rPath + " does not contain a permission: entry" + ); + } + break; case JAVASCRIPT: if (c.contains(rPath + ".expression")) { req = new JavascriptRequirement(c.getString(rPath + ".expression")); diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/HasPermissionsRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/HasPermissionsRequirement.java new file mode 100644 index 00000000..6e0465db --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/HasPermissionsRequirement.java @@ -0,0 +1,34 @@ +package com.extendedclip.deluxemenus.requirement; + +import com.extendedclip.deluxemenus.menu.MenuHolder; + +import java.util.List; + +public class HasPermissionsRequirement extends Requirement { + + private final List permissions; + private final int minimum; + private final boolean invert; + + public HasPermissionsRequirement(List permissions, int minimum, boolean invert) { + this.permissions = permissions; + this.minimum = minimum; + this.invert = invert; + } + + @Override + public boolean evaluate(MenuHolder holder) { + int amount = 0; + for (String permission : permissions) { + String check = holder.setPlaceholdersAndArguments(permission); + if (holder.getViewer().hasPermission(check)) { + ++amount; + continue; + } + if (minimum == -1) return invert; + } + if (invert) return permissions.size()-amount >= minimum; + return amount >= minimum; + } + +} diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/RequirementType.java b/src/main/java/com/extendedclip/deluxemenus/requirement/RequirementType.java index aeb15ae6..d1787a64 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/RequirementType.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/RequirementType.java @@ -41,6 +41,12 @@ public enum RequirementType { Arrays.asList("!has permission", "!has perm", "!haspermission", "!hasperm", "!perm"), "Checks if a player does not have a specific permission", Collections.singletonList("permission")), + HAS_PERMISSIONS(Arrays.asList("has permissions", "has perms", "haspermissions", "hasperms", "perms"), + "Checks if a player has a set amount of permissions", Collections.singletonList("permissions")), + DOES_NOT_HAVE_PERMISSIONS( + Arrays.asList("!has permissions", "!has perms", "!haspermissions", "!hasperms", "!perms"), + "Checks if a player does not have a set amount of permission", + Collections.singletonList("permissions")), STRING_CONTAINS(Arrays.asList("string contains", "stringcontains", "contains"), "Checks if a string contains another string", Arrays.asList("input", "output")), STRING_DOES_NOT_CONTAIN(Arrays.asList("!string contains", "!stringcontains", "!contains"), From 3ae385c7edea7bd5c86d5cece11d708c0ef75d5f Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Sun, 17 Nov 2024 15:42:57 +0100 Subject: [PATCH 02/25] Check for Has Permissions requirement minimum being lower than 1 --- .../deluxemenus/config/DeluxeMenusConfig.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index 1f925880..97ead7cf 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -1146,12 +1146,21 @@ private RequirementList getRequirements(FileConfiguration c, String path) { case DOES_NOT_HAVE_PERMISSIONS: if (c.contains(rPath + ".permissions")) { invert = type == RequirementType.DOES_NOT_HAVE_PERMISSIONS; - req = new HasPermissionsRequirement(c.getStringList(rPath + ".permissions"), c.getInt(rPath + ".minimum", -1), invert); + int minimum = -1; + if (c.contains(rPath + ".minimum") && (minimum = c.getInt(rPath + ".minimum")) < 1) { + DeluxeMenus.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Has Permissions requirement at path: " + rPath + " has a minimum lower than 1. All permissions will be checked" + ); + minimum = -1; + } + req = new HasPermissionsRequirement(c.getStringList(rPath + ".permissions"), minimum, invert); } else { DeluxeMenus.debug( DebugLevel.HIGHEST, Level.WARNING, - "Has Permission requirement at path: " + rPath + " does not contain a permission: entry" + "Has Permissions requirement at path: " + rPath + " does not contain permissions: entry" ); } break; From a0ffd8da1344f4a361b9f1931cd699b41646b848 Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Sun, 17 Nov 2024 20:02:10 +0100 Subject: [PATCH 03/25] Check for items from supported ItemHooks (added MythicLib dependency for MMOItems) --- build.gradle.kts | 1 + gradle/libs.versions.toml | 4 +++- .../deluxemenus/config/DeluxeMenusConfig.java | 11 ++++++--- .../deluxemenus/hooks/BaseHeadHook.java | 10 ++++++++ .../hooks/ExecutableBlocksHook.java | 5 ++++ .../hooks/ExecutableItemsHook.java | 12 ++++++++++ .../deluxemenus/hooks/HeadDatabaseHook.java | 8 +++++++ .../deluxemenus/hooks/ItemHook.java | 2 ++ .../deluxemenus/hooks/ItemsAdderHook.java | 9 ++++++++ .../deluxemenus/hooks/MMOItemsHook.java | 10 ++++++++ .../deluxemenus/hooks/NamedHeadHook.java | 23 +++++++++++++++++++ .../deluxemenus/hooks/OraxenHook.java | 8 +++++++ .../deluxemenus/hooks/TextureHeadHook.java | 10 ++++++++ .../requirement/HasItemRequirement.java | 19 +++++++++++---- 14 files changed, 123 insertions(+), 9 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 696f347d..fea4ebfa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { compileOnly(libs.headdb) compileOnly(libs.itemsadder) compileOnly(libs.oraxen) + compileOnly(libs.mythiclib) compileOnly(libs.mmoitems) compileOnly(libs.score) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 68878e43..ef109123 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,8 @@ authlib = "1.5.25" headdb = "1.3.1" itemsadder = "3.2.5" oraxen = "1.159.0" -mmoitems = "6.9.4-SNAPSHOT" +mythiclib = "1.6.2-SNAPSHOT" +mmoitems = "6.9.5-SNAPSHOT" papi = "2.11.6" score = "4.23.10.8" @@ -23,6 +24,7 @@ authlib = { module = "com.mojang:authlib", version.ref = "authlib" } headdb = { module = "com.arcaniax:HeadDatabase-API", version.ref = "headdb" } itemsadder = { module = "com.github.LoneDev6:api-itemsadder", version.ref = "itemsadder" } oraxen = { module = "com.github.oraxen:oraxen", version.ref = "oraxen" } +mythiclib = { module = "io.lumine:MythicLib-dist", version.ref = "mythiclib"} mmoitems = { module = "net.Indyuce:MMOItems-API", version.ref = "mmoitems" } papi = { module = "me.clip:placeholderapi", version.ref = "papi" } score = { module = "com.github.Ssomar-Developement:SCore", version.ref = "score" } diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index d7e24d72..54ccdda7 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -1056,9 +1056,14 @@ private RequirementList getRequirements(FileConfiguration c, String path) { case DOES_NOT_HAVE_ITEM: ItemWrapper wrapper = new ItemWrapper(); if (c.contains(rPath + ".material")) { + String materialName = c.getString(rPath + ".material"); try { - if (!containsPlaceholders(c.getString(rPath + ".material"))) - Material.valueOf(c.getString(rPath + ".material").toUpperCase()); + if (!containsPlaceholders(materialName) && plugin.getItemHooks().values() + .stream() + .filter(x -> materialName.startsWith(x.getPrefix())) + .findFirst() + .orElse(null) == null) + Material.valueOf(materialName.toUpperCase()); } catch (Exception ex) { DeluxeMenus.debug( DebugLevel.HIGHEST, @@ -1067,7 +1072,7 @@ private RequirementList getRequirements(FileConfiguration c, String path) { ); break; } - wrapper.setMaterial(c.getString(rPath + ".material")); + wrapper.setMaterial(materialName); } else { DeluxeMenus.debug( DebugLevel.HIGHEST, diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java index 778f0e70..d3d63e5b 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.bukkit.Bukkit; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -31,6 +32,15 @@ public ItemStack getItem(@NotNull final String... arguments) { return DeluxeMenus.getInstance().getHead().clone(); } + @Override + public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + if (arguments.length == 0) { + return false; + } + ItemStack skull = SkullUtils.getSkullByBase64EncodedTextureUrl(arguments[0]); + return Bukkit.getItemFactory().equals(item.getItemMeta(), skull.getItemMeta()); + } + @Override public String getPrefix() { return "basehead-"; diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableBlocksHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableBlocksHook.java index d0942d64..de00e36e 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableBlocksHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableBlocksHook.java @@ -28,6 +28,11 @@ public ItemStack getItem(@NotNull String... arguments) { return (item == null) ? new ItemStack(Material.STONE) : item.clone(); } + @Override + public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + return false; + } + @Override public String getPrefix() { return "executableblocks-"; diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableItemsHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableItemsHook.java index 553b61dc..c72b8473 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableItemsHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableItemsHook.java @@ -5,6 +5,8 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; + +import com.ssomar.score.api.executableitems.config.ExecutableItemInterface; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -31,6 +33,16 @@ public ItemStack getItem(@NotNull String... arguments) { return (item == null) ? new ItemStack(Material.STONE) : item.clone(); } + @Override + public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + if (arguments.length == 0) { + return false; + } + ExecutableItemInterface fromId = ExecutableItemsAPI.getExecutableItemsManager().getExecutableItem(arguments[0]).orElse(null); + ExecutableItemInterface fromItem = ExecutableItemsAPI.getExecutableItemsManager().getExecutableItem(item).orElse(null); + return fromItem != null && fromItem.equals(fromId); + } + @Override public String getPrefix() { return "executableitems-"; diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/HeadDatabaseHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/HeadDatabaseHook.java index 32e8c884..360d8020 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/HeadDatabaseHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/HeadDatabaseHook.java @@ -32,6 +32,14 @@ public ItemStack getItem(@NotNull final String... arguments) { return DeluxeMenus.getInstance().getHead().clone(); } + @Override + public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + if (arguments.length == 0) { + return false; + } + return arguments[0].equalsIgnoreCase(api.getItemID(item)); + } + @Override public String getPrefix() { return "hdb-"; diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/ItemHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/ItemHook.java index b0c49fa5..3a3ddde7 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/ItemHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/ItemHook.java @@ -7,6 +7,8 @@ public interface ItemHook { ItemStack getItem(@NotNull final String... arguments); + boolean isItem(@NotNull ItemStack item, @NotNull final String... arguments); + String getPrefix(); } diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/ItemsAdderHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/ItemsAdderHook.java index 98823237..a602ee9d 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/ItemsAdderHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/ItemsAdderHook.java @@ -36,6 +36,15 @@ public ItemStack getItem(@NotNull final String... arguments) { return item.clone(); } + @Override + public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + if (arguments.length == 0) { + return false; + } + CustomStack stack = CustomStack.byItemStack(item); + return stack != null && stack.getId().equalsIgnoreCase(arguments[0]); + } + @Override public String getPrefix() { return "itemsadder-"; diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java index 588a9ec0..e8753295 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java @@ -60,6 +60,16 @@ public ItemStack getItem(@NotNull final String... arguments) { return mmoItem == null ? new ItemStack(Material.STONE, 1) : mmoItem; } + @Override + public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + if (arguments.length == 0) { + return false; + } + String[] splitArgs = arguments[0].split(":"); + if (splitArgs.length != 2) return false; + return splitArgs[0].equalsIgnoreCase(MMOItems.getTypeName(item)) && splitArgs[1].equalsIgnoreCase(MMOItems.getID(item)); + } + @Override public String getPrefix() { return "mmoitems-"; diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java index 0d02f9cd..67d3b4b6 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java @@ -6,10 +6,14 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import com.extendedclip.deluxemenus.utils.VersionHelper; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; import org.jetbrains.annotations.NotNull; public class NamedHeadHook implements ItemHook, Listener, SimpleCache { @@ -39,6 +43,25 @@ public ItemStack getItem(@NotNull final String... arguments) { return DeluxeMenus.getInstance().getHead().clone(); } + @Override + public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + if (arguments.length == 0) { + return false; + } + if (!(item.getItemMeta() instanceof SkullMeta)) return false; + final SkullMeta headMeta = (SkullMeta) item.getItemMeta(); + + String owner; + if (!VersionHelper.IS_SKULL_OWNER_LEGACY) { + if (headMeta.getOwningPlayer() == null) return false; + owner = headMeta.getOwningPlayer().getName(); + } else { + owner = headMeta.getOwner(); + if (owner == null) return false; + } + return arguments[0].equalsIgnoreCase(owner); + } + @EventHandler(ignoreCancelled = true) public void onPlayerQuit(final PlayerQuitEvent event) { cache.remove(event.getPlayer().getName()); diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/OraxenHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/OraxenHook.java index 98a56aa5..07f13031 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/OraxenHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/OraxenHook.java @@ -28,6 +28,14 @@ public ItemStack getItem(@NotNull String... arguments) { return (item == null) ? new ItemStack(Material.STONE) : item.clone(); } + @Override + public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + if (arguments.length == 0) { + return false; + } + return arguments[0].equalsIgnoreCase(OraxenItems.getIdByItem(item)); + } + @Override public String getPrefix() { return "oraxen-"; diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java index c3c1917e..52ab4aa3 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.bukkit.Bukkit; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -31,6 +32,15 @@ public ItemStack getItem(@NotNull final String... arguments) { return DeluxeMenus.getInstance().getHead().clone(); } + @Override + public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + if (arguments.length == 0) { + return false; + } + ItemStack skull = SkullUtils.getSkullByBase64EncodedTextureUrl(arguments[0]); + return Bukkit.getItemFactory().equals(item.getItemMeta(), skull.getItemMeta()); + } + @Override public String getPrefix() { return "texture-"; diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java index d2967197..364f89a2 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java @@ -1,6 +1,7 @@ package com.extendedclip.deluxemenus.requirement; import com.extendedclip.deluxemenus.DeluxeMenus; +import com.extendedclip.deluxemenus.hooks.ItemHook; import com.extendedclip.deluxemenus.menu.MenuHolder; import com.extendedclip.deluxemenus.requirement.wrappers.ItemWrapper; import com.extendedclip.deluxemenus.utils.StringUtils; @@ -25,8 +26,14 @@ public HasItemRequirement(ItemWrapper wrapper, boolean invert) { public boolean evaluate(MenuHolder holder) { String materialName = holder.setPlaceholdersAndArguments(wrapper.getMaterial()).toUpperCase(); Material material = DeluxeMenus.MATERIALS.get(materialName); + ItemHook pluginHook = null; if (material == null) { - return invert; + pluginHook = DeluxeMenus.getInstance().getItemHooks().values() + .stream() + .filter(x -> materialName.startsWith(x.getPrefix())) + .findFirst() + .orElse(null); + if (pluginHook == null) return invert; } if (material == Material.AIR) return invert == (holder.getViewer().getInventory().firstEmpty() == -1); @@ -37,20 +44,20 @@ public boolean evaluate(MenuHolder holder) { int total = 0; for (ItemStack itemToCheck: inventory) { - if (!isRequiredItem(itemToCheck, holder, material)) continue; + if (!isRequiredItem(itemToCheck, holder, material, pluginHook)) continue; total += itemToCheck.getAmount(); } if (offHand != null) { for (ItemStack itemToCheck: offHand) { - if (!isRequiredItem(itemToCheck, holder, material)) continue; + if (!isRequiredItem(itemToCheck, holder, material, pluginHook)) continue; total += itemToCheck.getAmount(); } } if (armor != null) { for (ItemStack itemToCheck: armor) { - if (!isRequiredItem(itemToCheck, holder, material)) continue; + if (!isRequiredItem(itemToCheck, holder, material, pluginHook)) continue; total += itemToCheck.getAmount(); } } @@ -58,8 +65,10 @@ public boolean evaluate(MenuHolder holder) { return invert == (total < wrapper.getAmount()); } - private boolean isRequiredItem(ItemStack itemToCheck, MenuHolder holder, Material material) { + private boolean isRequiredItem(ItemStack itemToCheck, MenuHolder holder, Material material, ItemHook pluginHook) { if (itemToCheck == null || itemToCheck.getType() == Material.AIR) return false; + + if (pluginHook != null && !pluginHook.isItem(itemToCheck, holder.setPlaceholdersAndArguments(wrapper.getMaterial().substring(pluginHook.getPrefix().length())))) return false; if (wrapper.getMaterial() != null && itemToCheck.getType() != material) return false; if (wrapper.hasData() && itemToCheck.getDurability() != wrapper.getData()) return false; From adb52a14bb47bfadccdabb81b9c3e99d72db9026 Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Sun, 17 Nov 2024 20:28:53 +0100 Subject: [PATCH 04/25] Fixed itemhooks check (BaseHead & TextureHead not yet functional) --- .../extendedclip/deluxemenus/hooks/BaseHeadHook.java | 2 +- .../deluxemenus/hooks/TextureHeadHook.java | 2 +- .../deluxemenus/requirement/HasItemRequirement.java | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java index d3d63e5b..46e1a6dd 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java @@ -37,7 +37,7 @@ public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { if (arguments.length == 0) { return false; } - ItemStack skull = SkullUtils.getSkullByBase64EncodedTextureUrl(arguments[0]); + ItemStack skull = cache.computeIfAbsent(arguments[0], SkullUtils::getSkullByBase64EncodedTextureUrl).clone(); return Bukkit.getItemFactory().equals(item.getItemMeta(), skull.getItemMeta()); } diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java index 52ab4aa3..bd421b9c 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java @@ -37,7 +37,7 @@ public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { if (arguments.length == 0) { return false; } - ItemStack skull = SkullUtils.getSkullByBase64EncodedTextureUrl(arguments[0]); + ItemStack skull = cache.computeIfAbsent(arguments[0], key -> SkullUtils.getSkullByBase64EncodedTextureUrl(SkullUtils.getEncoded(key))).clone(); return Bukkit.getItemFactory().equals(item.getItemMeta(), skull.getItemMeta()); } diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java index 364f89a2..f1878195 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java @@ -24,8 +24,8 @@ public HasItemRequirement(ItemWrapper wrapper, boolean invert) { @Override public boolean evaluate(MenuHolder holder) { - String materialName = holder.setPlaceholdersAndArguments(wrapper.getMaterial()).toUpperCase(); - Material material = DeluxeMenus.MATERIALS.get(materialName); + String materialName = holder.setPlaceholdersAndArguments(wrapper.getMaterial()); + Material material = DeluxeMenus.MATERIALS.get(materialName.toUpperCase()); ItemHook pluginHook = null; if (material == null) { pluginHook = DeluxeMenus.getInstance().getItemHooks().values() @@ -68,8 +68,10 @@ public boolean evaluate(MenuHolder holder) { private boolean isRequiredItem(ItemStack itemToCheck, MenuHolder holder, Material material, ItemHook pluginHook) { if (itemToCheck == null || itemToCheck.getType() == Material.AIR) return false; - if (pluginHook != null && !pluginHook.isItem(itemToCheck, holder.setPlaceholdersAndArguments(wrapper.getMaterial().substring(pluginHook.getPrefix().length())))) return false; - if (wrapper.getMaterial() != null && itemToCheck.getType() != material) return false; + if (pluginHook != null) { + if (!pluginHook.isItem(itemToCheck, holder.setPlaceholdersAndArguments(wrapper.getMaterial().substring(pluginHook.getPrefix().length())))) return false; + } + else if (wrapper.getMaterial() != null && itemToCheck.getType() != material) return false; if (wrapper.hasData() && itemToCheck.getDurability() != wrapper.getData()) return false; ItemMeta metaToCheck = itemToCheck.getItemMeta(); From 4b263cac0e169da5e0542c6dc583a7b12a8c3c76 Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Sun, 17 Nov 2024 21:10:42 +0100 Subject: [PATCH 05/25] Fixed BaseHeadHook & TextureHeadHook --- .../deluxemenus/hooks/BaseHeadHook.java | 9 +++-- .../deluxemenus/hooks/TextureHeadHook.java | 4 +- .../deluxemenus/utils/SkullUtils.java | 37 ++++++++++++++++++- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java index 46e1a6dd..80def4e7 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java @@ -6,7 +6,6 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import org.bukkit.Bukkit; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -37,8 +36,12 @@ public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { if (arguments.length == 0) { return false; } - ItemStack skull = cache.computeIfAbsent(arguments[0], SkullUtils::getSkullByBase64EncodedTextureUrl).clone(); - return Bukkit.getItemFactory().equals(item.getItemMeta(), skull.getItemMeta()); + String itemTexture = SkullUtils.getTextureFromSkull(item); + String texture = SkullUtils.decodeSkinUrl(arguments[0]); + if (itemTexture == null || texture == null) return false; + + texture = texture.substring("https://textures.minecraft.net/texture/".length()-1); + return texture.equals(itemTexture); } @Override diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java index bd421b9c..8aa03186 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java @@ -6,7 +6,6 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import org.bukkit.Bukkit; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -37,8 +36,7 @@ public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { if (arguments.length == 0) { return false; } - ItemStack skull = cache.computeIfAbsent(arguments[0], key -> SkullUtils.getSkullByBase64EncodedTextureUrl(SkullUtils.getEncoded(key))).clone(); - return Bukkit.getItemFactory().equals(item.getItemMeta(), skull.getItemMeta()); + return arguments[0].equals(SkullUtils.getTextureFromSkull(item)); } @Override diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java index de1167f9..4b03aafd 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java @@ -80,6 +80,41 @@ public static ItemStack getSkullByBase64EncodedTextureUrl(@NotNull final String return head; } + public static String getTextureFromSkull(ItemStack item) { + if (!(item.getItemMeta() instanceof SkullMeta)) return null; + SkullMeta meta = (SkullMeta) item.getItemMeta(); + + if (VersionHelper.HAS_PLAYER_PROFILES) { + PlayerProfile profile = meta.getOwnerProfile(); + if (profile == null) return null; + + URL url = profile.getTextures().getSkin(); + if (url == null) return null; + + return url.toString().substring("https://textures.minecraft.net/texture/".length()-1); + } + + GameProfile profile; + try { + final Field profileField = meta.getClass().getDeclaredField("profile"); + profileField.setAccessible(true); + profile = (GameProfile) profileField.get(meta); + } catch (final NoSuchFieldException | IllegalArgumentException | IllegalAccessException exception) { + DeluxeMenus.printStacktrace( + "Failed to get base64 texture url from head item", + exception + ); + return null; + } + + for (Property property : profile.getProperties().get("textures")) { + if (property.getName().equals("textures")) { + return decodeSkinUrl(property.getValue()); + } + } + return null; + } + /** * Get the skull from a player name @@ -164,7 +199,7 @@ private static PlayerProfile getPlayerProfile(@NotNull final String base64Url) { * @return the url of the texture if found, otherwise {@code null} */ @Nullable - private static String decodeSkinUrl(@NotNull final String base64Texture) { + public static String decodeSkinUrl(@NotNull final String base64Texture) { final String decoded = new String(Base64.getDecoder().decode(base64Texture)); final JsonObject object = GSON.fromJson(decoded, JsonObject.class); From bbbe525eec51c0094f7b3e15b9274eead2f36aa2 Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Sun, 17 Nov 2024 23:40:48 +0100 Subject: [PATCH 06/25] Fix HasPermissionsRequirement logic for invert & add more warns --- .../deluxemenus/config/DeluxeMenusConfig.java | 18 ++++++++++++++++- .../HasPermissionsRequirement.java | 20 +++++++++---------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index 97ead7cf..8ef1b782 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -1155,7 +1155,23 @@ private RequirementList getRequirements(FileConfiguration c, String path) { ); minimum = -1; } - req = new HasPermissionsRequirement(c.getStringList(rPath + ".permissions"), minimum, invert); + List permissions = c.getStringList(rPath + ".permissions"); + if (permissions.isEmpty()) { + DeluxeMenus.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Has Permissions requirement at path: " + rPath + " has no permissions to check. Ignoring..." + ); + break; + } else if (minimum > permissions.size()) { + DeluxeMenus.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Has Permissions requirement at path: " + rPath + " has a minimum higher than the amount of permissions. Using "+permissions.size()+" instead" + ); + minimum = permissions.size(); + } + req = new HasPermissionsRequirement(permissions, minimum, invert); } else { DeluxeMenus.debug( DebugLevel.HIGHEST, diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/HasPermissionsRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/HasPermissionsRequirement.java index 6e0465db..05fa0742 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/HasPermissionsRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/HasPermissionsRequirement.java @@ -18,17 +18,15 @@ public HasPermissionsRequirement(List permissions, int minimum, boolean @Override public boolean evaluate(MenuHolder holder) { - int amount = 0; - for (String permission : permissions) { - String check = holder.setPlaceholdersAndArguments(permission); - if (holder.getViewer().hasPermission(check)) { - ++amount; - continue; - } - if (minimum == -1) return invert; - } - if (invert) return permissions.size()-amount >= minimum; - return amount >= minimum; + final int count = permissions.stream() + .map(holder::setPlaceholdersAndArguments) + .map(holder.getViewer()::hasPermission) + .mapToInt(hasPermission -> hasPermission ? 1 : 0) + .sum(); + return invert + ? count + minimum <= permissions.size() + : count >= minimum; } + } From e222803d5fda95c161ebc8581b3b5a52c34d409f Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Sun, 17 Nov 2024 23:42:02 +0100 Subject: [PATCH 07/25] Added "minimum" in requirement options --- .../extendedclip/deluxemenus/requirement/RequirementType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/RequirementType.java b/src/main/java/com/extendedclip/deluxemenus/requirement/RequirementType.java index d1787a64..ef39bcc2 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/RequirementType.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/RequirementType.java @@ -46,7 +46,7 @@ public enum RequirementType { DOES_NOT_HAVE_PERMISSIONS( Arrays.asList("!has permissions", "!has perms", "!haspermissions", "!hasperms", "!perms"), "Checks if a player does not have a set amount of permission", - Collections.singletonList("permissions")), + Arrays.asList("permissions", "minimum")), STRING_CONTAINS(Arrays.asList("string contains", "stringcontains", "contains"), "Checks if a string contains another string", Arrays.asList("input", "output")), STRING_DOES_NOT_CONTAIN(Arrays.asList("!string contains", "!stringcontains", "!contains"), From 06b50fbd81fa1e615db7b64b92c0caf3a3c577d3 Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Mon, 18 Nov 2024 00:26:14 +0100 Subject: [PATCH 08/25] Lowercase material to check for itemHook --- .../deluxemenus/config/DeluxeMenusConfig.java | 2 +- .../requirement/HasItemRequirement.java | 320 +++++++++--------- 2 files changed, 161 insertions(+), 161 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index 54ccdda7..c49e8cf3 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -1060,7 +1060,7 @@ private RequirementList getRequirements(FileConfiguration c, String path) { try { if (!containsPlaceholders(materialName) && plugin.getItemHooks().values() .stream() - .filter(x -> materialName.startsWith(x.getPrefix())) + .filter(x -> materialName.toLowerCase().startsWith(x.getPrefix())) .findFirst() .orElse(null) == null) Material.valueOf(materialName.toUpperCase()); diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java index f1878195..a89a5da1 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java @@ -1,160 +1,160 @@ -package com.extendedclip.deluxemenus.requirement; - -import com.extendedclip.deluxemenus.DeluxeMenus; -import com.extendedclip.deluxemenus.hooks.ItemHook; -import com.extendedclip.deluxemenus.menu.MenuHolder; -import com.extendedclip.deluxemenus.requirement.wrappers.ItemWrapper; -import com.extendedclip.deluxemenus.utils.StringUtils; -import com.extendedclip.deluxemenus.utils.VersionHelper; -import java.util.List; -import java.util.stream.Collectors; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -public class HasItemRequirement extends Requirement { - - private final ItemWrapper wrapper; - private final boolean invert; - - public HasItemRequirement(ItemWrapper wrapper, boolean invert) { - this.wrapper = wrapper; - this.invert = invert; - } - - @Override - public boolean evaluate(MenuHolder holder) { - String materialName = holder.setPlaceholdersAndArguments(wrapper.getMaterial()); - Material material = DeluxeMenus.MATERIALS.get(materialName.toUpperCase()); - ItemHook pluginHook = null; - if (material == null) { - pluginHook = DeluxeMenus.getInstance().getItemHooks().values() - .stream() - .filter(x -> materialName.startsWith(x.getPrefix())) - .findFirst() - .orElse(null); - if (pluginHook == null) return invert; - } - - if (material == Material.AIR) return invert == (holder.getViewer().getInventory().firstEmpty() == -1); - - ItemStack[] armor = wrapper.checkArmor() ? holder.getViewer().getInventory().getArmorContents() : null; - ItemStack[] offHand = wrapper.checkOffhand() ? holder.getViewer().getInventory().getExtraContents() : null; - ItemStack[] inventory = holder.getViewer().getInventory().getStorageContents(); - - int total = 0; - for (ItemStack itemToCheck: inventory) { - if (!isRequiredItem(itemToCheck, holder, material, pluginHook)) continue; - total += itemToCheck.getAmount(); - } - - if (offHand != null) { - for (ItemStack itemToCheck: offHand) { - if (!isRequiredItem(itemToCheck, holder, material, pluginHook)) continue; - total += itemToCheck.getAmount(); - } - } - - if (armor != null) { - for (ItemStack itemToCheck: armor) { - if (!isRequiredItem(itemToCheck, holder, material, pluginHook)) continue; - total += itemToCheck.getAmount(); - } - } - - return invert == (total < wrapper.getAmount()); - } - - private boolean isRequiredItem(ItemStack itemToCheck, MenuHolder holder, Material material, ItemHook pluginHook) { - if (itemToCheck == null || itemToCheck.getType() == Material.AIR) return false; - - if (pluginHook != null) { - if (!pluginHook.isItem(itemToCheck, holder.setPlaceholdersAndArguments(wrapper.getMaterial().substring(pluginHook.getPrefix().length())))) return false; - } - else if (wrapper.getMaterial() != null && itemToCheck.getType() != material) return false; - if (wrapper.hasData() && itemToCheck.getDurability() != wrapper.getData()) return false; - - ItemMeta metaToCheck = itemToCheck.getItemMeta(); - if (wrapper.isStrict()) { - if (metaToCheck != null) { - if (VersionHelper.IS_CUSTOM_MODEL_DATA) { - if (metaToCheck.hasCustomModelData()) return false; - } - if (metaToCheck.hasLore()) return false; - return !metaToCheck.hasDisplayName(); - } - - } else { - if ((wrapper.getCustomData() != 0 || wrapper.getName() != null || wrapper.getLore() != null) && metaToCheck == null) - return false; - - if (wrapper.getCustomData() != 0) { - if (VersionHelper.IS_CUSTOM_MODEL_DATA) { - if (!metaToCheck.hasCustomModelData()) return false; - if (metaToCheck.getCustomModelData() != wrapper.getCustomData()) return false; - } - } - - if (wrapper.getName() != null) { - if (!metaToCheck.hasDisplayName()) return false; - - String name = StringUtils.color(holder.setPlaceholdersAndArguments(wrapper.getName())); - String nameToCheck = StringUtils.color(holder.setPlaceholdersAndArguments(metaToCheck.getDisplayName())); - - if (wrapper.checkNameContains() && wrapper.checkNameIgnoreCase()) { - if (!org.apache.commons.lang3.StringUtils.containsIgnoreCase(nameToCheck, name)) return false; - } - else if (wrapper.checkNameContains()) { - if (!nameToCheck.contains(name)) return false; - } - else if (wrapper.checkNameIgnoreCase()) { - if (!nameToCheck.equalsIgnoreCase(name)) return false; - } - else if (!nameToCheck.equals(name)) { - return false; - } - } - - if (wrapper.getLoreList() != null) { - List loreX = metaToCheck.getLore(); - if (loreX == null) return false; - - String lore = wrapper.getLoreList().stream().map(holder::setPlaceholdersAndArguments).map(StringUtils::color).collect(Collectors.joining("&&")); - String loreToCheck = loreX.stream().map(holder::setPlaceholdersAndArguments).map(StringUtils::color).collect(Collectors.joining("&&")); - - if (wrapper.checkLoreContains() && wrapper.checkLoreIgnoreCase()) { - if (!org.apache.commons.lang3.StringUtils.containsIgnoreCase(loreToCheck, lore)) return false; - } - else if (wrapper.checkLoreContains()) { - if (!loreToCheck.contains(lore)) return false; - } - else if (wrapper.checkLoreIgnoreCase()) { - if (!loreToCheck.equalsIgnoreCase(lore)) return false; - } - else if (!loreToCheck.equals(lore)) { - return false; - } - } - - if (wrapper.getLore() != null) { - List loreX = metaToCheck.getLore(); - if (loreX == null) return false; - - String lore = StringUtils.color(holder.setPlaceholdersAndArguments(wrapper.getLore())); - String loreToCheck = loreX.stream().map(holder::setPlaceholdersAndArguments).map(StringUtils::color).collect(Collectors.joining("&&")); - - if (wrapper.checkLoreContains() && wrapper.checkLoreIgnoreCase()) { - return org.apache.commons.lang3.StringUtils.containsIgnoreCase(loreToCheck, lore); - } - else if (wrapper.checkLoreContains()) { - return loreToCheck.contains(lore); - } - else if (wrapper.checkLoreIgnoreCase()) { - return loreToCheck.equalsIgnoreCase(lore); - } - else return loreToCheck.equals(lore); - } - } - return true; - } -} +package com.extendedclip.deluxemenus.requirement; + +import com.extendedclip.deluxemenus.DeluxeMenus; +import com.extendedclip.deluxemenus.hooks.ItemHook; +import com.extendedclip.deluxemenus.menu.MenuHolder; +import com.extendedclip.deluxemenus.requirement.wrappers.ItemWrapper; +import com.extendedclip.deluxemenus.utils.StringUtils; +import com.extendedclip.deluxemenus.utils.VersionHelper; +import java.util.List; +import java.util.stream.Collectors; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +public class HasItemRequirement extends Requirement { + + private final ItemWrapper wrapper; + private final boolean invert; + + public HasItemRequirement(ItemWrapper wrapper, boolean invert) { + this.wrapper = wrapper; + this.invert = invert; + } + + @Override + public boolean evaluate(MenuHolder holder) { + String materialName = holder.setPlaceholdersAndArguments(wrapper.getMaterial()); + Material material = DeluxeMenus.MATERIALS.get(materialName.toUpperCase()); + ItemHook pluginHook = null; + if (material == null) { + pluginHook = DeluxeMenus.getInstance().getItemHooks().values() + .stream() + .filter(x -> materialName.toLowerCase().startsWith(x.getPrefix())) + .findFirst() + .orElse(null); + if (pluginHook == null) return invert; + } + + if (material == Material.AIR) return invert == (holder.getViewer().getInventory().firstEmpty() == -1); + + ItemStack[] armor = wrapper.checkArmor() ? holder.getViewer().getInventory().getArmorContents() : null; + ItemStack[] offHand = wrapper.checkOffhand() ? holder.getViewer().getInventory().getExtraContents() : null; + ItemStack[] inventory = holder.getViewer().getInventory().getStorageContents(); + + int total = 0; + for (ItemStack itemToCheck: inventory) { + if (!isRequiredItem(itemToCheck, holder, material, pluginHook)) continue; + total += itemToCheck.getAmount(); + } + + if (offHand != null) { + for (ItemStack itemToCheck: offHand) { + if (!isRequiredItem(itemToCheck, holder, material, pluginHook)) continue; + total += itemToCheck.getAmount(); + } + } + + if (armor != null) { + for (ItemStack itemToCheck: armor) { + if (!isRequiredItem(itemToCheck, holder, material, pluginHook)) continue; + total += itemToCheck.getAmount(); + } + } + + return invert == (total < wrapper.getAmount()); + } + + private boolean isRequiredItem(ItemStack itemToCheck, MenuHolder holder, Material material, ItemHook pluginHook) { + if (itemToCheck == null || itemToCheck.getType() == Material.AIR) return false; + + if (pluginHook != null) { + if (!pluginHook.isItem(itemToCheck, holder.setPlaceholdersAndArguments(wrapper.getMaterial().substring(pluginHook.getPrefix().length())))) return false; + } + else if (wrapper.getMaterial() != null && itemToCheck.getType() != material) return false; + if (wrapper.hasData() && itemToCheck.getDurability() != wrapper.getData()) return false; + + ItemMeta metaToCheck = itemToCheck.getItemMeta(); + if (wrapper.isStrict()) { + if (metaToCheck != null) { + if (VersionHelper.IS_CUSTOM_MODEL_DATA) { + if (metaToCheck.hasCustomModelData()) return false; + } + if (metaToCheck.hasLore()) return false; + return !metaToCheck.hasDisplayName(); + } + + } else { + if ((wrapper.getCustomData() != 0 || wrapper.getName() != null || wrapper.getLore() != null) && metaToCheck == null) + return false; + + if (wrapper.getCustomData() != 0) { + if (VersionHelper.IS_CUSTOM_MODEL_DATA) { + if (!metaToCheck.hasCustomModelData()) return false; + if (metaToCheck.getCustomModelData() != wrapper.getCustomData()) return false; + } + } + + if (wrapper.getName() != null) { + if (!metaToCheck.hasDisplayName()) return false; + + String name = StringUtils.color(holder.setPlaceholdersAndArguments(wrapper.getName())); + String nameToCheck = StringUtils.color(holder.setPlaceholdersAndArguments(metaToCheck.getDisplayName())); + + if (wrapper.checkNameContains() && wrapper.checkNameIgnoreCase()) { + if (!org.apache.commons.lang3.StringUtils.containsIgnoreCase(nameToCheck, name)) return false; + } + else if (wrapper.checkNameContains()) { + if (!nameToCheck.contains(name)) return false; + } + else if (wrapper.checkNameIgnoreCase()) { + if (!nameToCheck.equalsIgnoreCase(name)) return false; + } + else if (!nameToCheck.equals(name)) { + return false; + } + } + + if (wrapper.getLoreList() != null) { + List loreX = metaToCheck.getLore(); + if (loreX == null) return false; + + String lore = wrapper.getLoreList().stream().map(holder::setPlaceholdersAndArguments).map(StringUtils::color).collect(Collectors.joining("&&")); + String loreToCheck = loreX.stream().map(holder::setPlaceholdersAndArguments).map(StringUtils::color).collect(Collectors.joining("&&")); + + if (wrapper.checkLoreContains() && wrapper.checkLoreIgnoreCase()) { + if (!org.apache.commons.lang3.StringUtils.containsIgnoreCase(loreToCheck, lore)) return false; + } + else if (wrapper.checkLoreContains()) { + if (!loreToCheck.contains(lore)) return false; + } + else if (wrapper.checkLoreIgnoreCase()) { + if (!loreToCheck.equalsIgnoreCase(lore)) return false; + } + else if (!loreToCheck.equals(lore)) { + return false; + } + } + + if (wrapper.getLore() != null) { + List loreX = metaToCheck.getLore(); + if (loreX == null) return false; + + String lore = StringUtils.color(holder.setPlaceholdersAndArguments(wrapper.getLore())); + String loreToCheck = loreX.stream().map(holder::setPlaceholdersAndArguments).map(StringUtils::color).collect(Collectors.joining("&&")); + + if (wrapper.checkLoreContains() && wrapper.checkLoreIgnoreCase()) { + return org.apache.commons.lang3.StringUtils.containsIgnoreCase(loreToCheck, lore); + } + else if (wrapper.checkLoreContains()) { + return loreToCheck.contains(lore); + } + else if (wrapper.checkLoreIgnoreCase()) { + return loreToCheck.equalsIgnoreCase(lore); + } + else return loreToCheck.equals(lore); + } + } + return true; + } +} From 9125d6366e55340a1b48ff92226d25bced1e25a0 Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Mon, 18 Nov 2024 00:27:27 +0100 Subject: [PATCH 09/25] Extract logic to SkullUtils.getSkullOwner --- .../deluxemenus/hooks/NamedHeadHook.java | 147 ++++++++---------- .../deluxemenus/utils/SkullUtils.java | 12 ++ 2 files changed, 80 insertions(+), 79 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java index 67d3b4b6..f5d80563 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java @@ -1,79 +1,68 @@ -package com.extendedclip.deluxemenus.hooks; - -import com.extendedclip.deluxemenus.DeluxeMenus; -import com.extendedclip.deluxemenus.cache.SimpleCache; -import com.extendedclip.deluxemenus.utils.SkullUtils; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import com.extendedclip.deluxemenus.utils.VersionHelper; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.SkullMeta; -import org.jetbrains.annotations.NotNull; - -public class NamedHeadHook implements ItemHook, Listener, SimpleCache { - - private final Map cache = new ConcurrentHashMap<>(); - - public NamedHeadHook(@NotNull final DeluxeMenus plugin) { - plugin.getServer().getPluginManager().registerEvents(this, plugin); - } - - @Override - public ItemStack getItem(@NotNull final String... arguments) { - if (arguments.length == 0) { - return DeluxeMenus.getInstance().getHead().clone(); - } - - try { - return cache.computeIfAbsent(arguments[0], SkullUtils::getSkullByName).clone(); - } catch (Exception exception) { - DeluxeMenus.printStacktrace( - "Something went wrong while trying to get a head by name" + - ": " + arguments[0], - exception - ); - } - - return DeluxeMenus.getInstance().getHead().clone(); - } - - @Override - public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { - if (arguments.length == 0) { - return false; - } - if (!(item.getItemMeta() instanceof SkullMeta)) return false; - final SkullMeta headMeta = (SkullMeta) item.getItemMeta(); - - String owner; - if (!VersionHelper.IS_SKULL_OWNER_LEGACY) { - if (headMeta.getOwningPlayer() == null) return false; - owner = headMeta.getOwningPlayer().getName(); - } else { - owner = headMeta.getOwner(); - if (owner == null) return false; - } - return arguments[0].equalsIgnoreCase(owner); - } - - @EventHandler(ignoreCancelled = true) - public void onPlayerQuit(final PlayerQuitEvent event) { - cache.remove(event.getPlayer().getName()); - } - - @Override - public String getPrefix() { - return "head-"; - } - - @Override - public void clearCache() { - cache.clear(); - } -} +package com.extendedclip.deluxemenus.hooks; + +import com.extendedclip.deluxemenus.DeluxeMenus; +import com.extendedclip.deluxemenus.cache.SimpleCache; +import com.extendedclip.deluxemenus.utils.SkullUtils; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.extendedclip.deluxemenus.utils.VersionHelper; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; +import org.jetbrains.annotations.NotNull; + +public class NamedHeadHook implements ItemHook, Listener, SimpleCache { + + private final Map cache = new ConcurrentHashMap<>(); + + public NamedHeadHook(@NotNull final DeluxeMenus plugin) { + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public ItemStack getItem(@NotNull final String... arguments) { + if (arguments.length == 0) { + return DeluxeMenus.getInstance().getHead().clone(); + } + + try { + return cache.computeIfAbsent(arguments[0], SkullUtils::getSkullByName).clone(); + } catch (Exception exception) { + DeluxeMenus.printStacktrace( + "Something went wrong while trying to get a head by name" + + ": " + arguments[0], + exception + ); + } + + return DeluxeMenus.getInstance().getHead().clone(); + } + + @Override + public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + if (arguments.length == 0) { + return false; + } + return arguments[0].equalsIgnoreCase(SkullUtils.getSkullOwner(item)); + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerQuit(final PlayerQuitEvent event) { + cache.remove(event.getPlayer().getName()); + } + + @Override + public String getPrefix() { + return "head-"; + } + + @Override + public void clearCache() { + cache.clear(); + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java index 4b03aafd..428e5cf2 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java @@ -145,6 +145,18 @@ public static ItemStack getSkullByName(@NotNull final String playerName) { return head; } + public static String getSkullOwner(ItemStack skull) { + if (skull == null || !(skull.getItemMeta() instanceof SkullMeta)) return null; + SkullMeta meta = (SkullMeta) skull.getItemMeta(); + + if (!VersionHelper.IS_SKULL_OWNER_LEGACY) { + if (meta.getOwningPlayer() == null) return null; + return meta.getOwningPlayer().getName(); + } + + return meta.getOwner(); + } + /** * Create a game profile object * From e47da1cb8c325d911991126079df0f893e05d839 Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Mon, 18 Nov 2024 00:28:16 +0100 Subject: [PATCH 10/25] rename isItem to itemMatchIdentifiers --- .../java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java | 2 +- .../extendedclip/deluxemenus/hooks/ExecutableBlocksHook.java | 2 +- .../com/extendedclip/deluxemenus/hooks/ExecutableItemsHook.java | 2 +- .../com/extendedclip/deluxemenus/hooks/HeadDatabaseHook.java | 2 +- src/main/java/com/extendedclip/deluxemenus/hooks/ItemHook.java | 2 +- .../java/com/extendedclip/deluxemenus/hooks/ItemsAdderHook.java | 2 +- .../java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java | 2 +- .../java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java | 2 +- .../java/com/extendedclip/deluxemenus/hooks/OraxenHook.java | 2 +- .../com/extendedclip/deluxemenus/hooks/TextureHeadHook.java | 2 +- .../deluxemenus/requirement/HasItemRequirement.java | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java index 80def4e7..2fc19922 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java @@ -32,7 +32,7 @@ public ItemStack getItem(@NotNull final String... arguments) { } @Override - public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + public boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull String... arguments) { if (arguments.length == 0) { return false; } diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableBlocksHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableBlocksHook.java index de00e36e..22184d73 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableBlocksHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableBlocksHook.java @@ -29,7 +29,7 @@ public ItemStack getItem(@NotNull String... arguments) { } @Override - public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + public boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull String... arguments) { return false; } diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableItemsHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableItemsHook.java index c72b8473..9bdf1266 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableItemsHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableItemsHook.java @@ -34,7 +34,7 @@ public ItemStack getItem(@NotNull String... arguments) { } @Override - public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + public boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull String... arguments) { if (arguments.length == 0) { return false; } diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/HeadDatabaseHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/HeadDatabaseHook.java index 360d8020..b3e5c029 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/HeadDatabaseHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/HeadDatabaseHook.java @@ -33,7 +33,7 @@ public ItemStack getItem(@NotNull final String... arguments) { } @Override - public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + public boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull String... arguments) { if (arguments.length == 0) { return false; } diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/ItemHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/ItemHook.java index 3a3ddde7..d88d8f1d 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/ItemHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/ItemHook.java @@ -7,7 +7,7 @@ public interface ItemHook { ItemStack getItem(@NotNull final String... arguments); - boolean isItem(@NotNull ItemStack item, @NotNull final String... arguments); + boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull final String... arguments); String getPrefix(); diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/ItemsAdderHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/ItemsAdderHook.java index a602ee9d..ac3760b0 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/ItemsAdderHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/ItemsAdderHook.java @@ -37,7 +37,7 @@ public ItemStack getItem(@NotNull final String... arguments) { } @Override - public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + public boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull String... arguments) { if (arguments.length == 0) { return false; } diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java index e8753295..282e8703 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java @@ -61,7 +61,7 @@ public ItemStack getItem(@NotNull final String... arguments) { } @Override - public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + public boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull String... arguments) { if (arguments.length == 0) { return false; } diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java index f5d80563..26768da3 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java @@ -44,7 +44,7 @@ public ItemStack getItem(@NotNull final String... arguments) { } @Override - public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + public boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull String... arguments) { if (arguments.length == 0) { return false; } diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/OraxenHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/OraxenHook.java index 07f13031..166fd23e 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/OraxenHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/OraxenHook.java @@ -29,7 +29,7 @@ public ItemStack getItem(@NotNull String... arguments) { } @Override - public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + public boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull String... arguments) { if (arguments.length == 0) { return false; } diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java index 8aa03186..147d6557 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java @@ -32,7 +32,7 @@ public ItemStack getItem(@NotNull final String... arguments) { } @Override - public boolean isItem(@NotNull ItemStack item, @NotNull String... arguments) { + public boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull String... arguments) { if (arguments.length == 0) { return false; } diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java index a89a5da1..8fa9f375 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java @@ -69,7 +69,7 @@ private boolean isRequiredItem(ItemStack itemToCheck, MenuHolder holder, Materia if (itemToCheck == null || itemToCheck.getType() == Material.AIR) return false; if (pluginHook != null) { - if (!pluginHook.isItem(itemToCheck, holder.setPlaceholdersAndArguments(wrapper.getMaterial().substring(pluginHook.getPrefix().length())))) return false; + if (!pluginHook.itemMatchesIdentifiers(itemToCheck, holder.setPlaceholdersAndArguments(wrapper.getMaterial().substring(pluginHook.getPrefix().length())))) return false; } else if (wrapper.getMaterial() != null && itemToCheck.getType() != material) return false; if (wrapper.hasData() && itemToCheck.getDurability() != wrapper.getData()) return false; From 5d34619cf714948837d2a43a291a8c980e7ce7ec Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Tue, 19 Nov 2024 18:01:01 +0100 Subject: [PATCH 11/25] Added hide_tooltip, enchantment_glint_override, rarity, tooltip_style and item_model --- gradle/libs.versions.toml | 2 +- .../deluxemenus/config/DeluxeMenusConfig.java | 7 +- .../deluxemenus/menu/MenuItem.java | 35 ++++++++++ .../menu/options/MenuItemOptions.java | 67 +++++++++++++++++++ .../deluxemenus/utils/VersionHelper.java | 7 ++ 5 files changed, 116 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 68878e43..d5d55762 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # Compile only -spigot = "1.21-R0.1-SNAPSHOT" +spigot = "1.21.3-R0.1-SNAPSHOT" vault = "1.7.1" authlib = "1.5.25" headdb = "1.3.1" diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index d7e24d72..fa04ec9e 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -762,7 +762,12 @@ private Map> loadMenuItems(FileConfiguration .nbtInt(c.getString(currentPath + "nbt_int", null)) .nbtStrings(c.getStringList(currentPath + "nbt_strings")) .nbtInts(c.getStringList(currentPath + "nbt_ints")) - .priority(c.getInt(currentPath + "priority", 1)); + .priority(c.getInt(currentPath + "priority", 1)) + .hideTooltip(c.getString(currentPath + "hide_tooltip", null)) + .enchantmentGlintOverride(c.getString(currentPath + "enchantment_glint_override", null)) + .rarity(c.getString(currentPath + "rarity", null)) + .tooltipStyle(c.getString(currentPath + "tooltip_style", null)) + .itemModel(c.getString(currentPath + "item_model", null)); // Lore Append Mode if (c.contains(currentPath + "lore_append_mode")) { diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java index be8224f8..b63716ef 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -14,12 +14,14 @@ import org.bukkit.FireworkEffect; import org.bukkit.Material; import org.bukkit.Registry; +import org.bukkit.NamespacedKey; import org.bukkit.block.Banner; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.Light; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemRarity; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ArmorMeta; import org.bukkit.inventory.meta.BannerMeta; @@ -259,6 +261,39 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { itemMeta.setUnbreakable(true); } + if (VersionHelper.HAS_DATA_COMPONENTS) { + if (this.options.hideTooltip().isPresent()) { + String hideTooltip = holder.setPlaceholdersAndArguments(this.options.hideTooltip().get()); + itemMeta.setHideTooltip(Boolean.parseBoolean(hideTooltip)); + } + if (this.options.enchantmentGlintOverride().isPresent()) { + String enchantmentGlintOverride = holder.setPlaceholdersAndArguments(this.options.enchantmentGlintOverride().get()); + itemMeta.setEnchantmentGlintOverride(Boolean.parseBoolean(enchantmentGlintOverride)); + } + if (this.options.rarity().isPresent()) { + String rarity = holder.setPlaceholdersAndArguments(this.options.rarity().get()); + try { + itemMeta.setRarity(ItemRarity.valueOf(rarity.toUpperCase())); + } catch (IllegalArgumentException e) { + DeluxeMenus.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Rarity " + rarity + " is not a valid!" + ); + } + } + } + if (VersionHelper.HAS_TOOLTIP_STYLE) { + if (this.options.tooltipStyle().isPresent()) { + NamespacedKey tooltipStyle = NamespacedKey.fromString(this.options.tooltipStyle().get()); + if (tooltipStyle != null) itemMeta.setTooltipStyle(tooltipStyle); + } + if (this.options.itemModel().isPresent()) { + NamespacedKey itemModel = NamespacedKey.fromString(this.options.itemModel().get()); + if (itemModel != null) itemMeta.setItemModel(itemModel); + } + } + if (VersionHelper.HAS_ARMOR_TRIMS && ItemUtils.hasArmorMeta(itemStack)) { final Optional trimMaterialName = this.options.trimMaterial(); final Optional trimPatternName = this.options.trimPattern(); diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java b/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java index 3c0400e8..13dc63fc 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java @@ -37,6 +37,12 @@ public class MenuItemOptions { private final String trimMaterial; private final String trimPattern; + private final String hideTooltip; + private final String enchantmentGlintOverride; + private final String rarity; + private final String tooltipStyle; + private final String itemModel; + private final Map enchantments; private final List potionEffects; private final List bannerMeta; @@ -89,6 +95,11 @@ private MenuItemOptions(final @NotNull MenuItemOptionsBuilder builder) { this.rgb = builder.rgb; this.trimMaterial = builder.trimMaterial; this.trimPattern = builder.trimPattern; + this.hideTooltip = builder.hideTooltip; + this.enchantmentGlintOverride = builder.enchantmentGlintOverride; + this.rarity = builder.rarity; + this.tooltipStyle = builder.tooltipStyle; + this.itemModel = builder.itemModel; this.enchantments = builder.enchantments; this.potionEffects = builder.potionEffects; this.bannerMeta = builder.bannerMeta; @@ -178,6 +189,26 @@ public void headType(final @Nullable HeadType headType) { return Optional.ofNullable(trimPattern); } + public @NotNull Optional hideTooltip() { + return Optional.ofNullable(hideTooltip); + } + + public @NotNull Optional enchantmentGlintOverride() { + return Optional.ofNullable(enchantmentGlintOverride); + } + + public @NotNull Optional rarity() { + return Optional.ofNullable(rarity); + } + + public @NotNull Optional tooltipStyle() { + return Optional.ofNullable(tooltipStyle); + } + + public @NotNull Optional itemModel() { + return Optional.ofNullable(itemModel); + } + public @NotNull Map enchantments() { return enchantments; } @@ -311,6 +342,11 @@ public boolean updatePlaceholders() { .rgb(this.rgb) .trimMaterial(this.trimMaterial) .trimPattern(this.trimPattern) + .hideTooltip(this.hideTooltip) + .enchantmentGlintOverride(this.enchantmentGlintOverride) + .rarity(this.rarity) + .tooltipStyle(this.tooltipStyle) + .itemModel(this.itemModel) .enchantments(this.enchantments) .potionEffects(this.potionEffects) .bannerMeta(this.bannerMeta) @@ -355,6 +391,12 @@ public static class MenuItemOptionsBuilder { private String trimMaterial; private String trimPattern; + private String hideTooltip; + private String enchantmentGlintOverride; + private String rarity; + private String tooltipStyle; + private String itemModel; + private Map enchantments = Collections.emptyMap(); private List potionEffects = Collections.emptyList(); private List bannerMeta = Collections.emptyList(); @@ -463,6 +505,31 @@ public MenuItemOptionsBuilder trimPattern(final @Nullable String trimPattern) { return this; } + public MenuItemOptionsBuilder hideTooltip(final @Nullable String hideTooltip) { + this.hideTooltip = hideTooltip; + return this; + } + + public MenuItemOptionsBuilder enchantmentGlintOverride(final @Nullable String enchantmentGlintOverride) { + this.enchantmentGlintOverride = enchantmentGlintOverride; + return this; + } + + public MenuItemOptionsBuilder rarity(final @Nullable String rarity) { + this.rarity = rarity; + return this; + } + + public MenuItemOptionsBuilder tooltipStyle(final @Nullable String tooltipStyle) { + this.tooltipStyle = tooltipStyle; + return this; + } + + public MenuItemOptionsBuilder itemModel(final @Nullable String itemModel) { + this.itemModel = itemModel; + return this; + } + public MenuItemOptionsBuilder enchantments(final @NotNull Map enchantments) { this.enchantments = enchantments; return this; diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java b/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java index 49b239e8..a82924bd 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java @@ -21,6 +21,8 @@ public final class VersionHelper { private static final String PACKAGE_NAME = Bukkit.getServer().getClass().getPackage().getName(); public static final String NMS_VERSION = PACKAGE_NAME.substring(PACKAGE_NAME.lastIndexOf('.') + 1); + // Tooltip Visibility, Style & Rarity + private static final int V1_21_2 = 1_21_2; // Data components private static final int V1_20_5 = 1_20_5; // ArmorTrims @@ -44,6 +46,11 @@ public final class VersionHelper { private static final boolean IS_PAPER = checkPaper(); + /** + * Checks if the current version includes the ArmorTrims API + */ + public static final boolean HAS_TOOLTIP_STYLE = CURRENT_VERSION >= V1_21_2; + /** * Checks if the current version includes the Data Components */ From 0cd983a54e3144d4607e6432390c5cc0dd169f07 Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Tue, 19 Nov 2024 21:34:10 +0100 Subject: [PATCH 12/25] Updated HAS_TOOLTIP_STYLE comment --- .../com/extendedclip/deluxemenus/utils/VersionHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java b/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java index a82924bd..08e0d68f 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java @@ -21,7 +21,7 @@ public final class VersionHelper { private static final String PACKAGE_NAME = Bukkit.getServer().getClass().getPackage().getName(); public static final String NMS_VERSION = PACKAGE_NAME.substring(PACKAGE_NAME.lastIndexOf('.') + 1); - // Tooltip Visibility, Style & Rarity + // Tooltip Style & Item Model private static final int V1_21_2 = 1_21_2; // Data components private static final int V1_20_5 = 1_20_5; @@ -47,7 +47,7 @@ public final class VersionHelper { private static final boolean IS_PAPER = checkPaper(); /** - * Checks if the current version includes the ArmorTrims API + * Checks if the current version includes the setTooltipStyle and setItemModel */ public static final boolean HAS_TOOLTIP_STYLE = CURRENT_VERSION >= V1_21_2; From 83307a6d5e8631807fa65b60f1e3f165cea6d665 Mon Sep 17 00:00:00 2001 From: Tanguygab Date: Wed, 20 Nov 2024 16:45:31 +0100 Subject: [PATCH 13/25] forgot placeholders support for tooltip_style & item_model --- src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java index b63716ef..623651eb 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -285,11 +285,11 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { } if (VersionHelper.HAS_TOOLTIP_STYLE) { if (this.options.tooltipStyle().isPresent()) { - NamespacedKey tooltipStyle = NamespacedKey.fromString(this.options.tooltipStyle().get()); + NamespacedKey tooltipStyle = NamespacedKey.fromString(holder.setPlaceholdersAndArguments(this.options.tooltipStyle().get())); if (tooltipStyle != null) itemMeta.setTooltipStyle(tooltipStyle); } if (this.options.itemModel().isPresent()) { - NamespacedKey itemModel = NamespacedKey.fromString(this.options.itemModel().get()); + NamespacedKey itemModel = NamespacedKey.fromString(holder.setPlaceholdersAndArguments(this.options.itemModel().get())); if (itemModel != null) itemMeta.setItemModel(itemModel); } } From bfdd94209d1d54fd8bdf8032fbf6cd677055b7e0 Mon Sep 17 00:00:00 2001 From: MemencioPerez Date: Sun, 24 Nov 2024 02:16:36 -0400 Subject: [PATCH 14/25] feat: add options for NBT Short tags --- .../deluxemenus/config/DeluxeMenusConfig.java | 2 ++ .../deluxemenus/menu/MenuItem.java | 16 +++++++++++ .../menu/options/MenuItemOptions.java | 26 +++++++++++++++++ .../deluxemenus/nbt/NbtProvider.java | 28 +++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index c6ddc803..46a7d507 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -759,8 +759,10 @@ private Map> loadMenuItems(FileConfiguration .hideUnbreakable(c.getBoolean(currentPath + "hide_unbreakable", false)) .hideEnchants(c.getBoolean(currentPath + "hide_enchantments", false)) .nbtString(c.getString(currentPath + "nbt_string", null)) + .nbtShort(c.getString(currentPath + "nbt_short", null)) .nbtInt(c.getString(currentPath + "nbt_int", null)) .nbtStrings(c.getStringList(currentPath + "nbt_strings")) + .nbtShorts(c.getStringList(currentPath + "nbt_shorts")) .nbtInts(c.getStringList(currentPath + "nbt_ints")) .priority(c.getInt(currentPath + "priority", 1)); diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java index be8224f8..88178e5f 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -413,6 +413,14 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { } } + if (this.options.nbtShort().isPresent()) { + final String tag = holder.setPlaceholdersAndArguments(this.options.nbtShort().get()); + if (tag.contains(":")) { + final String[] parts = tag.split(":"); + itemStack = NbtProvider.setShort(itemStack, parts[0], Short.parseShort(parts[1])); + } + } + if (this.options.nbtInt().isPresent()) { final String tag = holder.setPlaceholdersAndArguments(this.options.nbtInt().get()); if (tag.contains(":")) { @@ -429,6 +437,14 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { } } + for (String nbtTag : this.options.nbtShorts()) { + final String tag = holder.setPlaceholdersAndArguments(nbtTag); + if (tag.contains(":")) { + final String[] parts = tag.split(":"); + itemStack = NbtProvider.setShort(itemStack, parts[0], Short.parseShort(parts[1])); + } + } + for (String nbtTag : this.options.nbtInts()) { final String tag = holder.setPlaceholdersAndArguments(nbtTag); if (tag.contains(":")) { diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java b/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java index 3c0400e8..da6977d7 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java @@ -50,8 +50,10 @@ public class MenuItemOptions { private final LoreAppendMode loreAppendMode; private final String nbtString; + private final String nbtShort; private final String nbtInt; private final List nbtStrings; + private final List nbtShorts; private final List nbtInts; private final int slot; @@ -97,8 +99,10 @@ private MenuItemOptions(final @NotNull MenuItemOptionsBuilder builder) { this.displayNameHasPlaceholders = builder.displayNameHasPlaceholders; this.loreHasPlaceholders = builder.loreHasPlaceholders; this.nbtString = builder.nbtString; + this.nbtShort = builder.nbtShort; this.nbtInt = builder.nbtInt; this.nbtStrings = builder.nbtStrings; + this.nbtShorts = builder.nbtShorts; this.nbtInts = builder.nbtInts; this.slot = builder.slot; this.priority = builder.priority; @@ -218,6 +222,10 @@ public boolean hasLore() { return Optional.ofNullable(nbtString); } + public @NotNull Optional nbtShort() { + return Optional.ofNullable(nbtShort); + } + public @NotNull Optional nbtInt() { return Optional.ofNullable(nbtInt); } @@ -226,6 +234,10 @@ public boolean hasLore() { return nbtStrings; } + public @NotNull List nbtShorts() { + return nbtShorts; + } + public @NotNull List nbtInts() { return nbtInts; } @@ -317,8 +329,10 @@ public boolean updatePlaceholders() { .itemFlags(this.itemFlags) .unbreakable(this.unbreakable) .nbtString(this.nbtString) + .nbtShort(this.nbtShort) .nbtInt(this.nbtInt) .nbtStrings(this.nbtStrings) + .nbtShorts(this.nbtShorts) .nbtInts(this.nbtInts) .slot(this.slot) .priority(this.priority) @@ -368,8 +382,10 @@ public static class MenuItemOptionsBuilder { private LoreAppendMode loreAppendMode; private String nbtString; + private String nbtShort; private String nbtInt; private List nbtStrings = Collections.emptyList(); + private List nbtShorts = Collections.emptyList(); private List nbtInts = Collections.emptyList(); private int slot; @@ -526,6 +542,11 @@ public MenuItemOptionsBuilder nbtString(final @Nullable String nbtString) { return this; } + public MenuItemOptionsBuilder nbtShort(final @Nullable String nbtShort) { + this.nbtShort = nbtShort; + return this; + } + public MenuItemOptionsBuilder nbtInt(final @Nullable String nbtInt) { this.nbtInt = nbtInt; return this; @@ -536,6 +557,11 @@ public MenuItemOptionsBuilder nbtStrings(final @NotNull List nbtStrings) return this; } + public MenuItemOptionsBuilder nbtShorts(final @NotNull List nbtShorts) { + this.nbtShorts = nbtShorts; + return this; + } + public MenuItemOptionsBuilder nbtInts(final @NotNull List nbtInts) { this.nbtInts = nbtInts; return this; diff --git a/src/main/java/com/extendedclip/deluxemenus/nbt/NbtProvider.java b/src/main/java/com/extendedclip/deluxemenus/nbt/NbtProvider.java index 418f9136..bb37d2a3 100644 --- a/src/main/java/com/extendedclip/deluxemenus/nbt/NbtProvider.java +++ b/src/main/java/com/extendedclip/deluxemenus/nbt/NbtProvider.java @@ -15,6 +15,7 @@ public final class NbtProvider { private static Method getStringMethod; private static Method setStringMethod; private static Method setBooleanMethod; + private static Method setShortMethod; private static Method setIntMethod; private static Method removeTagMethod; private static Method hasTagMethod; @@ -36,6 +37,7 @@ public final class NbtProvider { getStringMethod = compoundClass.getMethod(VersionConstants.GET_STRING_METHOD_NAME, String.class); setStringMethod = compoundClass.getMethod(VersionConstants.SET_STRING_METHOD_NAME, String.class, String.class); setBooleanMethod = compoundClass.getMethod(VersionConstants.SET_BOOLEAN_METHOD_NAME, String.class, boolean.class); + setShortMethod = compoundClass.getMethod(VersionConstants.SET_SHORT_METHOD_NAME, String.class, short.class); setIntMethod = compoundClass.getMethod(VersionConstants.SET_INTEGER_METHOD_NAME, String.class, int.class); removeTagMethod = compoundClass.getMethod(VersionConstants.REMOVE_TAG_METHOD_NAME, String.class); hasTagMethod = itemStackClass.getMethod(VersionConstants.HAS_TAG_METHOD_NAME); @@ -116,6 +118,19 @@ public static String getString(final ItemStack itemStack, final String key) { return getString(itemCompound, key); } + public static ItemStack setShort(final ItemStack itemStack, final String key, final short value) { + if (itemStack == null) return null; + if (itemStack.getType() == Material.AIR) return null; + + Object nmsItemStack = asNMSCopy(itemStack); + Object itemCompound = hasTag(nmsItemStack) ? getTag(nmsItemStack) : newNBTTagCompound(); + + setShort(itemCompound, key, value); + setTag(nmsItemStack, itemCompound); + + return asBukkitCopy(nmsItemStack); + } + public static ItemStack setInt(final ItemStack itemStack, final String key, final int value) { if (itemStack == null) return null; if (itemStack.getType() == Material.AIR) return null; @@ -176,6 +191,13 @@ private static void setBoolean(final Object itemCompound, final String key, fina } } + private static void setShort(final Object itemCompound, final String key, final short value) { + try { + setShortMethod.invoke(itemCompound, key, value); + } catch (IllegalAccessException | InvocationTargetException ignored) { + } + } + private static void setInt(final Object itemCompound, final String key, final int value) { try { setIntMethod.invoke(itemCompound, key, value); @@ -299,6 +321,7 @@ private static class VersionConstants { private final static String GET_STRING_METHOD_NAME = getStringMethodName(); private final static String SET_STRING_METHOD_NAME = setStringMethodName(); private final static String SET_BOOLEAN_METHOD_NAME = setBooleanMethodName(); + private final static String SET_SHORT_METHOD_NAME = setShortMethodName(); private final static String SET_INTEGER_METHOD_NAME = setIntegerMethodName(); private final static String REMOVE_TAG_METHOD_NAME = removeTagMethodName(); private final static String HAS_TAG_METHOD_NAME = hasTagMethodName(); @@ -320,6 +343,11 @@ private static String setBooleanMethodName() { return "setBoolean"; } + private static String setShortMethodName() { + if (VersionHelper.HAS_OBFUSCATED_NAMES) return "a"; + return "setShort"; + } + private static String setIntegerMethodName() { if (VersionHelper.HAS_OBFUSCATED_NAMES) return "a"; return "setInt"; From 77eca8665c0df07292369ab9f870e58d3bd1f781 Mon Sep 17 00:00:00 2001 From: MemencioPerez Date: Sun, 24 Nov 2024 04:33:10 -0400 Subject: [PATCH 15/25] feat: add options for NBT Byte tags --- .../deluxemenus/config/DeluxeMenusConfig.java | 2 ++ .../deluxemenus/menu/MenuItem.java | 16 +++++++++++ .../menu/options/MenuItemOptions.java | 26 +++++++++++++++++ .../deluxemenus/nbt/NbtProvider.java | 28 +++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index 46a7d507..dae137eb 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -759,9 +759,11 @@ private Map> loadMenuItems(FileConfiguration .hideUnbreakable(c.getBoolean(currentPath + "hide_unbreakable", false)) .hideEnchants(c.getBoolean(currentPath + "hide_enchantments", false)) .nbtString(c.getString(currentPath + "nbt_string", null)) + .nbtByte(c.getString(currentPath + "nbt_byte", null)) .nbtShort(c.getString(currentPath + "nbt_short", null)) .nbtInt(c.getString(currentPath + "nbt_int", null)) .nbtStrings(c.getStringList(currentPath + "nbt_strings")) + .nbtBytes(c.getStringList(currentPath + "nbt_bytes")) .nbtShorts(c.getStringList(currentPath + "nbt_shorts")) .nbtInts(c.getStringList(currentPath + "nbt_ints")) .priority(c.getInt(currentPath + "priority", 1)); diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java index 88178e5f..82f17003 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -413,6 +413,14 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { } } + if (this.options.nbtByte().isPresent()) { + final String tag = holder.setPlaceholdersAndArguments(this.options.nbtByte().get()); + if (tag.contains(":")) { + final String[] parts = tag.split(":"); + itemStack = NbtProvider.setByte(itemStack, parts[0], Byte.parseByte(parts[1])); + } + } + if (this.options.nbtShort().isPresent()) { final String tag = holder.setPlaceholdersAndArguments(this.options.nbtShort().get()); if (tag.contains(":")) { @@ -437,6 +445,14 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { } } + for (String nbtTag : this.options.nbtBytes()) { + final String tag = holder.setPlaceholdersAndArguments(nbtTag); + if (tag.contains(":")) { + final String[] parts = tag.split(":"); + itemStack = NbtProvider.setByte(itemStack, parts[0], Byte.parseByte(parts[1])); + } + } + for (String nbtTag : this.options.nbtShorts()) { final String tag = holder.setPlaceholdersAndArguments(nbtTag); if (tag.contains(":")) { diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java b/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java index da6977d7..fb1c45d8 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java @@ -50,9 +50,11 @@ public class MenuItemOptions { private final LoreAppendMode loreAppendMode; private final String nbtString; + private final String nbtByte; private final String nbtShort; private final String nbtInt; private final List nbtStrings; + private final List nbtBytes; private final List nbtShorts; private final List nbtInts; @@ -99,9 +101,11 @@ private MenuItemOptions(final @NotNull MenuItemOptionsBuilder builder) { this.displayNameHasPlaceholders = builder.displayNameHasPlaceholders; this.loreHasPlaceholders = builder.loreHasPlaceholders; this.nbtString = builder.nbtString; + this.nbtByte = builder.nbtByte; this.nbtShort = builder.nbtShort; this.nbtInt = builder.nbtInt; this.nbtStrings = builder.nbtStrings; + this.nbtBytes = builder.nbtBytes; this.nbtShorts = builder.nbtShorts; this.nbtInts = builder.nbtInts; this.slot = builder.slot; @@ -222,6 +226,10 @@ public boolean hasLore() { return Optional.ofNullable(nbtString); } + public @NotNull Optional nbtByte() { + return Optional.ofNullable(nbtByte); + } + public @NotNull Optional nbtShort() { return Optional.ofNullable(nbtShort); } @@ -234,6 +242,10 @@ public boolean hasLore() { return nbtStrings; } + public @NotNull List nbtBytes() { + return nbtBytes; + } + public @NotNull List nbtShorts() { return nbtShorts; } @@ -329,9 +341,11 @@ public boolean updatePlaceholders() { .itemFlags(this.itemFlags) .unbreakable(this.unbreakable) .nbtString(this.nbtString) + .nbtByte(this.nbtByte) .nbtShort(this.nbtShort) .nbtInt(this.nbtInt) .nbtStrings(this.nbtStrings) + .nbtBytes(this.nbtBytes) .nbtShorts(this.nbtShorts) .nbtInts(this.nbtInts) .slot(this.slot) @@ -382,9 +396,11 @@ public static class MenuItemOptionsBuilder { private LoreAppendMode loreAppendMode; private String nbtString; + private String nbtByte; private String nbtShort; private String nbtInt; private List nbtStrings = Collections.emptyList(); + private List nbtBytes = Collections.emptyList(); private List nbtShorts = Collections.emptyList(); private List nbtInts = Collections.emptyList(); @@ -542,6 +558,11 @@ public MenuItemOptionsBuilder nbtString(final @Nullable String nbtString) { return this; } + public MenuItemOptionsBuilder nbtByte(final @Nullable String nbtByte) { + this.nbtByte = nbtByte; + return this; + } + public MenuItemOptionsBuilder nbtShort(final @Nullable String nbtShort) { this.nbtShort = nbtShort; return this; @@ -557,6 +578,11 @@ public MenuItemOptionsBuilder nbtStrings(final @NotNull List nbtStrings) return this; } + public MenuItemOptionsBuilder nbtBytes(final @NotNull List nbtBytes) { + this.nbtBytes = nbtBytes; + return this; + } + public MenuItemOptionsBuilder nbtShorts(final @NotNull List nbtShorts) { this.nbtShorts = nbtShorts; return this; diff --git a/src/main/java/com/extendedclip/deluxemenus/nbt/NbtProvider.java b/src/main/java/com/extendedclip/deluxemenus/nbt/NbtProvider.java index bb37d2a3..3e0c4262 100644 --- a/src/main/java/com/extendedclip/deluxemenus/nbt/NbtProvider.java +++ b/src/main/java/com/extendedclip/deluxemenus/nbt/NbtProvider.java @@ -15,6 +15,7 @@ public final class NbtProvider { private static Method getStringMethod; private static Method setStringMethod; private static Method setBooleanMethod; + private static Method setByteMethod; private static Method setShortMethod; private static Method setIntMethod; private static Method removeTagMethod; @@ -37,6 +38,7 @@ public final class NbtProvider { getStringMethod = compoundClass.getMethod(VersionConstants.GET_STRING_METHOD_NAME, String.class); setStringMethod = compoundClass.getMethod(VersionConstants.SET_STRING_METHOD_NAME, String.class, String.class); setBooleanMethod = compoundClass.getMethod(VersionConstants.SET_BOOLEAN_METHOD_NAME, String.class, boolean.class); + setByteMethod = compoundClass.getMethod(VersionConstants.SET_BYTE_METHOD_NAME, String.class, byte.class); setShortMethod = compoundClass.getMethod(VersionConstants.SET_SHORT_METHOD_NAME, String.class, short.class); setIntMethod = compoundClass.getMethod(VersionConstants.SET_INTEGER_METHOD_NAME, String.class, int.class); removeTagMethod = compoundClass.getMethod(VersionConstants.REMOVE_TAG_METHOD_NAME, String.class); @@ -118,6 +120,19 @@ public static String getString(final ItemStack itemStack, final String key) { return getString(itemCompound, key); } + public static ItemStack setByte(final ItemStack itemStack, final String key, final byte value) { + if (itemStack == null) return null; + if (itemStack.getType() == Material.AIR) return null; + + Object nmsItemStack = asNMSCopy(itemStack); + Object itemCompound = hasTag(nmsItemStack) ? getTag(nmsItemStack) : newNBTTagCompound(); + + setByte(itemCompound, key, value); + setTag(nmsItemStack, itemCompound); + + return asBukkitCopy(nmsItemStack); + } + public static ItemStack setShort(final ItemStack itemStack, final String key, final short value) { if (itemStack == null) return null; if (itemStack.getType() == Material.AIR) return null; @@ -191,6 +206,13 @@ private static void setBoolean(final Object itemCompound, final String key, fina } } + private static void setByte(final Object itemCompound, final String key, final byte value) { + try { + setByteMethod.invoke(itemCompound, key, value); + } catch (IllegalAccessException | InvocationTargetException ignored) { + } + } + private static void setShort(final Object itemCompound, final String key, final short value) { try { setShortMethod.invoke(itemCompound, key, value); @@ -321,6 +343,7 @@ private static class VersionConstants { private final static String GET_STRING_METHOD_NAME = getStringMethodName(); private final static String SET_STRING_METHOD_NAME = setStringMethodName(); private final static String SET_BOOLEAN_METHOD_NAME = setBooleanMethodName(); + private final static String SET_BYTE_METHOD_NAME = setByteMethodName(); private final static String SET_SHORT_METHOD_NAME = setShortMethodName(); private final static String SET_INTEGER_METHOD_NAME = setIntegerMethodName(); private final static String REMOVE_TAG_METHOD_NAME = removeTagMethodName(); @@ -343,6 +366,11 @@ private static String setBooleanMethodName() { return "setBoolean"; } + private static String setByteMethodName() { + if (VersionHelper.HAS_OBFUSCATED_NAMES) return "a"; + return "setByte"; + } + private static String setShortMethodName() { if (VersionHelper.HAS_OBFUSCATED_NAMES) return "a"; return "setShort"; From fcae0e9d5347a0d05f97a15987a2f2eebafdcda4 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:50:32 +0200 Subject: [PATCH 16/25] Upgrade adventure-platform-bukkit to fix formating in 1.21+ --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 17e53953..e06b3db8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ score = "4.23.10.8" # Implementation nashorn = "15.4" -adventure-platform = "4.3.3" +adventure-platform = "4.3.4" adventure-minimessage = "4.17.0" [libraries] From 234798faf12861393a8e55774262dd78cbf8e0c3 Mon Sep 17 00:00:00 2001 From: MemencioPerez Date: Sun, 1 Dec 2024 05:06:08 -0400 Subject: [PATCH 17/25] Access to Sound#valueOf method through Reflection to avoid java.lang.IncompatibleClassChangeError when using versions prior to 1.21.3 --- .../deluxemenus/action/ClickActionTask.java | 5 +++-- .../deluxemenus/utils/SoundUtils.java | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index 2fa55ec5..90c1c18d 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -6,6 +6,7 @@ import com.extendedclip.deluxemenus.utils.AdventureUtils; import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.ExpUtils; +import com.extendedclip.deluxemenus.utils.SoundUtils; import com.extendedclip.deluxemenus.utils.StringUtils; import com.extendedclip.deluxemenus.utils.VersionHelper; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -360,7 +361,7 @@ public void run() { if (!executable.contains(" ")) { try { - sound = Sound.valueOf(executable.toUpperCase()); + sound = SoundUtils.getSound(executable.toUpperCase()); } catch (final IllegalArgumentException exception) { DeluxeMenus.printStacktrace( "Sound name given for sound action: " + executable + ", is not a valid sound!", @@ -372,7 +373,7 @@ public void run() { String[] parts = executable.split(" ", 3); try { - sound = Sound.valueOf(parts[0].toUpperCase()); + sound = SoundUtils.getSound(parts[0].toUpperCase()); } catch (final IllegalArgumentException exception) { DeluxeMenus.printStacktrace( "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java new file mode 100644 index 00000000..97a218e3 --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java @@ -0,0 +1,20 @@ +package com.extendedclip.deluxemenus.utils; + +import org.bukkit.Sound; + +import java.lang.reflect.Method; + +public class SoundUtils { + + public static Sound getSound(String name) { + try { + // As of Minecraft 1.21.3, the org.bukkit.Sound class type changed from Enum to Interface. + // This fixes java.lang.IncompatibleClassChangeError when trying to use versions prior to 1.21.3. + Method valueOfMethod = Class.forName("org.bukkit.Sound").getMethod("valueOf", String.class); + return (Sound) valueOfMethod.invoke(null, name); + } catch (Exception e) { + // Use the Sound#valueOf method if Reflection fails. + return Sound.valueOf(name); + } + } +} From 3a838fa6104e7b5f9db73c0c56f88c9ac9d59b86 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Sat, 7 Dec 2024 18:02:51 +0200 Subject: [PATCH 18/25] Register menu arguments even if the menu does not have an open command specified. --- .../deluxemenus/config/DeluxeMenusConfig.java | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index a441b340..c1053e8e 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -561,39 +561,39 @@ public void loadMenu(FileConfiguration c, String key, boolean mainConfig) { if (!openCommands.isEmpty()) { builder.commands(openCommands); builder.registerCommands(c.getBoolean(pre + "register_command", false)); + } - List argumentNames = new ArrayList<>(); - List argumentRequirements = new ArrayList<>(); - - if (c.contains(pre + "args")) { - // New requirements parsing - if (c.isConfigurationSection(pre + "args")) { - Set mapList = c.getConfigurationSection(pre + "args").getKeys(false); - debug("found args"); - for (String arg : mapList) { - debug("arg: " + arg); - // If it has requirements, add them - if (c.contains(pre + "args." + arg + ".requirements")) { - debug("arg has requirements: " + arg); - argumentRequirements.add(this.getRequirements(c, pre + "args." + arg)); - } - // Always add the arg itself - argumentNames.add(arg); + List argumentNames = new ArrayList<>(); + List argumentRequirements = new ArrayList<>(); + + if (c.contains(pre + "args")) { + // New requirements parsing + if (c.isConfigurationSection(pre + "args")) { + Set mapList = c.getConfigurationSection(pre + "args").getKeys(false); + debug("found args"); + for (String arg : mapList) { + debug("arg: " + arg); + // If it has requirements, add them + if (c.contains(pre + "args." + arg + ".requirements")) { + debug("arg has requirements: " + arg); + argumentRequirements.add(this.getRequirements(c, pre + "args." + arg)); } - // Old list parsing - } else if (c.isList(pre + "args")) { - argumentNames.addAll(c.getStringList(pre + "args")); - // Old singular item parsing - } else if (c.isString(pre + "args")) { - argumentNames.add(c.getString(pre + "args")); + // Always add the arg itself + argumentNames.add(arg); } + // Old list parsing + } else if (c.isList(pre + "args")) { + argumentNames.addAll(c.getStringList(pre + "args")); + // Old singular item parsing + } else if (c.isString(pre + "args")) { + argumentNames.add(c.getString(pre + "args")); } - - builder.arguments(argumentNames); - builder.argumentRequirements(argumentRequirements); - builder.argumentsUsageMessage(c.getString(pre + "args_usage_message", null)); } + builder.arguments(argumentNames); + builder.argumentRequirements(argumentRequirements); + builder.argumentsUsageMessage(c.getString(pre + "args_usage_message", null)); + int size = 54; if (type == InventoryType.CHEST) { if (!c.contains(pre + "size")) { From eba4866bb83c0f019e4321132ef1c982bd2a1fc4 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Thu, 2 Jan 2025 00:12:25 +0200 Subject: [PATCH 19/25] Multitude of changes - Changed singleton usage for plugin class to DI - Removed some static methods in plugin class in favor of their instance counterparts - Split the command class into subcommand classes - Added pagination to the menu list command. The old list format is still available using '/dm list all' - Added an option to suggest the admin '/dm open menu' command in the menu list - Added an option to change the page size for the menu list - Added a new GeneralConfig class to hold and handle the general values from config.yml - Moved some constants around to avoid unnecessary passing of variables --- .../extendedclip/deluxemenus/DeluxeMenus.java | 462 ++++++------- .../deluxemenus/action/ClickActionTask.java | 78 +-- .../command/DeluxeMenusCommand.java | 84 +++ .../command/subcommand/DumpCommand.java | 55 ++ .../command/subcommand/ExecuteCommand.java | 94 +++ .../command/subcommand/HelpCommand.java | 25 + .../command/subcommand/ListCommand.java | 318 +++++++++ .../command/subcommand/OpenCommand.java | 128 ++++ .../command/subcommand/ReloadCommand.java | 64 ++ .../command/subcommand/SubCommand.java | 23 + .../commands/DeluxeMenusCommands.java | 472 ------------- .../deluxemenus/config/DeluxeMenusConfig.java | 638 +++++------------- .../deluxemenus/config/GeneralConfig.java | 72 ++ .../deluxemenus/dupe/DupeFixer.java | 18 +- .../deluxemenus/dupe/MenuItemMarker.java | 4 + .../deluxemenus/hooks/BaseHeadHook.java | 15 +- .../deluxemenus/hooks/HeadDatabaseHook.java | 47 +- .../deluxemenus/hooks/MMOItemsHook.java | 9 +- .../deluxemenus/hooks/NamedHeadHook.java | 18 +- .../deluxemenus/hooks/TextureHeadHook.java | 56 +- .../deluxemenus/listener/Listener.java | 16 + .../deluxemenus/listener/PlayerListener.java | 307 ++++----- .../extendedclip/deluxemenus/menu/Menu.java | 102 ++- .../deluxemenus/menu/MenuHolder.java | 24 +- .../deluxemenus/menu/MenuItem.java | 38 +- .../persistentmeta/PersistentMetaHandler.java | 15 +- .../requirement/HasExpRequirement.java | 7 +- .../requirement/HasItemRequirement.java | 6 +- .../requirement/HasMetaRequirement.java | 7 +- .../requirement/HasMoneyRequirement.java | 13 +- .../requirement/InputResultRequirement.java | 5 +- .../requirement/IsObjectRequirement.java | 3 +- .../requirement/JavascriptRequirement.java | 13 +- .../deluxemenus/requirement/Requirement.java | 5 - .../updatechecker/UpdateChecker.java | 14 +- .../deluxemenus/utils/AdventureUtils.java | 5 +- .../deluxemenus/utils/DumpUtils.java | 20 +- .../deluxemenus/utils/SkullUtils.java | 393 +++++------ 38 files changed, 1902 insertions(+), 1771 deletions(-) create mode 100644 src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/command/subcommand/DumpCommand.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/command/subcommand/ExecuteCommand.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/command/subcommand/HelpCommand.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/command/subcommand/ListCommand.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/command/subcommand/OpenCommand.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/command/subcommand/ReloadCommand.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/command/subcommand/SubCommand.java delete mode 100644 src/main/java/com/extendedclip/deluxemenus/commands/DeluxeMenusCommands.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/config/GeneralConfig.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/listener/Listener.java diff --git a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java index 41b99ae7..44eda4e8 100644 --- a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java +++ b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java @@ -1,14 +1,25 @@ package com.extendedclip.deluxemenus; import com.extendedclip.deluxemenus.cache.SimpleCache; -import com.extendedclip.deluxemenus.commands.DeluxeMenusCommands; +import com.extendedclip.deluxemenus.command.DeluxeMenusCommand; import com.extendedclip.deluxemenus.config.DeluxeMenusConfig; +import com.extendedclip.deluxemenus.config.GeneralConfig; import com.extendedclip.deluxemenus.dupe.DupeFixer; import com.extendedclip.deluxemenus.dupe.MenuItemMarker; -import com.extendedclip.deluxemenus.hooks.*; +import com.extendedclip.deluxemenus.hooks.BaseHeadHook; +import com.extendedclip.deluxemenus.hooks.ExecutableBlocksHook; +import com.extendedclip.deluxemenus.hooks.ExecutableItemsHook; +import com.extendedclip.deluxemenus.hooks.HeadDatabaseHook; +import com.extendedclip.deluxemenus.hooks.ItemHook; +import com.extendedclip.deluxemenus.hooks.ItemsAdderHook; +import com.extendedclip.deluxemenus.hooks.MMOItemsHook; +import com.extendedclip.deluxemenus.hooks.NamedHeadHook; +import com.extendedclip.deluxemenus.hooks.OraxenHook; +import com.extendedclip.deluxemenus.hooks.TextureHeadHook; +import com.extendedclip.deluxemenus.hooks.VaultHook; import com.extendedclip.deluxemenus.listener.PlayerListener; -import com.extendedclip.deluxemenus.menu.options.HeadType; import com.extendedclip.deluxemenus.menu.Menu; +import com.extendedclip.deluxemenus.menu.options.HeadType; import com.extendedclip.deluxemenus.metrics.Metrics; import com.extendedclip.deluxemenus.nbt.NbtProvider; import com.extendedclip.deluxemenus.persistentmeta.PersistentMetaHandler; @@ -19,13 +30,13 @@ import com.extendedclip.deluxemenus.utils.VersionHelper; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; -import me.clip.placeholderapi.PlaceholderAPIPlugin; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; @@ -41,302 +52,271 @@ public class DeluxeMenus extends JavaPlugin { - public final static Map MATERIALS - = Arrays.stream(Material.values()).collect(Collectors.toUnmodifiableMap(Enum::name, Function.identity())); - private static DeluxeMenus instance; - private static DebugLevel debugLevel = DebugLevel.LOWEST; - private DeluxeMenusConfig menuConfig; - private Map itemHooks; - private VaultHook vaultHook; - private boolean checkUpdates; - private ItemStack head; - private PersistentMetaHandler persistentMetaHandler; - private MenuItemMarker menuItemMarker; - private DupeFixer dupeFixer; - private BukkitAudiences adventure; - - @Override - public void onLoad() { - instance = this; - - this.persistentMetaHandler = new PersistentMetaHandler(); - - if (NbtProvider.isAvailable()) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.INFO, - "NMS hook has been setup successfully!" - ); - return; - } + public static final Map MATERIALS = Arrays.stream(Material.values()).collect(Collectors.toUnmodifiableMap(Enum::name, Function.identity())); - debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Could not setup a NMS hook for your server version!" - ); - } - - @SuppressWarnings("deprecation") - @Override - public void onEnable() { - if (!hookPlaceholderAPI()) { - debug( - DebugLevel.HIGHEST, - Level.SEVERE, - "Could not hook into PlaceholderAPI!", - "DeluxeMenus will now disable!" - ); - Bukkit.getPluginManager().disablePlugin(this); - return; - } else { - debug( - DebugLevel.HIGHEST, - Level.INFO, - "Successfully hooked into PlaceholderAPI!" - ); - } + private static final DebugLevel STACKTRACE_PRINT_LEVEL = DebugLevel.MEDIUM; - menuItemMarker = new MenuItemMarker(this); - dupeFixer = new DupeFixer(this, menuItemMarker); + private PersistentMetaHandler persistentMetaHandler; + private MenuItemMarker menuItemMarker; - this.adventure = BukkitAudiences.create(this); + private BukkitAudiences audiences; - setupItemHooks(); + private VaultHook vaultHook; - if (Bukkit.getPluginManager().isPluginEnabled("Vault")) { - vaultHook = new VaultHook(); + private ItemStack head; + private Map itemHooks; - if (vaultHook.hooked()) { - debug( - DebugLevel.HIGHEST, - Level.INFO, - "Successfully hooked into Vault!" - ); - } - } + private final GeneralConfig generalConfig = new GeneralConfig(this); + private DeluxeMenusConfig menuConfig; - if (!VersionHelper.IS_ITEM_LEGACY) { - head = new ItemStack(Material.PLAYER_HEAD, 1); - } else { - head = new ItemStack(Material.valueOf("SKULL_ITEM"), 1, (short) 3); - } + @Override + public void onLoad() { + if (NbtProvider.isAvailable()) { + this.debug(DebugLevel.HIGHEST, Level.INFO, "NMS hook has been setup successfully!"); + return; + } - menuConfig = new DeluxeMenusConfig(this); - if (menuConfig.loadDefConfig()) { - debugLevel(menuConfig.debugLevel()); - checkUpdates = getConfig().getBoolean("check_updates"); - debug( - DebugLevel.HIGHEST, - Level.INFO, - menuConfig.loadGUIMenus() + " GUI menus loaded!" - ); - } else { - debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Failed to load from config.yml. Use /dm reload after fixing your errors." - ); + this.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not setup a NMS hook for your server version!"); } - new PlayerListener(this); - new DeluxeMenusCommands(this); - Bukkit.getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); - - if (checkUpdates) { - UpdateChecker updateChecker = new UpdateChecker(this); - - if (updateChecker.updateAvailable()) { - debug( - DebugLevel.HIGHEST, - Level.INFO, - "An update for DeluxeMenus (DeluxeMenus v" + updateChecker.getLatestVersion() + ")", - "is available at https://www.spigotmc.org/resources/deluxemenus.11734/" - ); - } else { - debug( - DebugLevel.HIGHEST, - Level.INFO, - "You are running the latest version of DeluxeMenus!" - ); - } - } + @Override + public void onEnable() { + this.generalConfig.load(); + + if (!hookIntoPlaceholderAPI()) { + Bukkit.getPluginManager().disablePlugin(this); + return; + } - startMetrics(); + this.persistentMetaHandler = new PersistentMetaHandler(this); + this.menuItemMarker = new MenuItemMarker(this); + new DupeFixer(this, this.menuItemMarker).register(); - new Expansion(this).register(); - } + this.audiences = BukkitAudiences.create(this); - @Override - public void onDisable() { - Bukkit.getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord"); + hookIntoVault(); + setUpItemHooks(); - Bukkit.getScheduler().cancelTasks(this); + this.menuConfig = new DeluxeMenusConfig(this); + if (this.menuConfig.loadDefConfig()) { + debug(DebugLevel.HIGHEST, Level.INFO, menuConfig.loadGUIMenus() + " GUI menus loaded!"); + } else { + debug(DebugLevel.HIGHEST, Level.WARNING, "Failed to load from config.yml. Use /dm reload after fixing your errors."); + } - if (this.adventure != null) { - this.adventure.close(); - this.adventure = null; + new PlayerListener(this).register(); + if (!new DeluxeMenusCommand(this).register()) { + debug(DebugLevel.HIGHEST, Level.SEVERE, "Could not register the DeluxeMenus command!"); + } + new Expansion(this).register(); + + setUpBungeeCordMessaging(); + setUpUpdateChecker(); + setUpMetrics(); } - Menu.unloadForShutdown(); + @Override + public void onDisable() { + Bukkit.getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord"); - itemHooks.clear(); + Bukkit.getScheduler().cancelTasks(this); - instance = null; - } + if (this.audiences != null) { + this.audiences.close(); + this.audiences = null; + } - private void setupItemHooks() { - itemHooks = new HashMap<>(); + Menu.unloadForShutdown(this); - if (PlaceholderAPIPlugin.getServerVersion().isSpigot()) { - itemHooks.put(HeadType.NAMED.getHookName(), new NamedHeadHook(this)); - itemHooks.put(HeadType.BASE64.getHookName(), new BaseHeadHook()); - itemHooks.put(HeadType.TEXTURE.getHookName(), new TextureHeadHook()); - } + itemHooks.clear(); - if (Bukkit.getPluginManager().isPluginEnabled("HeadDatabase")) { - try { - Class.forName("me.arcaniax.hdb.api.HeadDatabaseAPI"); - itemHooks.put(HeadType.HDB.getHookName(), new HeadDatabaseHook()); - } catch (ClassNotFoundException ignored) {} + HandlerList.unregisterAll(this); } - if (Bukkit.getPluginManager().isPluginEnabled("ItemsAdder")) { - itemHooks.put("itemsadder", new ItemsAdderHook()); + public Optional getItemHook(String id) { + return Optional.ofNullable(itemHooks.get(id)); } - if (Bukkit.getPluginManager().isPluginEnabled("Oraxen")) { - itemHooks.put("oraxen", new OraxenHook()); + public Map getItemHooks() { + return itemHooks; } - if (Bukkit.getPluginManager().isPluginEnabled("MMOItems")) { - itemHooks.put("mmoitems", new MMOItemsHook()); + public ItemStack getHead() { + return head != null ? head : new ItemStack(Material.DIRT, 1); } - if (Bukkit.getPluginManager().isPluginEnabled("ExecutableItems")) { - itemHooks.put("executableitems", new ExecutableItemsHook()); + public boolean shouldPrintStackTrace() { + return generalConfig.debugLevel().getPriority() <= STACKTRACE_PRINT_LEVEL.getPriority(); } - if (Bukkit.getPluginManager().isPluginEnabled("ExecutableBlocks")) { - itemHooks.put("executableblocks", new ExecutableBlocksHook()); + public void printStacktrace(final String message, final Throwable throwable) { + if (!shouldPrintStackTrace()) return; + + this.getLogger().log(Level.SEVERE, message, throwable); } - } + public void connect(Player p, String server) { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); - public Optional getItemHook(String id) { - return Optional.ofNullable(itemHooks.get(id)); - } + try { + out.writeUTF("Connect"); + out.writeUTF(server); + } catch (Exception e) { + debug(DebugLevel.HIGHEST, Level.SEVERE, "There was a problem attempting to send " + p.getName() + " to server " + server + "!"); - public Map getItemHooks() { - return itemHooks; - } + printStacktrace("There was a problem attempting to send " + p.getName() + " to server " + server + "!", e); + } - public ItemStack getHead() { - return head != null ? head : new ItemStack(Material.DIRT, 1); - } + p.sendPluginMessage(this, "BungeeCord", out.toByteArray()); + } - public static DeluxeMenus getInstance() { - return instance; - } + public void sms(CommandSender s, Component msg) { + audiences().sender(s).sendMessage(msg); + } - public static DebugLevel debugLevel() { - return debugLevel; - } + public void sms(CommandSender s, Messages msg) { + audiences().sender(s).sendMessage(msg.message()); + } - public static void debugLevel(final DebugLevel level) { - debugLevel = level; - } + public void debug(@NotNull final DebugLevel messageDebugLevel, @NotNull final Level level, @NotNull final String... messages) { + this.debug(generalConfig.debugLevel(), messageDebugLevel, level, messages); + } - public static DebugLevel printStacktraceLevel() { - return DebugLevel.MEDIUM; - } + public void debug(@NotNull final DebugLevel generalDebugLevel, @NotNull final DebugLevel messageDebugLevel, @NotNull final Level level, @NotNull final String... messages) { + if (generalDebugLevel.getPriority() > messageDebugLevel.getPriority()) return; - public static boolean shouldPrintStackTrace() { - return debugLevel().getPriority() <= printStacktraceLevel().getPriority(); - } + this.getLogger().log(level, String.join(System.lineSeparator(), messages)); + } - public static void printStacktrace(final String message, final Throwable throwable) { - if (!shouldPrintStackTrace()) return; + public MenuItemMarker getMenuItemMarker() { + return menuItemMarker; + } - getInstance().getLogger().log(Level.SEVERE, message, throwable); - } + public DeluxeMenusConfig getConfiguration() { + return menuConfig; + } - private void startMetrics() { - Metrics metrics = new Metrics(this, 445); - metrics.addCustomChart(new Metrics.SingleLineChart("menus", Menu::getLoadedMenuSize)); - } + public VaultHook getVault() { + return vaultHook; + } - public void connect(Player p, String server) { - ByteArrayDataOutput out = ByteStreams.newDataOutput(); + public PersistentMetaHandler getPersistentMetaHandler() { + return persistentMetaHandler; + } - try { - out.writeUTF("Connect"); - out.writeUTF(server); - } catch (Exception e) { - debug( - DebugLevel.HIGHEST, - Level.SEVERE, - "There was a problem attempting to send " + p.getName() + " to server " + server + "!" - ); + public BukkitAudiences audiences() { + if (this.audiences == null) { + throw new IllegalStateException("Tried to access Adventure when the plugin was disabled!"); + } + return this.audiences; + } - printStacktrace( - "There was a problem attempting to send " + p.getName() + " to server " + server + "!", - e - ); + public void clearCaches() { + itemHooks.values().stream().filter(Objects::nonNull).filter(hook -> hook instanceof SimpleCache).map(hook -> (SimpleCache) hook).forEach(SimpleCache::clearCache); } - p.sendPluginMessage(this, "BungeeCord", out.toByteArray()); - } + public void reload() { + this.generalConfig.reload(); + } - public void sms(CommandSender s, Component msg) { - adventure().sender(s).sendMessage(msg); - } + public GeneralConfig getGeneralConfig() { + return generalConfig; + } - public void sms(CommandSender s, Messages msg) { - adventure().sender(s).sendMessage(msg.message()); - } + private boolean hookIntoPlaceholderAPI() { + final boolean canHook = Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null; + if (!canHook) { + this.debug(DebugLevel.HIGHEST, Level.SEVERE, "Could not hook into PlaceholderAPI!", "DeluxeMenus will now disable!"); + return false; + } - public static void debug(@NotNull final DebugLevel debugLevel, @NotNull final Level level, @NotNull final String... messages) { - if (debugLevel().getPriority() > debugLevel.getPriority()) return; + this.debug(DebugLevel.HIGHEST, Level.INFO, "Successfully hooked into PlaceholderAPI!"); + return true; + } - getInstance().getLogger().log( - level, - String.join(System.lineSeparator(), messages) - ); - } + private void hookIntoVault() { + if (!Bukkit.getPluginManager().isPluginEnabled("Vault")) { + return; + } + this.vaultHook = new VaultHook(); - private boolean hookPlaceholderAPI() { - return Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null; - } + if (this.vaultHook.hooked()) { + this.debug(DebugLevel.HIGHEST, Level.INFO, "Successfully hooked into Vault!"); + return; + } - public MenuItemMarker getMenuItemMarker() { - return menuItemMarker; - } + this.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not hook into Vault!", + "DeluxeMenus will continue to work but some features (such as the 'has money' requirement) may not be available."); + } + + @SuppressWarnings("deprecation") + private void setUpItemHooks() { + if (!VersionHelper.IS_ITEM_LEGACY) { + this.head = new ItemStack(Material.PLAYER_HEAD, 1); + } else { + this.head = new ItemStack(Material.valueOf("SKULL_ITEM"), 1, (short) 3); + } + + this.itemHooks = new HashMap<>(); + + final NamedHeadHook namedHeadHook = new NamedHeadHook(this); + namedHeadHook.register(); + this.itemHooks.put(HeadType.NAMED.getHookName(), namedHeadHook); + this.itemHooks.put(HeadType.BASE64.getHookName(), new BaseHeadHook(this)); + this.itemHooks.put(HeadType.TEXTURE.getHookName(), new TextureHeadHook(this)); + + if (Bukkit.getPluginManager().isPluginEnabled("HeadDatabase")) { + try { + Class.forName("me.arcaniax.hdb.api.HeadDatabaseAPI"); + this.itemHooks.put(HeadType.HDB.getHookName(), new HeadDatabaseHook(this)); + } catch (ClassNotFoundException ignored) { + // We are looking for this specific class because we've had issues with other plugins being named HeadDatabase + // in the past + } + } + + if (Bukkit.getPluginManager().isPluginEnabled("ItemsAdder")) { + this.itemHooks.put("itemsadder", new ItemsAdderHook()); + } + + if (Bukkit.getPluginManager().isPluginEnabled("Oraxen")) { + this.itemHooks.put("oraxen", new OraxenHook()); + } + + if (Bukkit.getPluginManager().isPluginEnabled("MMOItems")) { + this.itemHooks.put("mmoitems", new MMOItemsHook(this)); + } + + if (Bukkit.getPluginManager().isPluginEnabled("ExecutableItems")) { + this.itemHooks.put("executableitems", new ExecutableItemsHook()); + } + + if (Bukkit.getPluginManager().isPluginEnabled("ExecutableBlocks")) { + this.itemHooks.put("executableblocks", new ExecutableBlocksHook()); + } + } - public DeluxeMenusConfig getConfiguration() { - return menuConfig; - } + private void setUpBungeeCordMessaging() { + Bukkit.getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); + } - public VaultHook getVault() { - return vaultHook; - } + private void setUpUpdateChecker() { + if (!this.generalConfig.checkForUpdates()) { + return; + } - public PersistentMetaHandler getPersistentMetaHandler() { - return persistentMetaHandler; - } + final UpdateChecker updateChecker = new UpdateChecker(this); + updateChecker.register(); + + if (updateChecker.updateAvailable()) { + this.debug(DebugLevel.HIGHEST, Level.INFO, "An update for DeluxeMenus (DeluxeMenus v" + updateChecker.getLatestVersion() + ")", "is available at https://www.spigotmc.org/resources/deluxemenus.11734/"); + return; + } + + this.debug(DebugLevel.HIGHEST, Level.INFO, "You are running the latest version of DeluxeMenus!"); + } - public BukkitAudiences adventure() { - if (this.adventure == null) { - throw new IllegalStateException("Tried to access Adventure when the plugin was disabled!"); + private void setUpMetrics() { + final Metrics metrics = new Metrics(this, 445); } - return this.adventure; - } - - public void clearCaches() { - itemHooks.values().stream() - .filter(Objects::nonNull) - .filter(hook -> hook instanceof SimpleCache) - .map(hook -> (SimpleCache) hook) - .forEach(SimpleCache::clearCache); - } } diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index 2fa55ec5..dbc48589 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -75,18 +75,18 @@ public void run() { switch (actionType) { case META: - if (!VersionHelper.IS_PDC_VERSION || DeluxeMenus.getInstance().getPersistentMetaHandler() == null) { - DeluxeMenus.debug(DebugLevel.HIGHEST, Level.INFO, "Meta action not supported on this server version."); + if (!VersionHelper.IS_PDC_VERSION || plugin.getPersistentMetaHandler() == null) { + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Meta action not supported on this server version."); break; } try { - final boolean result = DeluxeMenus.getInstance().getPersistentMetaHandler().setMeta(player, executable); + final boolean result = plugin.getPersistentMetaHandler().setMeta(player, executable); if (!result) { - DeluxeMenus.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Make sure you have the right syntax."); + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Make sure you have the right syntax."); break; } } catch (final NumberFormatException exception) { - DeluxeMenus.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid integer value for meta action!"); + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid integer value for meta action!"); } break; @@ -111,11 +111,11 @@ public void run() { break; case MINI_MESSAGE: - plugin.adventure().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); + plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); break; case MINI_BROADCAST: - plugin.adventure().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); + plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); break; case MESSAGE: @@ -127,7 +127,7 @@ public void run() { break; case CLOSE: - Menu.closeMenu(player, true, true); + Menu.closeMenu(plugin, player, true, true); break; case OPEN_GUI_MENU: @@ -136,7 +136,7 @@ public void run() { final String[] executableParts = temporaryExecutable.split(" ", 2); if (executableParts.length == 0) { - DeluxeMenus.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); break; } @@ -145,7 +145,7 @@ public void run() { final Optional optionalMenuToOpen = Menu.getMenuByName(menuName); if (optionalMenuToOpen.isEmpty()) { - DeluxeMenus.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); break; } @@ -160,7 +160,7 @@ public void run() { if (menuArgumentNames.isEmpty()) { if (passedArgumentValues != null && passedArgumentValues.length > 0) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Arguments were given for menu " + menuName + " in action [openguimenu] or [openmenu], but the menu does not support arguments!" @@ -188,7 +188,7 @@ public void run() { } if (passedArgumentValues.length < menuArgumentNames.size()) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" @@ -208,7 +208,7 @@ public void run() { if (passedArgumentValues.length <= index) { // This should never be the case! - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" @@ -235,21 +235,21 @@ public void run() { break; case CONNECT: - DeluxeMenus.getInstance().connect(player, executable); + plugin.connect(player, executable); break; case JSON_MESSAGE: - AdventureUtils.sendJson(player, executable); + AdventureUtils.sendJson(plugin, player, executable); break; case JSON_BROADCAST: case BROADCAST_JSON: - plugin.adventure().all().sendMessage(AdventureUtils.fromJson(executable)); + plugin.audiences().all().sendMessage(AdventureUtils.fromJson(executable)); break; case REFRESH: if (holder.isEmpty()) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.MEDIUM, Level.WARNING, player.getName() + " does not have menu open! Nothing to refresh!" @@ -261,15 +261,15 @@ public void run() { break; case TAKE_MONEY: - if (DeluxeMenus.getInstance().getVault() == null || !DeluxeMenus.getInstance().getVault().hooked()) { - DeluxeMenus.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot take money!"); + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot take money!"); break; } try { - DeluxeMenus.getInstance().getVault().takeMoney(player, Double.parseDouble(executable)); + plugin.getVault().takeMoney(player, Double.parseDouble(executable)); } catch (final NumberFormatException exception) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Amount for take money action: " + executable + ", is not a valid number!" @@ -278,15 +278,15 @@ public void run() { break; case GIVE_MONEY: - if (DeluxeMenus.getInstance().getVault() == null || !DeluxeMenus.getInstance().getVault().hooked()) { - DeluxeMenus.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot give money!"); + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot give money!"); break; } try { - DeluxeMenus.getInstance().getVault().giveMoney(player, Double.parseDouble(executable)); + plugin.getVault().giveMoney(player, Double.parseDouble(executable)); } catch (final NumberFormatException exception) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Amount for give money action: " + executable + ", is not a valid number!" @@ -311,7 +311,7 @@ public void run() { } catch (final NumberFormatException exception) { if (actionType == ActionType.TAKE_EXP) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Amount for take exp action: " + executable + ", is not a valid number!" @@ -319,7 +319,7 @@ public void run() { break; } - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Amount for give exp action: " + executable + ", is not a valid number!" @@ -328,27 +328,27 @@ public void run() { } case GIVE_PERM: - if (DeluxeMenus.getInstance().getVault() == null || !DeluxeMenus.getInstance().getVault().hooked()) { - DeluxeMenus.debug( + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot give permission: " + executable + "!"); break; } - DeluxeMenus.getInstance().getVault().givePermission(player, executable); + plugin.getVault().givePermission(player, executable); break; case TAKE_PERM: - if (DeluxeMenus.getInstance().getVault() == null || !DeluxeMenus.getInstance().getVault().hooked()) { - DeluxeMenus.debug( + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot take permission: " + executable + "!"); break; } - DeluxeMenus.getInstance().getVault().takePermission(player, executable); + plugin.getVault().takePermission(player, executable); break; case BROADCAST_SOUND: @@ -362,7 +362,7 @@ public void run() { try { sound = Sound.valueOf(executable.toUpperCase()); } catch (final IllegalArgumentException exception) { - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Sound name given for sound action: " + executable + ", is not a valid sound!", exception ); @@ -374,7 +374,7 @@ public void run() { try { sound = Sound.valueOf(parts[0].toUpperCase()); } catch (final IllegalArgumentException exception) { - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", exception ); @@ -385,13 +385,13 @@ public void run() { try { pitch = Float.parseFloat(parts[2]); } catch (final NumberFormatException exception) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Pitch given for sound action: " + parts[2] + ", is not a valid number!" ); - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Pitch given for sound action: " + parts[2] + ", is not a valid number!", exception ); @@ -402,13 +402,13 @@ public void run() { try { volume = Float.parseFloat(parts[1]); } catch (final NumberFormatException exception) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Volume given for sound action: " + parts[1] + ", is not a valid number!" ); - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Volume given for sound action: " + parts[1] + ", is not a valid number!", exception ); diff --git a/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java new file mode 100644 index 00000000..948a944e --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java @@ -0,0 +1,84 @@ +package com.extendedclip.deluxemenus.command; + +import com.extendedclip.deluxemenus.DeluxeMenus; +import com.extendedclip.deluxemenus.command.subcommand.DumpCommand; +import com.extendedclip.deluxemenus.command.subcommand.ExecuteCommand; +import com.extendedclip.deluxemenus.command.subcommand.HelpCommand; +import com.extendedclip.deluxemenus.command.subcommand.ListCommand; +import com.extendedclip.deluxemenus.command.subcommand.OpenCommand; +import com.extendedclip.deluxemenus.command.subcommand.ReloadCommand; +import com.extendedclip.deluxemenus.command.subcommand.SubCommand; +import com.extendedclip.deluxemenus.utils.Messages; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextReplacementConfig; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static net.kyori.adventure.text.Component.text; + +public class DeluxeMenusCommand implements CommandExecutor { + + private static final TextReplacementConfig.Builder VERSION_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); + private static final TextReplacementConfig.Builder AUTHORS_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); + + private final DeluxeMenus plugin; + private final Map subCommands = new HashMap<>(); + + public DeluxeMenusCommand(final @NotNull DeluxeMenus plugin) { + this.plugin = plugin; + } + + public boolean register() { + final PluginCommand command = this.plugin.getCommand("deluxemenus"); + if (command == null) { + return false; + } + + command.setExecutor(this); + registerSubCommands(); + return true; + } + + @Override + public boolean onCommand( + final @NotNull CommandSender sender, + final @NotNull Command command, + final @NotNull String label, + final @NotNull String[] args + ) { + final List arguments = Arrays.asList(args); + + if (arguments.isEmpty()) { + plugin.sms(sender, Messages.PLUGIN_VERSION.message().replaceText(VERSION_REPLACER_BUILDER.replacement(plugin.getDescription().getVersion()).build()).replaceText(AUTHORS_REPLACER_BUILDER.replacement(plugin.getDescription().getAuthors().stream().map(author -> text(author, NamedTextColor.WHITE)).collect(Component.toComponent(text(", ", NamedTextColor.GRAY)))).build())); + return true; + } + + final SubCommand subCommand = subCommands.get(arguments.get(0).toLowerCase()); + + if (subCommand != null) { + subCommand.execute(sender, arguments.subList(1, arguments.size())); + return true; + } + + plugin.sms(sender, Messages.WRONG_USAGE); + return true; + } + + private void registerSubCommands() { + subCommands.put("dump", new DumpCommand(plugin)); + subCommands.put("execute", new ExecuteCommand(plugin)); + subCommands.put("help", new HelpCommand(plugin)); + subCommands.put("list", new ListCommand(plugin)); + subCommands.put("open", new OpenCommand(plugin)); + subCommands.put("reload", new ReloadCommand(plugin)); + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/DumpCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/DumpCommand.java new file mode 100644 index 00000000..013af95b --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/DumpCommand.java @@ -0,0 +1,55 @@ +package com.extendedclip.deluxemenus.command.subcommand; + +import com.extendedclip.deluxemenus.DeluxeMenus; +import com.extendedclip.deluxemenus.utils.DumpUtils; +import com.extendedclip.deluxemenus.utils.Messages; +import net.kyori.adventure.text.event.ClickEvent; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +import static net.kyori.adventure.text.Component.text; + +public class DumpCommand extends SubCommand { + + public DumpCommand(final @NotNull DeluxeMenus plugin) { + super(plugin); + } + + @Override + public void execute(final @NotNull CommandSender sender, final @NotNull List args) { + if (!sender.hasPermission("deluxemenus.admin")) { + plugin.sms(sender, Messages.NO_PERMISSION); + return; + } + + if (args.size() != 1) { + plugin.sms(sender, Messages.WRONG_USAGE_DUMP_COMMAND); + return; + } + + String dump = ""; + try { + dump = DumpUtils.createDump(plugin, args.get(0)); + } catch (final RuntimeException ignored) { + } + + if (dump.isBlank()) { + plugin.sms(sender, Messages.DUMP_FAILED); + return; + } + + DumpUtils.postDump(dump).whenComplete((result, error) -> { + if (error != null) { + plugin.printStacktrace("Something went wrong while trying to create and post a dump!", error); + plugin.sms(sender, Messages.DUMP_FAILED); + return; + } + + final var link = text(DumpUtils.URL + result).clickEvent(ClickEvent.openUrl(DumpUtils.URL + result)); + + plugin.sms(sender, Messages.DUMP_SUCCESS.message().append(link)); + }); + } +} \ No newline at end of file diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ExecuteCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ExecuteCommand.java new file mode 100644 index 00000000..533ca1b6 --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ExecuteCommand.java @@ -0,0 +1,94 @@ +package com.extendedclip.deluxemenus.command.subcommand; + +import com.extendedclip.deluxemenus.DeluxeMenus; +import com.extendedclip.deluxemenus.action.ActionType; +import com.extendedclip.deluxemenus.action.ClickAction; +import com.extendedclip.deluxemenus.action.ClickActionTask; +import com.extendedclip.deluxemenus.config.DeluxeMenusConfig; +import com.extendedclip.deluxemenus.menu.Menu; +import com.extendedclip.deluxemenus.menu.MenuHolder; +import com.extendedclip.deluxemenus.utils.Messages; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ExecuteCommand extends SubCommand { + + public ExecuteCommand(final @NotNull DeluxeMenus plugin) { + super(plugin); + } + + @Override + public void execute(final @NotNull CommandSender sender, final @NotNull List args) { + if (!sender.isOp()) { + plugin.sms(sender, Messages.NO_PERMISSION); + return; + } + + if (args.size() < 2) { + plugin.sms(sender, Messages.WRONG_USAGE_EXECUTE_COMMAND); + return; + } + + Player target = Bukkit.getPlayerExact(args.get(0)); + if (target == null) { + plugin.sms(sender, Messages.PLAYER_IS_NOT_ONLINE.message().replaceText(PLAYER_REPLACER_BUILDER.replacement(args.get(1)).build())); + return; + } + + String executable = String.join(" ", args.subList(1, args.size())); + + ActionType type = ActionType.getByStart(executable); + + if (type == null) { + plugin.sms(sender, Messages.WRONG_ACTION_TYPE); + return; + } + + executable = executable.replaceFirst(Pattern.quote(type.getIdentifier()), "").trim(); + + ClickAction action = new ClickAction(type, executable); + + Matcher d = DeluxeMenusConfig.DELAY_MATCHER.matcher(executable); + + if (d.find()) { + action.setDelay(d.group(1)); + executable = executable.replaceFirst(Pattern.quote(d.group()), ""); + } + + Matcher ch = DeluxeMenusConfig.CHANCE_MATCHER.matcher(executable); + + if (ch.find()) { + action.setChance(ch.group(1)); + executable = executable.replaceFirst(Pattern.quote(ch.group()), ""); + } + + action.setExecutable(executable); + + MenuHolder holder = Menu.getMenuHolder(target).orElse(new MenuHolder(plugin, target)); + + if (!action.checkChance(holder)) { + plugin.sms(sender, Messages.CHANCE_FAIL); + return; + } + + final ClickActionTask actionTask = new ClickActionTask(plugin, target.getUniqueId(), action.getType(), action.getExecutable(), holder.getTypedArgs(), true, true); + + if (action.hasDelay()) { + actionTask.runTaskLater(plugin, action.getDelay(holder)); + + plugin.sms(sender, Messages.ACTION_TO_BE_EXECUTED.message().replaceText(AMOUNT_REPLACER_BUILDER.replacement(String.valueOf(action.getDelay(holder))).build())); + return; + } + + actionTask.runTask(plugin); + + plugin.sms(sender, Messages.ACTION_EXECUTED_FOR.message().replaceText(PLAYER_REPLACER_BUILDER.replacement(target.getName()).build())); + + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/HelpCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/HelpCommand.java new file mode 100644 index 00000000..1500c154 --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/HelpCommand.java @@ -0,0 +1,25 @@ +package com.extendedclip.deluxemenus.command.subcommand; + +import com.extendedclip.deluxemenus.DeluxeMenus; +import com.extendedclip.deluxemenus.utils.Messages; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class HelpCommand extends SubCommand { + + public HelpCommand(final @NotNull DeluxeMenus plugin) { + super(plugin); + } + + @Override + public void execute(final @NotNull CommandSender sender, final @NotNull List args) { + if (sender.hasPermission("deluxemenus.admin")) { + plugin.sms(sender, Messages.HELP_ADMIN); + return; + } + + plugin.sms(sender, Messages.HELP); + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ListCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ListCommand.java new file mode 100644 index 00000000..a1858239 --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ListCommand.java @@ -0,0 +1,318 @@ +package com.extendedclip.deluxemenus.command.subcommand; + +import com.extendedclip.deluxemenus.DeluxeMenus; +import com.extendedclip.deluxemenus.menu.Menu; +import com.extendedclip.deluxemenus.utils.Messages; +import com.google.common.primitives.Ints; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import static net.kyori.adventure.text.Component.newline; +import static net.kyori.adventure.text.Component.text; + +public class ListCommand extends SubCommand { + + public ListCommand(final @NotNull DeluxeMenus plugin) { + super(plugin); + } + + @Override + public void execute(final @NotNull CommandSender sender, final @NotNull List args) { + if (!sender.hasPermission("deluxemenus.list")) { + plugin.sms(sender, Messages.NO_PERMISSION); + return; + } + + if (!args.isEmpty() && args.get(0).equalsIgnoreCase("all")) { + final Collection menus = Menu.getAllMenus(); + if (menus.isEmpty()) { + plugin.sms(sender, Messages.MENUS_LOADED.message().replaceText(AMOUNT_REPLACER_BUILDER.replacement("There are no").build())); + return; + } + + sendSimpleMenuList(sender, Menu.getAllMenus()); + return; + } + + final Map> menus = Menu.getPathSortedMenus(); + if (menus.isEmpty() || menus.values().stream().allMatch(List::isEmpty)) { + plugin.sms(sender, Messages.MENUS_LOADED.message().replaceText(AMOUNT_REPLACER_BUILDER.replacement("There are no").build())); + return; + } + + final List configMenus = menus.remove("config"); + sendPaginatedMenuList(sender, menus, configMenus == null ? Collections.emptyList() : configMenus, args); + } + + private void sendSimpleMenuList(final @NotNull CommandSender sender, final @NotNull Collection menus) { + final TextComponent.Builder list = text(); + list.append(text("The following " + menus.size() + " menus are loaded on the server:", NamedTextColor.GOLD).append(newline())); + + if (sender instanceof ConsoleCommandSender) { + list.append(newline()); + + final Component menusList = menus.stream().map(menu -> { + final String menuCommand = getMenuDisplayCommand(menu); + return menuCommand == null + ? text(menu.options().name(), NamedTextColor.DARK_AQUA).append(text(" - ", NamedTextColor.GRAY)).append(text("No menu command", NamedTextColor.RED)) + : text(menu.options().name(), NamedTextColor.DARK_AQUA).append(text(" - ", NamedTextColor.GRAY)).append(text(menuCommand, NamedTextColor.GREEN)); + }).collect(Component.toComponent(text(" | ", NamedTextColor.WHITE))); + + plugin.sms(sender, list.append(menusList).build()); + return; + } + + list.append(text("**Hover menu name for more info**", NamedTextColor.GRAY)).append(newline()).append(newline()); + + final var menusList = menus.stream().map(menu -> { + final String menuCommand = getMenuDisplayCommand(menu); + + return menuCommand == null + ? text(menu.options().name(), NamedTextColor.DARK_AQUA).hoverEvent(HoverEvent.showText(text("No open command", NamedTextColor.GOLD))) + : text(menu.options().name(), NamedTextColor.DARK_AQUA).hoverEvent(HoverEvent.showText(text("Open Command: ", NamedTextColor.GOLD).append(text(menuCommand, NamedTextColor.YELLOW)))).clickEvent(ClickEvent.suggestCommand(menuCommand)); + }).collect(Component.toComponent(text(", ", NamedTextColor.WHITE))); + + list.append(menusList); + plugin.sms(sender, list.build()); + } + + private void sendPaginatedMenuList(final @NotNull CommandSender sender, final @NotNull Map> menus, + final @NotNull List configMenus, final @NotNull List args) { + final int totalMenusCount = configMenus.size() + menus.values().stream().mapToInt(List::size).sum(); + + Integer page = null; + if (totalMenusCount > plugin.getGeneralConfig().menusListPageSize() && !args.isEmpty()) { + page = Ints.tryParse(args.get(0)); + } + + final int maxPages = (int) Math.ceil((double) totalMenusCount / plugin.getGeneralConfig().menusListPageSize()); + + if (page == null || page < 1) { + page = 1; + } + + if (page > maxPages) { + page = maxPages; + } + + final Map> paginatedMenus = getPaginatedMenus( + menus, + configMenus.stream().collect(TreeMap::new, (map, menu) -> map.put(menu.options().name(), menu), TreeMap::putAll), + page, + plugin.getGeneralConfig().menusListPageSize() + ); + + final int pageMenusCount = paginatedMenus.values().stream().mapToInt(List::size).sum(); + final Map pageMenusTree = convertMenusToTree(paginatedMenus); + + final TextComponent.Builder list = text(); + list.append(text("Page " + page + "/" + maxPages + " - " + pageMenusCount + " menus:", NamedTextColor.GOLD).append(newline())); + + if (sender instanceof ConsoleCommandSender) { + final var menuList = createMenuListForConsole(pageMenusTree, 0); + + list.append(newline()).append(menuList).append(newline()).append(text("Use /dm list to view more menus", NamedTextColor.GRAY)); + plugin.sms(sender, list.build()); + return; + } + + list.append(text("**Hover menu name for more info**", NamedTextColor.GRAY)); + list.append(newline()).append(newline()); + + final var menuList = createMenuListForPlayer(pageMenusTree, 0); + + list.append(menuList); + + if (page > 1 || page < maxPages) { + list.append(newline()); + + if (page > 1) { + list.append(text("<< Previous", NamedTextColor.GOLD) + .hoverEvent(HoverEvent.showText( + text("Click to go to the previous page", NamedTextColor.GRAY) + .append(newline()).append(newline()) + .append(text("Executes: /dm list " + (page - 1), NamedTextColor.GRAY)) + )) + .clickEvent(ClickEvent.runCommand("/dm list " + (page - 1)))); + if (page < maxPages) { + list.append(text(" | ", NamedTextColor.GREEN)); + } + } + + if (page < maxPages) { + list.append(text("Next >>", NamedTextColor.GOLD) + .hoverEvent(HoverEvent.showText( + text("Click to go to the next page", NamedTextColor.GRAY) + .append(newline()).append(newline()) + .append(text("Executes: /dm list " + (page + 1), NamedTextColor.GRAY)) + )) + .clickEvent(ClickEvent.runCommand("/dm list " + (page + 1)))); + } + } + + plugin.sms(sender, list.build()); + } + + private Map> getPaginatedMenus(final Map> menus, + final @NotNull Map configMenus, + final int page, + final int pageSize + ) { + final Map> paginatedMenus = new LinkedHashMap<>(); + final int start = (page - 1) * pageSize; + final int end = start + pageSize; + + int count = 0; + int i = 0; + for (final Map.Entry entry : configMenus.entrySet()) { + if (count >= pageSize || i >= end) { + break; + } + + if (i < start) { + i++; + continue; + } + + paginatedMenus.computeIfAbsent("config", k -> new ArrayList<>()).add(entry.getValue()); + count++; + i++; + } + + for (final Map.Entry> entry : menus.entrySet()) { + if (count >= pageSize || i >= end) { + break; + } + + for (final Menu menu : entry.getValue()) { + if (count >= pageSize || i >= end) { + break; + } + + if (i < start) { + i++; + continue; + } + + paginatedMenus.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).add(menu); + count++; + i++; + } + } + + return paginatedMenus; + } + + @SuppressWarnings("unchecked") + private Component createMenuListForConsole(final Map tree, int tabs) { + final TextComponent.Builder list = text(); + + for (final Map.Entry entry : tree.entrySet()) { + if (entry.getValue() instanceof List) { + for (final Menu menu : (List) entry.getValue()) { + list.append(text(" ".repeat(tabs) + "- " + entry.getKey(), NamedTextColor.DARK_AQUA).append(text(" - ", NamedTextColor.GRAY)).append(text(menu.options().name(), NamedTextColor.GREEN)).append(newline())); + } + } else { + list.append(text(" ".repeat(tabs) + "|- " + entry.getKey(), NamedTextColor.DARK_AQUA)).append(newline()); + list.append(createMenuListForConsole((Map) entry.getValue(), tabs + 1)); + } + } + + return list.build(); + } + + @SuppressWarnings("unchecked") + private Component createMenuListForPlayer(final Map tree, int tabs) { + final TextComponent.Builder list = text(); + + for (final Map.Entry entry : tree.entrySet()) { + if (!(entry.getValue() instanceof List)) { + list.append(text(" ".repeat(tabs) + "|-" + entry.getKey(), NamedTextColor.DARK_AQUA)).append(newline()); + list.append(createMenuListForPlayer((Map) entry.getValue(), tabs + 1)); + continue; + } + + for (final Menu menu : (List) entry.getValue()) { + final String menuCommand = getMenuDisplayCommand(menu); + + list.append( + text(" ".repeat(tabs) + "- " + entry.getKey(), NamedTextColor.DARK_AQUA).append(text(" - ", NamedTextColor.GRAY)).append(text(menu.options().name(), NamedTextColor.GREEN)) + .hoverEvent(HoverEvent.showText(menuCommand != null ? text("Open Command: ", NamedTextColor.GOLD).append(text(menuCommand, NamedTextColor.YELLOW)) : text("No open command", NamedTextColor.GOLD))) + .clickEvent(ClickEvent.suggestCommand((menuCommand != null ? menuCommand : "No open command"))) + ).append(newline()); + } + } + + return list.build(); + } + + private Map convertMenusToTree(final Map> menus) { + final Map tree = new LinkedHashMap<>(); + + for (final Map.Entry> entry : menus.entrySet()) { + final String[] path = entry.getKey().split("/"); + addMenuToTreeRecursively(tree, List.of(path), entry.getValue(), 0); + } + + return tree; + } + + @SuppressWarnings("unchecked") + private void addMenuToTreeRecursively(final Map tree, final List path, final List menus, final int step) { + if (step < 0 || step >= path.size()) { + return; + } + + if (step == path.size() - 1) { + if (!tree.containsKey(path.get(step))) { + tree.put(path.get(step), new ArrayList<>(menus)); + } else { + final List list = (List) tree.get(path.get(step)); + list.addAll(menus); + } + return; + } + + final String value = path.get(step); + if (!tree.containsKey(value)) { + tree.put(value, new TreeMap<>()); + } + + addMenuToTreeRecursively((Map) tree.get(value), path, menus, step + 1); + } + + /** + * Get the command that can be used to open this menu. + * The response will be the first command in the list of commands for this menu. + * If the config option to use admin commands in menus list is enabled, the admin "/dm open" command will be returned. + * @return The command that can be used to open this menu. + */ + public @Nullable String getMenuDisplayCommand(final @NotNull Menu menu) { + final boolean useAdminCommand = this.plugin.getGeneralConfig().useAdminCommandsInMenusList(); + + if (useAdminCommand) { + return "/deluxemenus open " + menu.options().name(); + } + + if (menu.options().commands().isEmpty()) { + return null; + } + + return "/" + menu.options().commands().get(0); + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/OpenCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/OpenCommand.java new file mode 100644 index 00000000..9e83eaab --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/OpenCommand.java @@ -0,0 +1,128 @@ +package com.extendedclip.deluxemenus.command.subcommand; + +import com.extendedclip.deluxemenus.DeluxeMenus; +import com.extendedclip.deluxemenus.menu.Menu; +import com.extendedclip.deluxemenus.utils.Messages; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Optional; + +public class OpenCommand extends SubCommand { + + public OpenCommand(final @NotNull DeluxeMenus plugin) { + super(plugin); + } + + @Override + public void execute(final @NotNull CommandSender sender, final @NotNull List args) { + if (!sender.hasPermission("deluxemenus.open")) { + plugin.sms(sender, Messages.NO_PERMISSION); + return; + } + + boolean player = (sender instanceof Player); + + if (args.isEmpty()) { + plugin.sms(sender, Messages.WRONG_USAGE_OPEN_COMMAND); + return; + } + + if (Menu.getAllMenus().isEmpty()) { + plugin.sms(sender, Messages.MENUS_LOADED.message().replaceText(AMOUNT_REPLACER_BUILDER.replacement("There are no").build())); + return; + } + + Player viewer; + String placeholderPlayer = null; + + if (args.size() == 2 && args.get(1).startsWith("-p:")) { + if (!sender.hasPermission("deluxemenus.placeholdersfor")) { + plugin.sms(sender, Messages.NO_PERMISSION_PLAYER_ARGUMENT); + return; + } + + placeholderPlayer = args.get(1).replace("-p:", ""); + + } else if (args.size() >= 3 && args.get(2).startsWith("-p:")) { + if (!sender.hasPermission("deluxemenus.placeholdersfor")) { + plugin.sms(sender, Messages.NO_PERMISSION_PLAYER_ARGUMENT); + return; + } + + placeholderPlayer = args.get(2).replace("-p:", ""); + } + + if (args.size() >= 2) { + if (placeholderPlayer == null) { + if (player && !sender.hasPermission("deluxemenus.open.others")) { + plugin.sms(sender, Messages.NO_PERMISSION); + return; + } + + viewer = Bukkit.getPlayerExact(args.get(1)); + + } else { + if (args.size() >= 3) { + if (!sender.hasPermission("deluxemenus.open.others")) { + plugin.sms(sender, Messages.NO_PERMISSION); + return; + } + + viewer = Bukkit.getPlayerExact(args.get(1)); + + } else { + if (!player) { + plugin.sms(sender, Messages.MUST_SPECIFY_PLAYER); + return; + } + + viewer = (Player) sender; + } + } + + } else { + if (!player) { + plugin.sms(sender, Messages.MUST_SPECIFY_PLAYER); + return; + } + + viewer = (Player) sender; + } + + if (viewer == null) { + plugin.sms(sender, Messages.PLAYER_IS_NOT_ONLINE.message().replaceText(PLAYER_REPLACER_BUILDER.replacement(args.get(2)).build())); + return; + } + + Player placeholder = null; + + if (placeholderPlayer != null) { + placeholder = Bukkit.getPlayerExact(placeholderPlayer); + + if (placeholder == null) { + plugin.sms(sender, Messages.PLAYER_IS_NOT_ONLINE.message().replaceText(PLAYER_REPLACER_BUILDER.replacement(placeholderPlayer).build())); + return; + + } else { + if (placeholder.hasPermission("deluxemenus.placeholdersfor.exempt")) { + plugin.sms(sender, Messages.PLAYER_IS_EXEMPT.message().replaceText(PLAYER_REPLACER_BUILDER.replacement(placeholderPlayer).build())); + + return; + } + } + } + + Optional menu = Menu.getMenuByName(args.get(0)); + + if (menu.isEmpty()) { + plugin.sms(sender, Messages.INVALID_MENU.message().replaceText(MENU_REPLACER_BUILDER.replacement(args.get(0)).build())); + return; + } + + menu.get().openMenu(viewer, null, placeholder); + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ReloadCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ReloadCommand.java new file mode 100644 index 00000000..8a664926 --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ReloadCommand.java @@ -0,0 +1,64 @@ +package com.extendedclip.deluxemenus.command.subcommand; + +import com.extendedclip.deluxemenus.DeluxeMenus; +import com.extendedclip.deluxemenus.menu.Menu; +import com.extendedclip.deluxemenus.utils.Messages; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class ReloadCommand extends SubCommand { + + public ReloadCommand(final @NotNull DeluxeMenus plugin) { + super(plugin); + } + + @Override + public void execute(final @NotNull CommandSender sender, final @NotNull List args) { + if (!sender.hasPermission("deluxemenus.reload")) { + plugin.sms(sender, Messages.NO_PERMISSION); + return; + } + + if (plugin.getConfiguration().checkConfig(null, "config.yml", false) == null) { + plugin.sms(sender, Messages.RELOAD_FAIL); + return; + } + + if (!args.isEmpty()) { + if (Menu.getMenuByName(args.get(0)).isEmpty()) { + plugin.sms(sender, Messages.INVALID_MENU.message().replaceText(MENU_REPLACER_BUILDER.replacement(args.get(0)).build())); + return; + } + + Menu.unload(plugin, args.get(0)); + + if (plugin.getConfiguration().loadGUIMenu(args.get(0))) { + plugin.sms(sender, Messages.MENU_RELOADED.message().replaceText(MENU_REPLACER_BUILDER.replacement(args.get(0)).build())); + return; + } + + plugin.sms(sender, Messages.MENU_NOT_RELOADED.message().replaceText(MENU_REPLACER_BUILDER.replacement(args.get(0)).build())); + return; + + } + + plugin.clearCaches(); + plugin.reloadConfig(); + plugin.saveConfig(); + plugin.reload(); + Menu.unload(plugin); + plugin.getConfiguration().loadGUIMenus(); + plugin.sms(sender, Messages.RELOAD_SUCCESS); + + int gLoaded = Menu.getLoadedMenuSize(); + + if (gLoaded == 1) { + plugin.sms(sender, Messages.MENU_LOADED.message().replaceText(AMOUNT_REPLACER_BUILDER.replacement(String.valueOf(gLoaded)).build())); + return; + } + + plugin.sms(sender, Messages.MENUS_LOADED.message().replaceText(AMOUNT_REPLACER_BUILDER.replacement(String.valueOf(gLoaded)).build())); + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/SubCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/SubCommand.java new file mode 100644 index 00000000..90bf406a --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/SubCommand.java @@ -0,0 +1,23 @@ +package com.extendedclip.deluxemenus.command.subcommand; + +import com.extendedclip.deluxemenus.DeluxeMenus; +import net.kyori.adventure.text.TextReplacementConfig; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public abstract class SubCommand { + + protected static final TextReplacementConfig.Builder PLAYER_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); + protected static final TextReplacementConfig.Builder AMOUNT_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); + protected static final TextReplacementConfig.Builder MENU_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); + + protected final DeluxeMenus plugin; + + public SubCommand(final @NotNull DeluxeMenus plugin) { + this.plugin = plugin; + } + + public abstract void execute(final @NotNull CommandSender sender, final @NotNull List args); +} diff --git a/src/main/java/com/extendedclip/deluxemenus/commands/DeluxeMenusCommands.java b/src/main/java/com/extendedclip/deluxemenus/commands/DeluxeMenusCommands.java deleted file mode 100644 index e7d1b808..00000000 --- a/src/main/java/com/extendedclip/deluxemenus/commands/DeluxeMenusCommands.java +++ /dev/null @@ -1,472 +0,0 @@ -package com.extendedclip.deluxemenus.commands; - -import com.extendedclip.deluxemenus.DeluxeMenus; -import com.extendedclip.deluxemenus.action.ActionType; -import com.extendedclip.deluxemenus.action.ClickAction; -import com.extendedclip.deluxemenus.action.ClickActionTask; -import com.extendedclip.deluxemenus.config.DeluxeMenusConfig; -import com.extendedclip.deluxemenus.menu.Menu; -import com.extendedclip.deluxemenus.menu.MenuHolder; -import com.extendedclip.deluxemenus.utils.DumpUtils; -import com.extendedclip.deluxemenus.utils.Messages; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.TextComponent; -import net.kyori.adventure.text.TextReplacementConfig; -import net.kyori.adventure.text.event.ClickEvent; -import net.kyori.adventure.text.event.HoverEvent; -import net.kyori.adventure.text.format.NamedTextColor; -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.ConsoleCommandSender; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -import static net.kyori.adventure.text.Component.newline; -import static net.kyori.adventure.text.Component.text; - -public class DeluxeMenusCommands implements CommandExecutor { - - private static final TextReplacementConfig.Builder PLAYER_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); - private static final TextReplacementConfig.Builder VERSION_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); - private static final TextReplacementConfig.Builder AUTHORS_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); - private static final TextReplacementConfig.Builder AMOUNT_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); - private static final TextReplacementConfig.Builder MENU_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); - - private final DeluxeMenus plugin; - - public DeluxeMenusCommands(final @NotNull DeluxeMenus plugin) { - this.plugin = plugin; - this.plugin.getCommand("deluxemenus").setExecutor(this); - } - - @Override - public boolean onCommand( - final @NotNull CommandSender sender, - final @NotNull Command command, - final @NotNull String label, - final @NotNull String[] args - ) { - - if (args.length == 0) { - plugin.sms( - sender, - Messages.PLUGIN_VERSION.message() - .replaceText(VERSION_REPLACER_BUILDER.replacement(plugin.getDescription().getVersion()).build()) - .replaceText(AUTHORS_REPLACER_BUILDER.replacement( - plugin.getDescription() - .getAuthors() - .stream() - .map(author -> text(author, NamedTextColor.WHITE)) - .collect(Component.toComponent(text(", ", NamedTextColor.GRAY)))).build() - ) - ); - return true; - } - - if (args[0].equalsIgnoreCase("help")) { - if (sender.hasPermission("deluxemenus.admin")) { - plugin.sms(sender, Messages.HELP_ADMIN); - return true; - } - - plugin.sms(sender, Messages.HELP); - return true; - - } else if (args[0].equalsIgnoreCase("dump")) { - if (!sender.hasPermission("deluxemenus.admin")) { - plugin.sms(sender, Messages.NO_PERMISSION); - return true; - } - - if (args.length != 2) { - plugin.sms(sender, Messages.WRONG_USAGE_DUMP_COMMAND); - return true; - } - - String dump = ""; - try { - dump = DumpUtils.createDump(plugin, args[1]); - } catch (final RuntimeException ignored) { - } - - if (dump.isBlank()) { - plugin.sms(sender, Messages.DUMP_FAILED); - return true; - } - - DumpUtils.postDump(dump).whenComplete((result, error) -> { - if (error != null) { - DeluxeMenus.printStacktrace( - "Something went wrong while trying to create and post a dump!", - error - ); - plugin.sms(sender, Messages.DUMP_FAILED); - return; - } - - final var link = text(DumpUtils.URL + result) - .clickEvent(ClickEvent.openUrl(DumpUtils.URL + result)); - - plugin.sms(sender, Messages.DUMP_SUCCESS.message().append(link)); - }); - - return true; - } else if (args[0].equalsIgnoreCase("execute")) { - if (!sender.isOp()) { - plugin.sms(sender, Messages.NO_PERMISSION); - return true; - } - - if (args.length < 3) { - plugin.sms(sender, Messages.WRONG_USAGE_EXECUTE_COMMAND); - return true; - } - - Player target = Bukkit.getPlayer(args[1]); - if (target == null) { - plugin.sms( - sender, - Messages.PLAYER_IS_NOT_ONLINE.message().replaceText(PLAYER_REPLACER_BUILDER.replacement(args[1]).build()) - ); - return true; - } - - String executable = String.join(" ", Arrays.asList(args).subList(2, args.length)); - - ActionType type = ActionType.getByStart(executable); - - if (type == null) { - plugin.sms(sender, Messages.WRONG_ACTION_TYPE); - return true; - } - - executable = executable.replaceFirst(Pattern.quote(type.getIdentifier()), "").trim(); - - ClickAction action = new ClickAction(type, executable); - - Matcher d = DeluxeMenusConfig.DELAY_MATCHER.matcher(executable); - - if (d.find()) { - action.setDelay(d.group(1)); - executable = executable.replaceFirst(Pattern.quote(d.group()), ""); - } - - Matcher ch = DeluxeMenusConfig.CHANCE_MATCHER.matcher(executable); - - if (ch.find()) { - action.setChance(ch.group(1)); - executable = executable.replaceFirst(Pattern.quote(ch.group()), ""); - } - - action.setExecutable(executable); - - MenuHolder holder = Menu.getMenuHolder(target).orElse(new MenuHolder(target)); - - if (!action.checkChance(holder)) { - plugin.sms(sender, Messages.CHANCE_FAIL); - return true; - } - - final ClickActionTask actionTask = new ClickActionTask( - plugin, - target.getUniqueId(), - action.getType(), - action.getExecutable(), - holder.getTypedArgs(), - true, - true - ); - - if (action.hasDelay()) { - actionTask.runTaskLater(plugin, action.getDelay(holder)); - - plugin.sms( - sender, - Messages.ACTION_TO_BE_EXECUTED.message().replaceText( - AMOUNT_REPLACER_BUILDER.replacement(String.valueOf(action.getDelay(holder))).build()) - ); - return true; - } - - actionTask.runTask(plugin); - - plugin.sms( - sender, - Messages.ACTION_EXECUTED_FOR.message().replaceText( - PLAYER_REPLACER_BUILDER.replacement(target.getName()).build()) - ); - return true; - - } else if (args[0].equalsIgnoreCase("reload")) { - if (!sender.hasPermission("deluxemenus.reload")) { - plugin.sms(sender, Messages.NO_PERMISSION); - return true; - } - - if (plugin.getConfiguration().checkConfig(null, "config.yml", false) == null) { - plugin.sms(sender, Messages.RELOAD_FAIL); - return true; - } - - if (args.length > 1) { - if (Menu.getMenuByName(args[1]).isEmpty()) { - plugin.sms( - sender, - Messages.INVALID_MENU.message().replaceText( - MENU_REPLACER_BUILDER.replacement(args[1]).build()) - ); - return true; - } - - Menu.unload(args[1]); - - if (plugin.getConfiguration().loadGUIMenu(args[1])) { - plugin.sms( - sender, - Messages.MENU_RELOADED.message().replaceText( - MENU_REPLACER_BUILDER.replacement(args[1]).build()) - ); - return true; - } - - - plugin.sms( - sender, - Messages.MENU_NOT_RELOADED.message().replaceText( - MENU_REPLACER_BUILDER.replacement(args[1]).build()) - ); - return true; - - } - - plugin.clearCaches(); - plugin.reloadConfig(); - plugin.saveConfig(); - DeluxeMenus.debugLevel(plugin.getConfiguration().debugLevel()); - Menu.unload(); - plugin.getConfiguration().loadGUIMenus(); - plugin.sms(sender, Messages.RELOAD_SUCCESS); - - int gLoaded = Menu.getLoadedMenuSize(); - - if (gLoaded == 1) { - plugin.sms( - sender, - Messages.MENU_LOADED.message().replaceText( - AMOUNT_REPLACER_BUILDER.replacement(String.valueOf(gLoaded)).build()) - ); - } else { - plugin.sms( - sender, - Messages.MENUS_LOADED.message().replaceText( - AMOUNT_REPLACER_BUILDER.replacement(String.valueOf(gLoaded)).build()) - ); - } - return true; - - } else if (args[0].equalsIgnoreCase("list")) { - - if (!sender.hasPermission("deluxemenus.list")) { - plugin.sms(sender, Messages.NO_PERMISSION); - return true; - } - - Collection menus = Menu.getAllMenus(); - - if (menus.isEmpty()) { - plugin.sms( - sender, - Messages.MENUS_LOADED.message().replaceText( - AMOUNT_REPLACER_BUILDER.replacement("There are no").build()) - ); - return true; - } - - // Component builder start - final TextComponent.Builder list = text(); - - // Header - list.append(text("The following " + menus.size() + " menus are loaded on the server:", NamedTextColor.GOLD) - .append(newline())); - - if (sender instanceof ConsoleCommandSender) { - list.append(newline()); - - // Add all the menus using streams - final var menusList = menus.stream().map(menu -> menu.options().commands().isEmpty() - ? text(menu.options().name(), NamedTextColor.DARK_AQUA) - .append(text(" - ", NamedTextColor.GRAY)) - .append(text("No menu command", NamedTextColor.RED)) - - : text(menu.options().name(), NamedTextColor.DARK_AQUA) - .append(text(" - ", NamedTextColor.GRAY)) - .append(text("/" + menu.options().commands().get(0), NamedTextColor.GREEN)) - ).collect(Component.toComponent(text(" | ", NamedTextColor.WHITE))); - - plugin.sms(sender, list.append(menusList).build()); - return true; - } - - // Inform they can hover - list.append(text("**Hover menu name for more info**", NamedTextColor.GRAY)); - - // Extra space below - list.append(newline()).append(newline()); - - // Add all the menus using streams - final var menusList = menus.stream().map(menu -> menu.options().commands().isEmpty() - ? text(menu.options().name(), NamedTextColor.DARK_AQUA).hoverEvent( - HoverEvent.showText(text("No open command", NamedTextColor.GOLD))) - : text(menu.options().name(), NamedTextColor.DARK_AQUA).hoverEvent( - HoverEvent.showText(text("Open Command: ", NamedTextColor.GOLD) - .append(text("/" + menu.options().commands().get(0), NamedTextColor.YELLOW)))) - .clickEvent( - ClickEvent.suggestCommand("/" + menu.options().commands().get(0))) - ).collect(Component.toComponent(text(", ", NamedTextColor.WHITE))); - - list.append(menusList); - - plugin.sms(sender, list.build()); - return true; - - } else if (args[0].equalsIgnoreCase("open")) { - - if (!sender.hasPermission("deluxemenus.open")) { - plugin.sms(sender, Messages.NO_PERMISSION); - return true; - } - - boolean player = (sender instanceof Player); - - if (args.length < 2) { - plugin.sms(sender, Messages.WRONG_USAGE_OPEN_COMMAND); - return true; - } - - if (Menu.getAllMenus().isEmpty()) { - plugin.sms( - sender, - Messages.MENUS_LOADED.message().replaceText( - AMOUNT_REPLACER_BUILDER.replacement("There are no").build()) - ); - return true; - } - - Player viewer; - String placeholderPlayer = null; - - if (args.length == 3 && args[2].startsWith("-p:")) { - if (!sender.hasPermission("deluxemenus.placeholdersfor")) { - plugin.sms(sender, Messages.NO_PERMISSION_PLAYER_ARGUMENT); - return true; - } - - placeholderPlayer = args[2].replace("-p:", ""); - - } else if (args.length >= 4 && args[3].startsWith("-p:")) { - if (!sender.hasPermission("deluxemenus.placeholdersfor")) { - plugin.sms(sender, Messages.NO_PERMISSION_PLAYER_ARGUMENT); - return true; - } - - placeholderPlayer = args[3].replace("-p:", ""); - } - - if (args.length >= 3) { - if (placeholderPlayer == null) { - if (player && !sender.hasPermission("deluxemenus.open.others")) { - plugin.sms(sender, Messages.NO_PERMISSION); - return true; - } - - viewer = Bukkit.getPlayer(args[2]); - - } else { - if (args.length >= 4) { - if (!sender.hasPermission("deluxemenus.open.others")) { - plugin.sms(sender, Messages.NO_PERMISSION); - return true; - } - - viewer = Bukkit.getPlayer(args[2]); - - } else { - if (!player) { - plugin.sms(sender, Messages.MUST_SPECIFY_PLAYER); - return true; - } - - viewer = (Player) sender; - } - } - - } else { - if (!player) { - plugin.sms(sender, Messages.MUST_SPECIFY_PLAYER); - return true; - } - - viewer = (Player) sender; - } - - if (viewer == null) { - plugin.sms( - sender, - Messages.PLAYER_IS_NOT_ONLINE.message().replaceText(PLAYER_REPLACER_BUILDER.replacement(args[2]).build()) - ); - return true; - } - - Player placeholder = null; - - if (placeholderPlayer != null) { - placeholder = Bukkit.getPlayer(placeholderPlayer); - - if (placeholder == null) { - plugin.sms( - sender, - Messages.PLAYER_IS_NOT_ONLINE.message().replaceText(PLAYER_REPLACER_BUILDER.replacement(placeholderPlayer).build()) - ); - return true; - - } else { - if (placeholder.hasPermission("deluxemenus.placeholdersfor.exempt")) { - plugin.sms( - sender, - Messages.PLAYER_IS_EXEMPT.message().replaceText(PLAYER_REPLACER_BUILDER.replacement(placeholderPlayer).build()) - ); - - return true; - } - } - } - - Optional menu = Menu.getMenuByName(args[1]); - - if (menu.isEmpty()) { - plugin.sms( - sender, - Messages.INVALID_MENU.message().replaceText( - MENU_REPLACER_BUILDER.replacement(args[1]).build()) - ); - return true; - } - - menu.get().openMenu(viewer, null, placeholder); - return true; - - } else { - plugin.sms(sender, Messages.WRONG_USAGE); - } - return true; - } -} diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index f2873933..9e0f2763 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -12,24 +12,26 @@ import com.extendedclip.deluxemenus.menu.MenuItem; import com.extendedclip.deluxemenus.menu.options.MenuItemOptions; import com.extendedclip.deluxemenus.menu.options.MenuOptions; -import com.extendedclip.deluxemenus.requirement.*; +import com.extendedclip.deluxemenus.requirement.HasExpRequirement; +import com.extendedclip.deluxemenus.requirement.HasItemRequirement; +import com.extendedclip.deluxemenus.requirement.HasMetaRequirement; +import com.extendedclip.deluxemenus.requirement.HasMoneyRequirement; +import com.extendedclip.deluxemenus.requirement.HasPermissionRequirement; +import com.extendedclip.deluxemenus.requirement.HasPermissionsRequirement; +import com.extendedclip.deluxemenus.requirement.InputResultRequirement; +import com.extendedclip.deluxemenus.requirement.IsNearRequirement; +import com.extendedclip.deluxemenus.requirement.IsObjectRequirement; +import com.extendedclip.deluxemenus.requirement.JavascriptRequirement; +import com.extendedclip.deluxemenus.requirement.RegexMatchesRequirement; +import com.extendedclip.deluxemenus.requirement.Requirement; +import com.extendedclip.deluxemenus.requirement.RequirementList; +import com.extendedclip.deluxemenus.requirement.RequirementType; +import com.extendedclip.deluxemenus.requirement.StringLengthRequirement; import com.extendedclip.deluxemenus.requirement.wrappers.ItemWrapper; import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.ItemUtils; import com.extendedclip.deluxemenus.utils.LocationUtils; import com.extendedclip.deluxemenus.utils.VersionHelper; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.function.BiConsumer; -import java.util.logging.Level; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - import com.google.common.base.Enums; import com.google.common.primitives.Ints; import org.bukkit.DyeColor; @@ -47,6 +49,12 @@ import org.bukkit.potion.PotionEffectType; import org.jetbrains.annotations.NotNull; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -57,6 +65,11 @@ import java.util.Optional; import java.util.Set; import java.util.TreeMap; +import java.util.function.BiConsumer; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import static com.extendedclip.deluxemenus.utils.Constants.PLACEHOLDER_PREFIX; import static com.extendedclip.deluxemenus.utils.Constants.PLAYER_ITEMS; @@ -66,24 +79,38 @@ public class DeluxeMenusConfig { public static final List VALID_MATERIALS = new ArrayList<>(); public static final List VALID_MATERIAL_PREFIXES = new ArrayList<>(); + public static final Pattern DELAY_MATCHER = Pattern.compile("]+)>", Pattern.CASE_INSENSITIVE); + public static final Pattern CHANCE_MATCHER = Pattern.compile("]+)>", Pattern.CASE_INSENSITIVE); + public static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("%((?[a-zA-Z0-9]+)_)(?[^%]+)%"); + private static final List VALID_INVENTORY_TYPES = VersionHelper.getValidInventoryTypes(); static { VALID_MATERIALS.addAll(PLAYER_ITEMS); VALID_MATERIALS.add(WATER_BOTTLE); VALID_MATERIAL_PREFIXES.add(PLACEHOLDER_PREFIX); - VALID_MATERIAL_PREFIXES.addAll( - DeluxeMenus.getInstance().getItemHooks().values() - .stream() - .map(ItemHook::getPrefix) - .collect(Collectors.toList()) - ); } - public static final Pattern DELAY_MATCHER = Pattern.compile("]+)>", Pattern.CASE_INSENSITIVE); - public static final Pattern CHANCE_MATCHER = Pattern.compile("]+)>", Pattern.CASE_INSENSITIVE); - public static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("%((?[a-zA-Z0-9]+)_)(?[^%]+)%"); - private static final List VALID_INVENTORY_TYPES = VersionHelper.getValidInventoryTypes(); + private final String separator = File.separator; + private final File menuDirectory; + private final DeluxeMenus plugin; + private final List exampleMenus = Arrays.asList("basics_menu", "advanced_menu", "requirements_menu" + // more example menus here + ); + + public DeluxeMenusConfig(@NotNull final DeluxeMenus plugin) { + VALID_MATERIAL_PREFIXES.addAll(plugin.getItemHooks().values().stream().map(ItemHook::getPrefix).collect(Collectors.toList())); + + this.plugin = plugin; + menuDirectory = new File(this.plugin.getDataFolder() + separator + "gui_menus"); + try { + if (menuDirectory.mkdirs()) { + plugin.debug(DebugLevel.HIGH, Level.INFO, "Individual menus directory did not exist.", "Created directory: plugins" + separator + "DeluxeMenus" + separator + "gui_menus"); + } + } catch (SecurityException e) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Something went wrong while creating directory: plugins" + separator + "DeluxeMenus" + separator + "gui_menus"); + } + } private static boolean isValidMaterial(final @NotNull String material) { final String lowercaseMaterial = material.toLowerCase(Locale.ROOT); @@ -105,37 +132,8 @@ private static boolean isValidMaterial(final @NotNull String material) { return false; } - private final String separator = File.separator; - private final File menuDirectory; - private final DeluxeMenus plugin; - - private final List exampleMenus = Arrays.asList( - "basics_menu", - "advanced_menu", - "requirements_menu" - // more example menus here - ); - - public DeluxeMenusConfig(DeluxeMenus plugin) { - this.plugin = plugin; - menuDirectory = new File(this.plugin.getDataFolder() + separator + "gui_menus"); - try { - if (menuDirectory.mkdirs()) { - DeluxeMenus.debug( - DebugLevel.HIGH, - Level.INFO, - "Individual menus directory did not exist.", - "Created directory: plugins" + separator + "DeluxeMenus" + separator + "gui_menus" - ); - } - } catch (SecurityException e) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Something went wrong while creating directory: plugins" + separator + "DeluxeMenus" + separator - + "gui_menus" - ); - } + public static boolean containsPlaceholders(String text) { + return PLACEHOLDER_PATTERN.matcher(text).find(); } private List getStringListFromConfig(FileConfiguration config, String path) { @@ -150,10 +148,6 @@ private List getStringListFromConfig(FileConfiguration config, String pa } } - public static boolean containsPlaceholders(String text) { - return PLACEHOLDER_PATTERN.matcher(text).find(); - } - public boolean loadDefConfig() { if (checkConfig(null, "config.yml", true) == null) { return false; @@ -161,16 +155,17 @@ public boolean loadDefConfig() { FileConfiguration c = plugin.getConfig(); - c.options().header("DeluxeMenus " + plugin.getDescription().getVersion() - + " main configuration file" - + "\n" - + "\nA full wiki on how to use this plugin can be found at:" - + "\nhttps://wiki.helpch.at/clips-plugins/deluxemenus" - + "\n" - + c.options().header( + "DeluxeMenus " + plugin.getDescription().getVersion() + " main configuration file" + + "\n" + + "\nA full wiki on how to use this plugin can be found at:" + + "\nhttps://wiki.helpch.at/helpchat-plugins/deluxemenus" + + "\n" ); - c.addDefault("debug", "HIGHEST"); + c.addDefault("debug", "LOW"); c.addDefault("check_updates", true); + c.addDefault("use_admin_commands_in_menus_list", false); + c.addDefault("menus_list_page_size", 10); c.options().copyDefaults(true); if (!c.contains("gui_menus")) { @@ -189,10 +184,7 @@ private void createMenuExamples(FileConfiguration c) { try { menuFile.createNewFile(); } catch (IOException e) { - DeluxeMenus.printStacktrace( - "Failed to create example menus!", - e - ); + plugin.printStacktrace("Failed to create example menus!", e); continue; } saveResourceToFile(name + ".yml", menuFile); @@ -211,15 +203,8 @@ private boolean saveResourceToFile(String resource, File file) { os.write(buffer); return true; } catch (NullPointerException | IOException ex) { - DeluxeMenus.printStacktrace( - "Failed to update file: " + resource, - ex - ); - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.SEVERE, - "Failed to save default settings for:" + file.getName() + " from resource:" + resource - ); + plugin.printStacktrace("Failed to update file: " + resource, ex); + plugin.debug(DebugLevel.HIGHEST, Level.SEVERE, "Failed to save default settings for:" + file.getName() + " from resource:" + resource); } return false; } @@ -246,10 +231,7 @@ public FileConfiguration checkConfig(String folder, String fileName, boolean cre try { configFile.createNewFile(); } catch (IOException e) { - DeluxeMenus.printStacktrace( - "Failed to create file: " + fileName, - e - ); + plugin.printStacktrace("Failed to create file: " + fileName, e); return null; } } @@ -268,28 +250,14 @@ private FileConfiguration checkConfig(File f) { config.load(f); return config; } catch (IOException e) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.SEVERE, - "Could not read file: " + f.getName() - ); - - DeluxeMenus.printStacktrace( - "Could not read file: " + f.getName(), - e - ); + plugin.debug(DebugLevel.HIGHEST, Level.SEVERE, "Could not read file: " + f.getName()); + + plugin.printStacktrace("Could not read file: " + f.getName(), e); return null; } catch (InvalidConfigurationException e) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.SEVERE, - "Detected invalid configuration in file: " + f.getName() - ); - - DeluxeMenus.printStacktrace( - "Detected invalid configuration in file: " + f.getName(), - e - ); + plugin.debug(DebugLevel.HIGHEST, Level.SEVERE, "Detected invalid configuration in file: " + f.getName()); + + plugin.printStacktrace("Detected invalid configuration in file: " + f.getName(), e); return null; } } @@ -322,7 +290,7 @@ public boolean loadGUIMenu(String menu) { if (c.contains("gui_menus." + menu + ".file")) { loadMenuFromFile(menu); } else { - loadMenu(c, menu, true); + loadMenu(c, menu, true, "config"); } return true; @@ -357,7 +325,7 @@ public int loadGUIMenus() { loadMenuFromFile(key); } else { - loadMenu(c, key, true); + loadMenu(c, key, true, "config"); } } return Menu.getLoadedMenuSize(); @@ -368,24 +336,14 @@ public boolean loadMenuFromFile(String menuName) { String fileName = plugin.getConfig().getString("gui_menus." + menuName + ".file"); if (!fileName.endsWith(".yml")) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.SEVERE, - "Filename specified for menu: " + menuName + " is not a .yml file!", - "Make sure that the file name to load this menu from is specified as a .yml file!", - "Skipping loading of menu: " + menuName - ); + plugin.debug(DebugLevel.HIGHEST, Level.SEVERE, "Filename specified for menu: " + menuName + " is not a .yml file!", "Make sure that the file name to load this menu from is specified as a .yml file!", "Skipping loading of menu: " + menuName); return false; } File f = new File(menuDirectory.getPath(), fileName); if (!f.exists()) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.INFO, - f.getName() + " does not exist!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.INFO, f.getName() + " does not exist!"); try { File folder = f.getParentFile(); @@ -393,26 +351,12 @@ public boolean loadMenuFromFile(String menuName) { f.createNewFile(); if (!saveResourceToFile("default_menu.yml", f)) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Failed to create a default menu file for menu: " + menuName, - "Skipping loading menu: " + menuName - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Failed to create a default menu file for menu: " + menuName, "Skipping loading menu: " + menuName); return false; } - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.INFO, - f.getName() + " created! Add your menu options to this file and use /dm reload to load it!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.INFO, f.getName() + " created! Add your menu options to this file and use /dm reload to load it!"); } catch (IOException e) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.SEVERE, - "Could not create menu file: plugins" + separator + "DeluxeMenus" + separator + "gui_menus" - + separator + fileName - ); + plugin.debug(DebugLevel.HIGHEST, Level.SEVERE, "Could not create menu file: plugins" + separator + "DeluxeMenus" + separator + "gui_menus" + separator + fileName); return false; } } @@ -420,39 +364,27 @@ public boolean loadMenuFromFile(String menuName) { FileConfiguration cfg = checkConfig(f); if (cfg == null) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Menu: " + menuName + " in file: " + fileName + " not loaded." - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Menu: " + menuName + " in file: " + fileName + " not loaded."); return false; } if (cfg.getKeys(false).isEmpty()) { - DeluxeMenus.debug( - DebugLevel.HIGH, - Level.INFO, - "Menu config: " + f.getName() + " is empty! Creating default config example..." - ); + plugin.debug(DebugLevel.HIGH, Level.INFO, "Menu config: " + f.getName() + " is empty! Creating default config example..."); saveResourceToFile("default_menu.yml", f); return false; } - loadMenu(cfg, menuName, false); + final Path guiMenusPath = menuDirectory.toPath(); + final Path menuPath = f.toPath(); + final Path relativePath = guiMenusPath.relativize(menuPath); + + loadMenu(cfg, menuName, false, relativePath.toString()); return Menu.getMenuByName(menuName).isPresent(); } - public void loadMenu(FileConfiguration c, String key, boolean mainConfig) { - + public void loadMenu(FileConfiguration c, String key, boolean mainConfig, final @NotNull String path) { if (mainConfig) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Menu: " + key + " does not have a file specified in config.yml! Creating menus in the " + - "config.yml file is deprecated and will be removed in a future version! Please migrate your " + - "menus to individual files in the gui_menus directory! For more information see: " + - "https://wiki.helpch.at/clips-plugins/deluxemenus/external-menus" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Menu: " + key + " does not have a file specified in config.yml! Creating menus in the " + "config.yml file is deprecated and will be removed in a future version! Please migrate your " + "menus to individual files in the gui_menus directory! For more information see: " + "https://wiki.helpch.at/clips-plugins/deluxemenus/external-menus"); } String pre = "gui_menus." + key + "."; @@ -462,12 +394,7 @@ public void loadMenu(FileConfiguration c, String key, boolean mainConfig) { } if (!c.contains(pre + "menu_title")) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.SEVERE, - "Menu title for menu: " + key + " is not present!", - "Skipping menu: " + key - ); + plugin.debug(DebugLevel.HIGHEST, Level.SEVERE, "Menu title for menu: " + key + " is not present!", "Skipping menu: " + key); return; } @@ -480,12 +407,7 @@ public void loadMenu(FileConfiguration c, String key, boolean mainConfig) { } if (title == null || title.isEmpty()) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.SEVERE, - "Menu title for menu: " + key + " is invalid!", - "Skipping menu: " + key - ); + plugin.debug(DebugLevel.HIGHEST, Level.SEVERE, "Menu title for menu: " + key + " is invalid!", "Skipping menu: " + key); return; } @@ -498,13 +420,7 @@ public void loadMenu(FileConfiguration c, String key, boolean mainConfig) { final InventoryType inventoryType = InventoryType.valueOf(c.getString(pre + "inventory_type").toUpperCase()); type = !VALID_INVENTORY_TYPES.contains(inventoryType) ? InventoryType.CHEST : inventoryType; } catch (Exception ex) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Inventory type for menu: " + key + " is invalid!", - "Valid Inventory types: " + Arrays.toString(VALID_INVENTORY_TYPES.toArray()), - "Defaulting to CHEST inventory type." - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Inventory type for menu: " + key + " is invalid!", "Valid Inventory types: " + Arrays.toString(VALID_INVENTORY_TYPES.toArray()), "Defaulting to CHEST inventory type."); } } @@ -518,22 +434,12 @@ public void loadMenu(FileConfiguration c, String key, boolean mainConfig) { String cmd = c.getString(pre + "open_command"); if (cmd == null) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.SEVERE, - "open_command specified for menu: " + key + " is null!", - "Skipping menu: " + key - ); + plugin.debug(DebugLevel.HIGHEST, Level.SEVERE, "open_command specified for menu: " + key + " is null!", "Skipping menu: " + key); return; } if (Menu.isMenuCommand(cmd)) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.SEVERE, - "open_command specified for menu: " + key + " already exists for another menu!", - "Skipping menu: " + key - ); + plugin.debug(DebugLevel.HIGHEST, Level.SEVERE, "open_command specified for menu: " + key + " already exists for another menu!", "Skipping menu: " + key); return; } @@ -545,12 +451,7 @@ public void loadMenu(FileConfiguration c, String key, boolean mainConfig) { for (String cmd : cmds) { if (Menu.isMenuCommand(cmd)) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "command: " + cmd + " specified for menu: " + key + " already exists for another menu!", - "Skipping command: " + cmd + " in menu: " + key - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "command: " + cmd + " specified for menu: " + key + " already exists for another menu!", "Skipping command: " + cmd + " in menu: " + key); } else { openCommands.add(cmd.toLowerCase()); } @@ -597,58 +498,32 @@ public void loadMenu(FileConfiguration c, String key, boolean mainConfig) { int size = 54; if (type == InventoryType.CHEST) { if (!c.contains(pre + "size")) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.INFO, - "Menu size for menu: " + key + " is not present!", - "Using default size of 54" - ); + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Menu size for menu: " + key + " is not present!", "Using default size of 54"); } else { size = c.getInt(pre + "size"); - if ((size + 1) % 9 == 0) - size++; + if ((size + 1) % 9 == 0) size++; - if ((size - 1) % 9 == 0) - size--; + if ((size - 1) % 9 == 0) size--; if (size < 9) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.INFO, - "Menu size for menu: " + key + " is lower than 9", - "Defaulting to 9." - ); + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Menu size for menu: " + key + " is lower than 9", "Defaulting to 9."); size = 9; } if (size > 54) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Menu size for menu: " + key + " is higher than 54", - "Defaulting to 54." - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Menu size for menu: " + key + " is higher than 54", "Defaulting to 54."); size = 54; } if (size % 9 != 0) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Menu size for menu: " + key + " is not a multiple of 9", - "Defaulting to 54." - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Menu size for menu: " + key + " is not a multiple of 9", "Defaulting to 54."); size = 54; } } } else { size = type.getDefaultSize(); - DeluxeMenus.debug( - DebugLevel.LOWEST, - Level.INFO, - "TYPE IS: " + type + ". Setting size to:" + type.getDefaultSize() - ); + plugin.debug(DebugLevel.LOWEST, Level.INFO, "TYPE IS: " + type + ". Setting size to:" + type.getDefaultSize()); } builder.size(size); @@ -674,12 +549,7 @@ public void loadMenu(FileConfiguration c, String key, boolean mainConfig) { Map> items = loadMenuItems(c, key, mainConfig); if (items == null || items.isEmpty()) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.SEVERE, - "Failed to load menu items for menu: " + key, - "Skipping menu: " + key - ); + plugin.debug(DebugLevel.HIGHEST, Level.SEVERE, "Failed to load menu items for menu: " + key, "Skipping menu: " + key); return; } @@ -687,11 +557,10 @@ public void loadMenu(FileConfiguration c, String key, boolean mainConfig) { builder.parsePlaceholdersAfterArguments(c.getBoolean(pre + "parse_placeholders_after_arguments", false)); // Don't need to register the menu since it's done in the constructor - new Menu(builder.build(), items); + new Menu(plugin, builder.build(), items, path); } - private Map> loadMenuItems(FileConfiguration c, String name, - boolean mainConfig) { + private Map> loadMenuItems(FileConfiguration c, String name, boolean mainConfig) { String itemsPath = "gui_menus." + name + ".items"; if (!mainConfig) { @@ -715,24 +584,14 @@ private Map> loadMenuItems(FileConfiguration String currentPath = itemsPath + "." + key + "."; if (!c.contains(currentPath + "material")) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Material for item: " + key + " in menu: " + name + " is not present!", - "Skipping item: " + key - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Material for item: " + key + " in menu: " + name + " is not present!", "Skipping item: " + key); continue; } final String material = c.getString(currentPath + "material"); final String lowercaseMaterial = material.toLowerCase(Locale.ROOT); if (!isValidMaterial(lowercaseMaterial)) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Material for item: " + key + " in menu: " + name + " is not valid!", - "Skipping item: " + key - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Material for item: " + key + " in menu: " + name + " is not valid!", "Skipping item: " + key); continue; } @@ -780,12 +639,7 @@ private Map> loadMenuItems(FileConfiguration builder.loreAppendMode(LoreAppendMode.valueOf(loreAppendMode)); } catch (IllegalArgumentException | NullPointerException ignored) { builder.loreAppendMode(LoreAppendMode.OVERRIDE); // Defaults to override in case of invalid append mode - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Lore append mode: " + loreAppendMode + " for item: " + key + " in menu: " + name - + " is not a valid lore append mode!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Lore append mode: " + loreAppendMode + " for item: " + key + " in menu: " + name + " is not a valid lore append mode!"); } } @@ -797,12 +651,7 @@ private Map> loadMenuItems(FileConfiguration ItemFlag flag = Enums.getIfPresent(ItemFlag.class, flagAsString.toUpperCase()).orNull(); if (flag == null) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Item flag: " + flagAsString + " for item: " + key + " in menu: " + name - + " is not a valid item flag!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Item flag: " + flagAsString + " for item: " + key + " in menu: " + name + " is not a valid item flag!"); continue; } @@ -825,22 +674,14 @@ private Map> loadMenuItems(FileConfiguration for (String e : c.getStringList(currentPath + "banner_meta")) { if (!e.contains(";")) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Banner Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Banner Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!"); continue; } String[] metaParts = e.split(";", 2); if (metaParts.length != 2) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Banner Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Banner Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!"); continue; } @@ -851,16 +692,9 @@ private Map> loadMenuItems(FileConfiguration color = DyeColor.valueOf(metaParts[0].toUpperCase()); type = PatternType.valueOf(metaParts[1].toUpperCase()); } catch (IllegalArgumentException exception) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Banner Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Banner Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!"); - DeluxeMenus.printStacktrace( - "Banner Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!", - exception - ); + plugin.printStacktrace("Banner Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!", exception); continue; } @@ -878,22 +712,14 @@ private Map> loadMenuItems(FileConfiguration try { if (!e.contains(";")) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Potion Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Potion Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!"); continue; } String[] metaParts = e.split(";", 3); if (metaParts.length != 3) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Potion Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Potion Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!"); continue; } @@ -902,22 +728,14 @@ private Map> loadMenuItems(FileConfiguration int amplifier = Integer.parseInt(metaParts[2]); if (type == null) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Potion Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Potion Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!"); continue; } potionEffects.add(type.createEffect(duration, amplifier)); } catch (Exception ex) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Potion Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Potion Meta for item: " + key + ", meta entry: " + e + " is invalid! Skipping this entry!"); } } if (!potionEffects.isEmpty()) { @@ -934,50 +752,42 @@ private Map> loadMenuItems(FileConfiguration if (c.contains(currentPath + "click_commands")) { builder.clickHandler(getClickHandler(c, currentPath + "click_commands")); if (c.contains(currentPath + "click_requirement")) { - builder.clickRequirements( - this.getRequirements(c, currentPath + "click_requirement")); + builder.clickRequirements(this.getRequirements(c, currentPath + "click_requirement")); } } if (c.contains(currentPath + "left_click_commands")) { builder.leftClickHandler(getClickHandler(c, currentPath + "left_click_commands")); if (c.contains(currentPath + "left_click_requirement")) { - builder.leftClickRequirements( - this.getRequirements(c, currentPath + "left_click_requirement")); + builder.leftClickRequirements(this.getRequirements(c, currentPath + "left_click_requirement")); } } if (c.contains(currentPath + "right_click_commands")) { builder.rightClickHandler(getClickHandler(c, currentPath + "right_click_commands")); if (c.contains(currentPath + "right_click_requirement")) { - builder.rightClickRequirements( - this.getRequirements(c, currentPath + "right_click_requirement")); + builder.rightClickRequirements(this.getRequirements(c, currentPath + "right_click_requirement")); } } if (c.contains(currentPath + "shift_left_click_commands")) { - builder.shiftLeftClickHandler( - getClickHandler(c, currentPath + "shift_left_click_commands")); + builder.shiftLeftClickHandler(getClickHandler(c, currentPath + "shift_left_click_commands")); if (c.contains(currentPath + "shift_left_click_requirement")) { - builder.shiftLeftClickRequirements( - this.getRequirements(c, currentPath + "shift_left_click_requirement")); + builder.shiftLeftClickRequirements(this.getRequirements(c, currentPath + "shift_left_click_requirement")); } } if (c.contains(currentPath + "shift_right_click_commands")) { - builder.shiftRightClickHandler( - getClickHandler(c, currentPath + "shift_right_click_commands")); + builder.shiftRightClickHandler(getClickHandler(c, currentPath + "shift_right_click_commands")); if (c.contains(currentPath + "shift_right_click_requirement")) { - builder.shiftRightClickRequirements( - this.getRequirements(c, currentPath + "shift_right_click_requirement")); + builder.shiftRightClickRequirements(this.getRequirements(c, currentPath + "shift_right_click_requirement")); } } if (c.contains(currentPath + "middle_click_commands")) { builder.middleClickHandler(getClickHandler(c, currentPath + "middle_click_commands")); if (c.contains(currentPath + "middle_click_requirement")) { - builder.middleClickRequirements( - this.getRequirements(c, currentPath + "middle_click_requirement")); + builder.middleClickRequirements(this.getRequirements(c, currentPath + "middle_click_requirement")); } } @@ -999,7 +809,7 @@ private Map> loadMenuItems(FileConfiguration slots.add(c.getInt(currentPath + "slot", 0)); } - final MenuItem menuItem = new MenuItem(builder.build()); + final MenuItem menuItem = new MenuItem(plugin, builder.build()); for (int slot : slots) { TreeMap slotPriorityMap; @@ -1009,10 +819,7 @@ private Map> loadMenuItems(FileConfiguration } else { slotPriorityMap = menuItems.get(slot); } - slotPriorityMap.put( - menuItem.options().priority(), - new MenuItem(menuItem.options().asBuilder().slot(slot).build()) - ); + slotPriorityMap.put(menuItem.options().priority(), new MenuItem(plugin, menuItem.options().asBuilder().slot(slot).build())); } } return menuItems; @@ -1035,11 +842,7 @@ private RequirementList getRequirements(FileConfiguration c, String path) { debug("requirement: " + key + " from requirements list"); String rPath = path + ".requirements." + key; if (!c.contains(rPath + ".type")) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "No type set for requirement: " + key + " for path: " + rPath - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "No type set for requirement: " + key + " for path: " + rPath); continue; } @@ -1047,11 +850,7 @@ private RequirementList getRequirements(FileConfiguration c, String path) { RequirementType type = RequirementType.getType(stringType); if (type == null) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Requirement type '" + stringType + "' at path '" + rPath + "' is not valid!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Requirement type '" + stringType + "' at path '" + rPath + "' is not valid!"); continue; } @@ -1074,20 +873,12 @@ private RequirementList getRequirements(FileConfiguration c, String path) { .orElse(null) == null) Material.valueOf(materialName.toUpperCase()); } catch (Exception ex) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "has item requirement at path: " + rPath + " does not specify a valid Material name!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "has item requirement at path: " + rPath + " does not specify a valid Material name!"); break; } wrapper.setMaterial(materialName); } else { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "has item requirement at path: " + rPath + " does not contain a material: entry!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "has item requirement at path: " + rPath + " does not contain a material: entry!"); break; } wrapper.setAmount(c.getInt(rPath + ".amount", 1)); @@ -1141,7 +932,7 @@ private RequirementList getRequirements(FileConfiguration c, String path) { } invert = type == RequirementType.DOES_NOT_HAVE_ITEM; - req = new HasItemRequirement(wrapper, invert); + req = new HasItemRequirement(plugin, wrapper, invert); break; case HAS_PERMISSION: case DOES_NOT_HAVE_PERMISSION: @@ -1149,11 +940,7 @@ private RequirementList getRequirements(FileConfiguration c, String path) { invert = type == RequirementType.DOES_NOT_HAVE_PERMISSION; req = new HasPermissionRequirement(c.getString(rPath + ".permission"), invert); } else { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Has Permission requirement at path: " + rPath + " does not contain a permission: entry" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Has Permission requirement at path: " + rPath + " does not contain a permission: entry"); } break; case HAS_PERMISSIONS: @@ -1162,7 +949,7 @@ private RequirementList getRequirements(FileConfiguration c, String path) { invert = type == RequirementType.DOES_NOT_HAVE_PERMISSIONS; int minimum = -1; if (c.contains(rPath + ".minimum") && (minimum = c.getInt(rPath + ".minimum")) < 1) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Has Permissions requirement at path: " + rPath + " has a minimum lower than 1. All permissions will be checked" @@ -1171,14 +958,14 @@ private RequirementList getRequirements(FileConfiguration c, String path) { } List permissions = c.getStringList(rPath + ".permissions"); if (permissions.isEmpty()) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Has Permissions requirement at path: " + rPath + " has no permissions to check. Ignoring..." ); break; } else if (minimum > permissions.size()) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Has Permissions requirement at path: " + rPath + " has a minimum higher than the amount of permissions. Using "+permissions.size()+" instead" @@ -1187,7 +974,7 @@ private RequirementList getRequirements(FileConfiguration c, String path) { } req = new HasPermissionsRequirement(permissions, minimum, invert); } else { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Has Permissions requirement at path: " + rPath + " does not contain permissions: entry" @@ -1196,13 +983,9 @@ private RequirementList getRequirements(FileConfiguration c, String path) { break; case JAVASCRIPT: if (c.contains(rPath + ".expression")) { - req = new JavascriptRequirement(c.getString(rPath + ".expression")); + req = new JavascriptRequirement(plugin, c.getString(rPath + ".expression")); } else { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Javascript requirement at path: " + rPath + " does not contain an expression: entry" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Javascript requirement at path: " + rPath + " does not contain an expression: entry"); } break; case EQUAL_TO: @@ -1218,49 +1001,31 @@ private RequirementList getRequirements(FileConfiguration c, String path) { case STRING_DOES_NOT_EQUAL: case STRING_DOES_NOT_EQUAL_IGNORECASE: if (c.contains(rPath + ".input") && c.contains(rPath + ".output")) { - req = new InputResultRequirement(type, - c.getString(rPath + ".input"), c.getString(rPath + ".output")); + req = new InputResultRequirement(type, c.getString(rPath + ".input"), c.getString(rPath + ".output")); } else { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Requirement at path: " + rPath + " does not contain the input: and/or the output: entries" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Requirement at path: " + rPath + " does not contain the input: and/or the output: entries"); } break; case HAS_MONEY: case DOES_NOT_HAVE_MONEY: if (c.contains(rPath + ".amount") || c.contains(rPath + ".placeholder")) { invert = type == RequirementType.DOES_NOT_HAVE_MONEY; - req = new HasMoneyRequirement(c.getDouble(rPath + ".amount"), invert, - c.getString(rPath + ".placeholder", null)); + req = new HasMoneyRequirement(plugin, c.getDouble(rPath + ".amount"), invert, c.getString(rPath + ".placeholder", null)); } else { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Has Money requirement at path: " + rPath + " does not contain an amount: entry" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Has Money requirement at path: " + rPath + " does not contain an amount: entry"); } break; case HAS_EXP: case DOES_NOT_HAVE_EXP: if (c.contains(rPath + ".amount")) { if (!containsPlaceholders(c.getString(rPath + ".amount")) && !c.isInt(rPath + ".amount")) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Value at path: " + rPath + ".amount is not a placeholder or a number" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Value at path: " + rPath + ".amount is not a placeholder or a number"); break; } invert = type == RequirementType.DOES_NOT_HAVE_EXP; - req = new HasExpRequirement(c.getString(rPath + ".amount"), invert, c.getBoolean(rPath + ".level")); + req = new HasExpRequirement(plugin, c.getString(rPath + ".amount"), invert, c.getBoolean(rPath + ".level")); } else { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Has Exp requirement at path: " + rPath + " does not contain an amount: entry" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Has Exp requirement at path: " + rPath + " does not contain an amount: entry"); } break; case REGEX_MATCHES: @@ -1268,14 +1033,9 @@ private RequirementList getRequirements(FileConfiguration c, String path) { if (c.contains(rPath + ".input") && c.contains(rPath + ".regex")) { Pattern p = Pattern.compile(c.getString(rPath + ".regex")); invert = type == RequirementType.REGEX_DOES_NOT_MATCH; - req = new RegexMatchesRequirement(p, - c.getString(rPath + ".input"), invert); + req = new RegexMatchesRequirement(p, c.getString(rPath + ".input"), invert); } else { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Regex requirement at path: " + rPath + " does not contain a input: or regex: entry" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Regex requirement at path: " + rPath + " does not contain a input: or regex: entry"); } break; case IS_NEAR: @@ -1284,44 +1044,25 @@ private RequirementList getRequirements(FileConfiguration c, String path) { invert = type == RequirementType.IS_NOT_NEAR; Location loc = LocationUtils.deserializeLocation(c.getString(rPath + ".location")); if (loc == null) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "requirement at path: " + rPath + " has an invalid location. Valid Format is: ,,," - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "requirement at path: " + rPath + " has an invalid location. Valid Format is: ,,,"); } req = new IsNearRequirement(loc, c.getInt(rPath + ".distance"), invert); } else { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Is Near requirement at path: " + rPath + " does not contain a location: or distance: entry" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Is Near requirement at path: " + rPath + " does not contain a location: or distance: entry"); } break; case HAS_META: case DOES_NOT_HAVE_META: if (!VersionHelper.IS_PDC_VERSION) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Has Meta requirement is not available for your server version!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Has Meta requirement is not available for your server version!"); break; } - if (c.contains(rPath + ".key") && c.contains(rPath + ".meta_type") && c - .contains(rPath + ".value")) { + if (c.contains(rPath + ".key") && c.contains(rPath + ".meta_type") && c.contains(rPath + ".value")) { String metaKey = c.getString(rPath + ".key"); invert = type == RequirementType.DOES_NOT_HAVE_META; - req = new HasMetaRequirement(metaKey, c.getString(rPath + ".meta_type").toUpperCase(), - c.getString(rPath + ".value"), invert); + req = new HasMetaRequirement(plugin, metaKey, c.getString(rPath + ".meta_type").toUpperCase(), c.getString(rPath + ".value"), invert); } else { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Has Meta requirement at path: " + rPath - + " does not contain the key:, meta_type: and/or value: entries!" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Has Meta requirement at path: " + rPath + " does not contain the key:, meta_type: and/or value: entries!"); } break; case STRING_LENGTH: @@ -1333,22 +1074,14 @@ private RequirementList getRequirements(FileConfiguration c, String path) { } req = new StringLengthRequirement(c.getString(rPath + ".input"), min, max); } else { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "String length requirement at path: " + rPath + " does not contain an input: or one of (min: or max:)" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "String length requirement at path: " + rPath + " does not contain an input: or one of (min: or max:)"); } break; case IS_OBJECT: if (c.contains(rPath + ".input") && c.contains(rPath + ".object")) { req = new IsObjectRequirement(c.getString(rPath + ".input"), c.getString(rPath + ".object")); } else { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "String length requirement at path: " + rPath + " does not contain an input: or object:" - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "String length requirement at path: " + rPath + " does not contain an input: or object:"); } break; default: @@ -1467,15 +1200,7 @@ public void onClick(@NotNull final MenuHolder holder) { continue; } - final ClickActionTask actionTask = new ClickActionTask( - plugin, - holder.getViewer().getUniqueId(), - action.getType(), - action.getExecutable(), - holder.getTypedArgs(), - holder.parsePlaceholdersInArguments(), - holder.parsePlaceholdersAfterArguments() - ); + final ClickActionTask actionTask = new ClickActionTask(plugin, holder.getViewer().getUniqueId(), action.getType(), action.getExecutable(), holder.getTypedArgs(), holder.parsePlaceholdersInArguments(), holder.parsePlaceholdersAfterArguments()); if (action.hasDelay()) { actionTask.runTaskLater(plugin, action.getDelay(holder)); @@ -1497,14 +1222,7 @@ private void checkForDeprecatedItemOptions(ConfigurationSection config, String m return; } - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - String.format( - "Option '%s' of item '%s' in menu '%s' is deprecated and will be removed in the future. Replace it with item_flags: [%s].", - option, config.getName(), menuName, itemFlag - ) - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, String.format("Option '%s' of item '%s' in menu '%s' is deprecated and will be removed in the future. Replace it with item_flags: [%s].", option, config.getName(), menuName, itemFlag)); }; oldItemFlagOptionCheck.accept("hide_attributes", ItemFlag.HIDE_ATTRIBUTES); @@ -1513,7 +1231,7 @@ private void checkForDeprecatedItemOptions(ConfigurationSection config, String m } public void debug(String... messages) { - DeluxeMenus.debug(DebugLevel.LOWEST, Level.INFO, messages); + plugin.debug(DebugLevel.LOWEST, Level.INFO, messages); } public @NotNull DebugLevel debugLevel() { @@ -1534,7 +1252,6 @@ public void debug(String... messages) { public File getMenuDirector() { return menuDirectory; } - public void addEnchantmentsOptionToBuilder(final FileConfiguration c, final String currentPath, final String itemKey, final String menuName, final MenuItemOptions.MenuItemOptionsBuilder builder) { @@ -1547,7 +1264,7 @@ public void addEnchantmentsOptionToBuilder(final FileConfiguration c, final Stri for (final String configEnchantment : configEnchantments) { if (configEnchantment == null || !configEnchantment.contains(";")) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Enchantment format '" + configEnchantment + "' is incorrect for item " + itemKey + @@ -1559,7 +1276,7 @@ public void addEnchantmentsOptionToBuilder(final FileConfiguration c, final Stri String[] parts = configEnchantment.split(";", 2); if (parts.length != 2 || parts[0] == null || parts[1] == null) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Enchantment format '" + configEnchantment + "' is incorrect for item " + itemKey + @@ -1571,7 +1288,7 @@ public void addEnchantmentsOptionToBuilder(final FileConfiguration c, final Stri final Enchantment enchantment = Enchantment.getByName(parts[0].strip().toUpperCase()); if (enchantment == null) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Enchantment '" + parts[0].strip() + "' for item " + itemKey + @@ -1582,7 +1299,7 @@ public void addEnchantmentsOptionToBuilder(final FileConfiguration c, final Stri Integer level = Ints.tryParse(parts[1].strip()); if (level == null) { level = 1; - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Enchantment level '" + parts[1].strip() + "' is incorrect for item " + itemKey + @@ -1608,20 +1325,10 @@ public void addDamageOptionToBuilder(final FileConfiguration c, final String cur key = "data"; if (c.contains(currentPath + key)) { if (!damageOptionIsPresent) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Found 'data' option for item: " + itemKey + " in menu: " + menuName+ ". This option " + - "is deprecated and will be removed soon. Please use 'damage' instead." - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Found 'data' option for item: " + itemKey + " in menu: " + menuName + ". This option " + "is deprecated and will be removed soon. Please use 'damage' instead."); damageValue = c.getString(currentPath + key, ""); } else { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Found 'data' and 'damage' options for item: " + itemKey + " in menu: " + menuName + - ". 'data' option is deprecated and will be ignored. Using 'damage' instead." - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Found 'data' and 'damage' options for item: " + itemKey + " in menu: " + menuName + ". 'data' option is deprecated and will be ignored. Using 'damage' instead."); } } @@ -1634,28 +1341,13 @@ public void addDamageOptionToBuilder(final FileConfiguration c, final String cur } if (!ItemUtils.isPlaceholderOption(damageValue) && Ints.tryParse(damageValue) == null) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Found invalid value for '" + key + "' option for item: " + itemKey + " in menu: " + - menuName + ".", - "The correct formats for '" + key + "' are:", - " -> ", - " -> placeholder-", - "Ignoring the invalid value." - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Found invalid value for '" + key + "' option for item: " + itemKey + " in menu: " + menuName + ".", "The correct formats for '" + key + "' are:", " -> ", " -> placeholder-", "Ignoring the invalid value."); return; } final String[] parts = damageValue.split("-", 2); if (parts.length >= 2 && !containsPlaceholders(parts[1])) { - DeluxeMenus.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Could not find placeholder for '" + key + "' option for item: " + itemKey + " in menu: " + - menuName + ".", - "Ignoring the invalid value." - ); + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find placeholder for '" + key + "' option for item: " + itemKey + " in menu: " + menuName + ".", "Ignoring the invalid value."); return; } diff --git a/src/main/java/com/extendedclip/deluxemenus/config/GeneralConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/GeneralConfig.java new file mode 100644 index 00000000..d8f25066 --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/config/GeneralConfig.java @@ -0,0 +1,72 @@ +package com.extendedclip.deluxemenus.config; + +import com.extendedclip.deluxemenus.DeluxeMenus; +import com.extendedclip.deluxemenus.utils.DebugLevel; +import org.jetbrains.annotations.NotNull; + +public class GeneralConfig { + private final DeluxeMenus plugin; + + private boolean checkForUpdates = true; + private DebugLevel debugLevel = getDefaultDebugLevel(); + private boolean useAdminCommandsInMenusList = false; + private int menusListPageSize = 10; + + public GeneralConfig(final @NotNull DeluxeMenus plugin) { + this.plugin = plugin; + } + + public void load() { + plugin.getConfig().addDefault("check_updates", checkForUpdates); + plugin.getConfig().addDefault("debug", debugLevel.name()); + plugin.getConfig().addDefault("use_admin_commands_in_menus_list", false); + plugin.getConfig().addDefault("menus_list_page_size", menusListPageSize); + + checkForUpdates = plugin.getConfig().getBoolean("check_updates", false); + debugLevel = loadDebugLevel(); + useAdminCommandsInMenusList = plugin.getConfig().getBoolean("use_admin_commands_in_menus_list", false); + menusListPageSize = plugin.getConfig().getInt("menus_list_page_size", 10); + } + + public void reload() { + plugin.reloadConfig(); + load(); + } + + public boolean checkForUpdates() { + return checkForUpdates; + } + + public DebugLevel debugLevel() { + return debugLevel; + } + + public boolean useAdminCommandsInMenusList() { + return useAdminCommandsInMenusList; + } + + public int menusListPageSize() { + return menusListPageSize; + } + + private @NotNull DebugLevel loadDebugLevel() { + String configDebugLevel = plugin.getConfig().getString("debug", "HIGHEST"); + + if (configDebugLevel.equalsIgnoreCase("true")) { + configDebugLevel = "LOWEST"; + plugin.getConfig().set("debug", "LOWEST"); + } + + if (configDebugLevel.equalsIgnoreCase("false")) { + configDebugLevel = "HIGHEST"; + plugin.getConfig().set("debug", "HIGHEST"); + } + + final DebugLevel debugLevel = DebugLevel.getByName(configDebugLevel); + return debugLevel == null ? getDefaultDebugLevel() : debugLevel; + } + + private DebugLevel getDefaultDebugLevel() { + return DebugLevel.LOW; + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/dupe/DupeFixer.java b/src/main/java/com/extendedclip/deluxemenus/dupe/DupeFixer.java index 4730739a..5a425110 100644 --- a/src/main/java/com/extendedclip/deluxemenus/dupe/DupeFixer.java +++ b/src/main/java/com/extendedclip/deluxemenus/dupe/DupeFixer.java @@ -1,9 +1,9 @@ package com.extendedclip.deluxemenus.dupe; import com.extendedclip.deluxemenus.DeluxeMenus; +import com.extendedclip.deluxemenus.listener.Listener; import com.extendedclip.deluxemenus.utils.DebugLevel; import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerLoginEvent; @@ -12,15 +12,17 @@ import java.util.logging.Level; -public class DupeFixer implements Listener { +/** + * Prevents duplication of items created by DeluxeMenus. Items created by DeluxeMenus are marked and removed if found + * outside the inventory they were created in. + */ +public class DupeFixer extends Listener { - private final DeluxeMenus plugin; private final MenuItemMarker marker; public DupeFixer(@NotNull final DeluxeMenus plugin, @NotNull final MenuItemMarker marker) { - this.plugin = plugin; + super(plugin); this.marker = marker; - plugin.getServer().getPluginManager().registerEvents(this, plugin); } @EventHandler @@ -29,7 +31,7 @@ private void onPickup(@NotNull final EntityPickupItemEvent event) { return; } - DeluxeMenus.debug( + plugin.debug( DebugLevel.LOWEST, Level.INFO, "Someone picked up a DeluxeMenus item. Removing it." @@ -43,7 +45,7 @@ private void onDrop(@NotNull final PlayerDropItemEvent event) { return; } - DeluxeMenus.debug( + plugin.debug( DebugLevel.LOWEST, Level.INFO, "A DeluxeMenus item was dropped in the world. Removing it." @@ -60,7 +62,7 @@ private void onLogin(@NotNull final PlayerLoginEvent event) { if (itemStack == null) continue; if (!marker.isMarked(itemStack)) continue; - DeluxeMenus.debug( + plugin.debug( DebugLevel.LOWEST, Level.INFO, "Player logged in with a DeluxeMenus item in their inventory. Removing it." diff --git a/src/main/java/com/extendedclip/deluxemenus/dupe/MenuItemMarker.java b/src/main/java/com/extendedclip/deluxemenus/dupe/MenuItemMarker.java index cf71d746..6f439afb 100644 --- a/src/main/java/com/extendedclip/deluxemenus/dupe/MenuItemMarker.java +++ b/src/main/java/com/extendedclip/deluxemenus/dupe/MenuItemMarker.java @@ -12,6 +12,10 @@ import java.util.regex.Pattern; +/** + * Mark inventory items created by DeluxeMenus to prevent duplication. Marked items will be removed from all inventories + * except the one they were created in. + */ public class MenuItemMarker implements ItemMarker { private final static String DEFAULT_MARK = "DM"; diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java index 2fc19922..0178ec75 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java @@ -11,24 +11,29 @@ public class BaseHeadHook implements ItemHook, SimpleCache { + private final DeluxeMenus plugin; private final Map cache = new ConcurrentHashMap<>(); + public BaseHeadHook(@NotNull final DeluxeMenus plugin) { + this.plugin = plugin; + } + @Override public ItemStack getItem(@NotNull final String... arguments) { if (arguments.length == 0) { - return DeluxeMenus.getInstance().getHead().clone(); + return plugin.getHead().clone(); } try { - return cache.computeIfAbsent(arguments[0], SkullUtils::getSkullByBase64EncodedTextureUrl).clone(); + return cache.computeIfAbsent(arguments[0], value -> SkullUtils.getSkullByBase64EncodedTextureUrl(plugin, value)).clone(); } catch (Exception exception) { - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Something went wrong while trying to get base64 head: " + arguments[0], exception ); } - return DeluxeMenus.getInstance().getHead().clone(); + return plugin.getHead().clone(); } @Override @@ -36,7 +41,7 @@ public boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull String.. if (arguments.length == 0) { return false; } - String itemTexture = SkullUtils.getTextureFromSkull(item); + String itemTexture = SkullUtils.getTextureFromSkull(plugin, item); String texture = SkullUtils.decodeSkinUrl(arguments[0]); if (itemTexture == null || texture == null) return false; diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/HeadDatabaseHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/HeadDatabaseHook.java index b3e5c029..f1c4c421 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/HeadDatabaseHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/HeadDatabaseHook.java @@ -7,30 +7,29 @@ public class HeadDatabaseHook implements ItemHook { - private final HeadDatabaseAPI api; + private final DeluxeMenus plugin; + private final HeadDatabaseAPI api; - public HeadDatabaseHook() { - api = new HeadDatabaseAPI(); - } - - @Override - public ItemStack getItem(@NotNull final String... arguments) { - if (arguments.length == 0) { - return DeluxeMenus.getInstance().getHead().clone(); + public HeadDatabaseHook(@NotNull final DeluxeMenus plugin) { + this.plugin = plugin; + api = new HeadDatabaseAPI(); } - try { - final ItemStack item = api.getItemHead(arguments[0]); - return item != null ? item : DeluxeMenus.getInstance().getHead().clone(); - } catch (NullPointerException exception) { - DeluxeMenus.printStacktrace( - "Something went wrong while trying to get head database head: " + arguments[0], - exception - ); - } + @Override + public ItemStack getItem(@NotNull final String... arguments) { + if (arguments.length == 0) { + return plugin.getHead().clone(); + } - return DeluxeMenus.getInstance().getHead().clone(); - } + try { + final ItemStack item = api.getItemHead(arguments[0]); + return item != null ? item : plugin.getHead().clone(); + } catch (NullPointerException exception) { + plugin.printStacktrace("Something went wrong while trying to get head database head: " + arguments[0], exception); + } + + return plugin.getHead().clone(); + } @Override public boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull String... arguments) { @@ -40,8 +39,8 @@ public boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull String.. return arguments[0].equalsIgnoreCase(api.getItemID(item)); } - @Override - public String getPrefix() { - return "hdb-"; - } + @Override + public String getPrefix() { + return "hdb-"; + } } diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java index 282e8703..cd21397c 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java @@ -18,6 +18,11 @@ public class MMOItemsHook implements ItemHook, SimpleCache { private final Map cache = new ConcurrentHashMap<>(); + private final DeluxeMenus plugin; + + public MMOItemsHook(final @NotNull DeluxeMenus plugin) { + this.plugin = plugin; + } @Override public ItemStack getItem(@NotNull final String... arguments) { @@ -42,7 +47,7 @@ public ItemStack getItem(@NotNull final String... arguments) { ItemStack mmoItem = null; try { - mmoItem = Bukkit.getScheduler().callSyncMethod(DeluxeMenus.getInstance(), () -> { + mmoItem = Bukkit.getScheduler().callSyncMethod(plugin, () -> { ItemStack item = MMOItems.plugin.getItem(itemType, splitArgs[1]); if (item == null) { @@ -54,7 +59,7 @@ public ItemStack getItem(@NotNull final String... arguments) { return item; }).get(); } catch (InterruptedException | ExecutionException e) { - DeluxeMenus.debug(DebugLevel.HIGHEST, Level.SEVERE, "Error getting MMOItem synchronously."); + plugin.debug(DebugLevel.HIGHEST, Level.SEVERE, "Error getting MMOItem synchronously."); } return mmoItem == null ? new ItemStack(Material.STONE, 1) : mmoItem; diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java index 26768da3..aa880ba5 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java @@ -2,45 +2,41 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.cache.SimpleCache; +import com.extendedclip.deluxemenus.listener.Listener; import com.extendedclip.deluxemenus.utils.SkullUtils; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import com.extendedclip.deluxemenus.utils.VersionHelper; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.SkullMeta; import org.jetbrains.annotations.NotNull; -public class NamedHeadHook implements ItemHook, Listener, SimpleCache { +public class NamedHeadHook extends Listener implements ItemHook, SimpleCache { private final Map cache = new ConcurrentHashMap<>(); public NamedHeadHook(@NotNull final DeluxeMenus plugin) { - plugin.getServer().getPluginManager().registerEvents(this, plugin); + super(plugin); } @Override public ItemStack getItem(@NotNull final String... arguments) { if (arguments.length == 0) { - return DeluxeMenus.getInstance().getHead().clone(); + return plugin.getHead().clone(); } try { - return cache.computeIfAbsent(arguments[0], SkullUtils::getSkullByName).clone(); + return cache.computeIfAbsent(arguments[0], value -> SkullUtils.getSkullByName(plugin, value)).clone(); } catch (Exception exception) { - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Something went wrong while trying to get a head by name" + ": " + arguments[0], exception ); } - return DeluxeMenus.getInstance().getHead().clone(); + return plugin.getHead().clone(); } @Override diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java index 147d6557..127a8356 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/TextureHeadHook.java @@ -3,49 +3,51 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.cache.SimpleCache; import com.extendedclip.deluxemenus.utils.SkullUtils; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + public class TextureHeadHook implements ItemHook, SimpleCache { - private final Map cache = new ConcurrentHashMap<>(); + private final DeluxeMenus plugin; + private final Map cache = new ConcurrentHashMap<>(); - @Override - public ItemStack getItem(@NotNull final String... arguments) { - if (arguments.length == 0) { - return DeluxeMenus.getInstance().getHead().clone(); + public TextureHeadHook(@NotNull final DeluxeMenus plugin) { + this.plugin = plugin; } - try { - return cache.computeIfAbsent(arguments[0], key -> SkullUtils.getSkullByBase64EncodedTextureUrl(SkullUtils.getEncoded(key))).clone(); - } catch (Exception exception) { - DeluxeMenus.printStacktrace( - "Something went wrong while trying to get texture head: " + arguments[0], - exception - ); - } + @Override + public ItemStack getItem(@NotNull final String... arguments) { + if (arguments.length == 0) { + return plugin.getHead().clone(); + } - return DeluxeMenus.getInstance().getHead().clone(); - } + try { + return cache.computeIfAbsent(arguments[0], key -> SkullUtils.getSkullByBase64EncodedTextureUrl(plugin, SkullUtils.getEncoded(key))).clone(); + } catch (Exception exception) { + plugin.printStacktrace("Something went wrong while trying to get texture head: " + arguments[0], exception); + } + + return plugin.getHead().clone(); + } @Override public boolean itemMatchesIdentifiers(@NotNull ItemStack item, @NotNull String... arguments) { if (arguments.length == 0) { return false; } - return arguments[0].equals(SkullUtils.getTextureFromSkull(item)); + return arguments[0].equals(SkullUtils.getTextureFromSkull(plugin, item)); } - @Override - public String getPrefix() { - return "texture-"; - } + @Override + public String getPrefix() { + return "texture-"; + } - @Override - public void clearCache() { - cache.clear(); - } + @Override + public void clearCache() { + cache.clear(); + } } diff --git a/src/main/java/com/extendedclip/deluxemenus/listener/Listener.java b/src/main/java/com/extendedclip/deluxemenus/listener/Listener.java new file mode 100644 index 00000000..a219218c --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/listener/Listener.java @@ -0,0 +1,16 @@ +package com.extendedclip.deluxemenus.listener; + +import com.extendedclip.deluxemenus.DeluxeMenus; +import org.jetbrains.annotations.NotNull; + +public abstract class Listener implements org.bukkit.event.Listener { + protected final DeluxeMenus plugin; + + public Listener(@NotNull final DeluxeMenus plugin) { + this.plugin = plugin; + } + + public void register() { + this.plugin.getServer().getPluginManager().registerEvents(this, this.plugin); + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java b/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java index 8f1d994a..af69231b 100644 --- a/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java +++ b/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java @@ -8,15 +8,10 @@ import com.extendedclip.deluxemenus.requirement.RequirementList; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; - -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.TimeUnit; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; @@ -25,210 +20,202 @@ import org.bukkit.event.player.PlayerQuitEvent; import org.jetbrains.annotations.NotNull; -public class PlayerListener implements Listener { - - private final DeluxeMenus plugin; - private final Cache cache = CacheBuilder.newBuilder() - .expireAfterWrite(75, TimeUnit.MILLISECONDS).build(); - - // This is so dumb. Mojang fix your shit. - private final Cache shiftCache = CacheBuilder.newBuilder() - .expireAfterWrite(200, TimeUnit.MILLISECONDS).build(); +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.TimeUnit; - public PlayerListener(DeluxeMenus plugin) { - this.plugin = plugin; - Bukkit.getPluginManager().registerEvents(this, this.plugin); - } +public class PlayerListener extends Listener { - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) - public void onCommandExecute(PlayerCommandPreprocessEvent event) { + private final Cache cache = CacheBuilder.newBuilder().expireAfterWrite(75, TimeUnit.MILLISECONDS).build(); - final String cmd = event.getMessage().substring(1); - final Optional optionalMenu = Menu.getMenuByCommand(cmd.toLowerCase()); + // This is so dumb. Mojang fix your shit. + private final Cache shiftCache = CacheBuilder.newBuilder().expireAfterWrite(200, TimeUnit.MILLISECONDS).build(); - if (optionalMenu.isEmpty()) { - return; + public PlayerListener(@NotNull final DeluxeMenus plugin) { + super(plugin); } - final Menu menu = optionalMenu.get(); + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onCommandExecute(PlayerCommandPreprocessEvent event) { - if (menu.options().registerCommands()) { - return; - } + final String cmd = event.getMessage().substring(1); + final Optional optionalMenu = Menu.getMenuByCommand(cmd.toLowerCase()); - Player player = event.getPlayer(); - menu.openMenu(player); - event.setCancelled(true); - } + if (optionalMenu.isEmpty()) { + return; + } - @EventHandler - public void onLeave(PlayerQuitEvent event) { - Player player = event.getPlayer(); + final Menu menu = optionalMenu.get(); - if (Menu.isInMenu(player)) { - Menu.closeMenu(player, false); - } - } + if (menu.options().registerCommands()) { + return; + } - @EventHandler - public void onOpen(InventoryOpenEvent event) { - if (!(event.getPlayer() instanceof Player)) { - return; + Player player = event.getPlayer(); + menu.openMenu(player); + event.setCancelled(true); } - final Player player = (Player) event.getPlayer(); + @EventHandler + public void onLeave(PlayerQuitEvent event) { + Player player = event.getPlayer(); - if (player.isSleeping()) { - event.setCancelled(true); + if (Menu.isInMenu(player)) { + Menu.closeMenu(plugin, player, false); + } } - if (Menu.isInMenu(player)) { - Menu.closeMenu(player, true); - } - } + @EventHandler + public void onOpen(InventoryOpenEvent event) { + if (!(event.getPlayer() instanceof Player)) { + return; + } + + final Player player = (Player) event.getPlayer(); - @EventHandler - public void onClose(InventoryCloseEvent event) { + if (player.isSleeping()) { + event.setCancelled(true); + } - if (!(event.getPlayer() instanceof Player)) { - return; + if (Menu.isInMenu(player)) { + Menu.closeMenu(plugin, player, true); + } } - final Player player = (Player) event.getPlayer(); + @EventHandler + public void onClose(InventoryCloseEvent event) { - if (Menu.isInMenu(player)) { - Menu.closeMenu(player, false); - Bukkit.getScheduler().runTaskLater(plugin, () -> { - Menu.cleanInventory(player, plugin.getMenuItemMarker()); - player.updateInventory(); - }, 3L); - } - } + if (!(event.getPlayer() instanceof Player)) { + return; + } - @EventHandler(priority = EventPriority.LOW) - public void onClick(InventoryClickEvent event) { + final Player player = (Player) event.getPlayer(); - if (!(event.getWhoClicked() instanceof Player)) { - return; + if (Menu.isInMenu(player)) { + Menu.closeMenu(plugin, player, false); + Bukkit.getScheduler().runTaskLater(plugin, () -> { + Menu.cleanInventory(plugin, player); + player.updateInventory(); + }, 3L); + } } - final Player player = (Player) event.getWhoClicked(); + @EventHandler(priority = EventPriority.LOW) + public void onClick(InventoryClickEvent event) { - final Optional optionalHolder = Menu.getMenuHolder(player); + if (!(event.getWhoClicked() instanceof Player)) { + return; + } - if (optionalHolder.isEmpty()) { - return; - } + final Player player = (Player) event.getWhoClicked(); - final MenuHolder holder = optionalHolder.get(); + final Optional optionalHolder = Menu.getMenuHolder(player); - if (holder.getMenu().isEmpty()) { - Menu.closeMenu(player, true); - } + if (optionalHolder.isEmpty()) { + return; + } - if (holder.isUpdating()) { - event.setCancelled(true); - return; - } + final MenuHolder holder = optionalHolder.get(); - event.setCancelled(true); + if (holder.getMenu().isEmpty()) { + Menu.closeMenu(plugin, player, true); + } - int slot = event.getRawSlot(); + if (holder.isUpdating()) { + event.setCancelled(true); + return; + } - MenuItem item = holder.getItem(slot); + event.setCancelled(true); - if (item == null) { - return; - } + int slot = event.getRawSlot(); - if (this.cache.getIfPresent(player.getUniqueId()) != null) { - return; - } + MenuItem item = holder.getItem(slot); - if (this.shiftCache.getIfPresent(player.getUniqueId()) != null) { - return; - } + if (item == null) { + return; + } - if (event.getClick() == ClickType.DOUBLE_CLICK) { - return; - } + if (this.cache.getIfPresent(player.getUniqueId()) != null) { + return; + } - if (event.getClick() == ClickType.SHIFT_LEFT) { - this.shiftCache.put(player.getUniqueId(), System.currentTimeMillis()); - } + if (this.shiftCache.getIfPresent(player.getUniqueId()) != null) { + return; + } - if (handleClick(player, holder, item.options().clickHandler(), - item.options().clickRequirements())) { - return; - } + if (event.getClick() == ClickType.DOUBLE_CLICK) { + return; + } - if (event.isShiftClick() && event.isLeftClick()) { - if (handleClick(player, holder, item.options().shiftLeftClickHandler(), - item.options().shiftLeftClickRequirements())) { - return; - } - } + if (event.getClick() == ClickType.SHIFT_LEFT) { + this.shiftCache.put(player.getUniqueId(), System.currentTimeMillis()); + } - if (event.isShiftClick() && event.isRightClick()) { - if (handleClick(player, holder, item.options().shiftRightClickHandler(), - item.options().shiftRightClickRequirements())) { - return; - } - } + if (handleClick(player, holder, item.options().clickHandler(), item.options().clickRequirements())) { + return; + } - if (event.getClick() == ClickType.LEFT) { - if (handleClick(player, holder, item.options().leftClickHandler(), - item.options().leftClickRequirements())) { - return; - } - } + if (event.isShiftClick() && event.isLeftClick()) { + if (handleClick(player, holder, item.options().shiftLeftClickHandler(), item.options().shiftLeftClickRequirements())) { + return; + } + } - if (event.getClick() == ClickType.RIGHT) { - if (handleClick(player, holder, item.options().rightClickHandler(), - item.options().rightClickRequirements())) { - return; - } - } + if (event.isShiftClick() && event.isRightClick()) { + if (handleClick(player, holder, item.options().shiftRightClickHandler(), item.options().shiftRightClickRequirements())) { + return; + } + } - if (event.getClick() == ClickType.MIDDLE) { - if (handleClick(player, holder, item.options().middleClickHandler(), - item.options().middleClickRequirements())) { - return; - } - } - } - - /** - * Handles menu click by player - * @param player player who clicked - * @param holder menu holder - * @param handler click handler - * @param requirements click requirements - * @return true if click was handled successfully. will ever return false if no click handler was found - */ - private boolean handleClick(final @NotNull Player player, final @NotNull MenuHolder holder, - final @NotNull Optional handler, - final @NotNull Optional requirements) { - if (handler.isEmpty()) { - return false; + if (event.getClick() == ClickType.LEFT) { + if (handleClick(player, holder, item.options().leftClickHandler(), item.options().leftClickRequirements())) { + return; + } + } + + if (event.getClick() == ClickType.RIGHT) { + if (handleClick(player, holder, item.options().rightClickHandler(), item.options().rightClickRequirements())) { + return; + } + } + + if (event.getClick() == ClickType.MIDDLE) { + if (handleClick(player, holder, item.options().middleClickHandler(), item.options().middleClickRequirements())) { + } + } } - if (requirements.isPresent()) { - final ClickHandler denyHandler = requirements.get().getDenyHandler(); + /** + * Handles menu click by player + * + * @param player player who clicked + * @param holder menu holder + * @param handler click handler + * @param requirements click requirements + * @return true if click was handled successfully. will ever return false if no click handler was found + */ + private boolean handleClick(final @NotNull Player player, final @NotNull MenuHolder holder, final @NotNull Optional handler, final @NotNull Optional requirements) { + if (handler.isEmpty()) { + return false; + } + + if (requirements.isPresent()) { + final ClickHandler denyHandler = requirements.get().getDenyHandler(); + + if (!requirements.get().evaluate(holder)) { + if (denyHandler == null) { + return true; + } - if (!requirements.get().evaluate(holder)) { - if (denyHandler == null) { - return true; + denyHandler.onClick(holder); + return true; + } } - denyHandler.onClick(holder); + this.cache.put(player.getUniqueId(), System.currentTimeMillis()); + handler.get().onClick(holder); + return true; - } } - - this.cache.put(player.getUniqueId(), System.currentTimeMillis()); - handler.get().onClick(holder); - - return true; - } } diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java index bc1d2013..2de43f5d 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java @@ -1,7 +1,6 @@ package com.extendedclip.deluxemenus.menu; import com.extendedclip.deluxemenus.DeluxeMenus; -import com.extendedclip.deluxemenus.dupe.MenuItemMarker; import com.extendedclip.deluxemenus.menu.options.MenuOptions; import com.extendedclip.deluxemenus.requirement.RequirementList; import com.extendedclip.deluxemenus.utils.DebugLevel; @@ -32,14 +31,24 @@ public class Menu extends Command { private static final Map lastOpenedMenus = new HashMap<>(); private static CommandMap commandMap = null; + private final DeluxeMenus plugin; private final MenuOptions options; private final Map> items; - - public Menu(final @NotNull MenuOptions options, final @NotNull Map> items) { + // menu path starting from the plugin directory + private final String path; + + public Menu( + final @NotNull DeluxeMenus plugin, + final @NotNull MenuOptions options, + final @NotNull Map> items, + final @NotNull String path + ) { super(options.commands().isEmpty() ? options.name() : options.commands().get(0)); + this.plugin = plugin; this.options = options; this.items = items; + this.path = path; if (this.options.registerCommands()) { if (this.options.commands().size() > 1) { @@ -51,10 +60,10 @@ public Menu(final @NotNull MenuOptions options, final @NotNull Map> getPathSortedMenus() { + return menus.values().stream().map(m -> Map.entry(m.path(), m)).collect( + TreeMap::new, (tree, entry) -> { + final List list = tree.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()); + list.add(entry.getValue()); + tree.put(entry.getKey(), list); + }, + (tree1, tree2) -> { + for (Entry> entry : tree2.entrySet()) { + final List list = tree1.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()); + list.addAll(entry.getValue()); + tree1.put(entry.getKey(), list); + } + } + ); + } + public static @NotNull Optional getMenuByName(final @NotNull String name) { return menus.entrySet().stream().filter(e -> e.getKey().equalsIgnoreCase(name)).findFirst().map(Entry::getValue); } @@ -130,12 +158,12 @@ public static Optional getLastMenu(final @NotNull Player player) { return Optional.ofNullable(lastOpenedMenus.get(player.getUniqueId())); } - public static void cleanInventory(final @NotNull Player player, final @NotNull MenuItemMarker marker) { + public static void cleanInventory(final @NotNull DeluxeMenus plugin, final @NotNull Player player) { for (final ItemStack itemStack : player.getInventory().getContents()) { if (itemStack == null) continue; - if (!marker.isMarked(itemStack)) continue; + if (!plugin.getMenuItemMarker().isMarked(itemStack)) continue; - DeluxeMenus.debug( + plugin.debug( DebugLevel.LOWEST, Level.INFO, "Found a DeluxeMenus item in a player's inventory. Removing it." @@ -145,7 +173,7 @@ public static void cleanInventory(final @NotNull Player player, final @NotNull M player.updateInventory(); } - public static void closeMenu(final @NotNull Player player, final boolean close, final boolean executeCloseActions) { + public static void closeMenu(final @NotNull DeluxeMenus plugin, final @NotNull Player player, final boolean close, final boolean executeCloseActions) { Optional optionalHolder = getMenuHolder(player); if (optionalHolder.isEmpty()) { return; @@ -160,24 +188,24 @@ public static void closeMenu(final @NotNull Player player, final boolean close, } if (close) { - Bukkit.getScheduler().runTask(DeluxeMenus.getInstance(), () -> { + Bukkit.getScheduler().runTask(plugin, () -> { player.closeInventory(); - cleanInventory(player, DeluxeMenus.getInstance().getMenuItemMarker()); + cleanInventory(plugin, player); }); } menuHolders.remove(holder); lastOpenedMenus.put(player.getUniqueId(), holder.getMenu().orElse(null)); } - public static void closeMenuForShutdown(final @NotNull Player player) { + public static void closeMenuForShutdown(final @NotNull DeluxeMenus plugin, final @NotNull Player player) { getMenuHolder(player).ifPresent(MenuHolder::stopPlaceholderUpdate); player.closeInventory(); - cleanInventory(player, DeluxeMenus.getInstance().getMenuItemMarker()); + cleanInventory(plugin, player); } - public static void closeMenu(final @NotNull Player player, final boolean close) { - closeMenu(player, close, false); + public static void closeMenu(final @NotNull DeluxeMenus plugin, final @NotNull Player player, final boolean close) { + closeMenu(plugin, player, close, false); } private void addCommand() { @@ -187,7 +215,7 @@ private void addCommand() { f.setAccessible(true); commandMap = (CommandMap) f.get(Bukkit.getServer()); } catch (final @NotNull Exception exception) { - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Something went wrong while trying to register command: " + this.getName(), exception ); @@ -196,7 +224,7 @@ private void addCommand() { } boolean registered = commandMap.register("DeluxeMenus", this); if (registered) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.LOW, Level.INFO, "Registered command: " + this.getName() + " for menu: " + this.options.name() @@ -218,20 +246,20 @@ private void removeCommand() { boolean unregistered = this.unregister((CommandMap) cMap.get(Bukkit.getServer())); this.unregister(commandMap); if (unregistered) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGH, Level.INFO, "Successfully unregistered command: " + this.getName() ); } else { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Failed to unregister command: " + this.getName() ); } } catch (final @NotNull Exception exception) { - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Something went wrong while trying to unregister command: " + this.getName(), exception ); @@ -249,7 +277,7 @@ public boolean execute(final @NotNull CommandSender sender, final @NotNull Strin Map argMap = null; if (!this.options.arguments().isEmpty()) { - DeluxeMenus.debug(DebugLevel.LOWEST, Level.INFO, "has args"); + plugin.debug(DebugLevel.LOWEST, Level.INFO, "has args"); if (typedArgs.length < this.options.arguments().size()) { if (this.options.argumentsUsageMessage().isPresent()) { Msg.msg(sender, this.options.argumentsUsageMessage().get()); @@ -261,18 +289,18 @@ public boolean execute(final @NotNull CommandSender sender, final @NotNull Strin for (String arg : this.options.arguments()) { if (index + 1 == this.options.arguments().size()) { String last = String.join(" ", Arrays.asList(typedArgs).subList(index, typedArgs.length)); - DeluxeMenus.debug(DebugLevel.LOWEST, Level.INFO, "arg: " + arg + " => " + last); + plugin.debug(DebugLevel.LOWEST, Level.INFO, "arg: " + arg + " => " + last); argMap.put(arg, last); } else { argMap.put(arg, typedArgs[index]); - DeluxeMenus.debug(DebugLevel.LOWEST, Level.INFO, "arg: " + arg + " => " + typedArgs[index]); + plugin.debug(DebugLevel.LOWEST, Level.INFO, "arg: " + arg + " => " + typedArgs[index]); } index++; } } Player player = (Player) sender; - DeluxeMenus.debug(DebugLevel.LOWEST, Level.INFO, "opening menu: " + this.options.name()); + plugin.debug(DebugLevel.LOWEST, Level.INFO, "opening menu: " + this.options.name()); openMenu(player, argMap, null); return true; } @@ -331,7 +359,7 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map { + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { Set activeItems = new HashSet<>(); @@ -358,7 +386,7 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map= this.options.size()) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Item set to slot " + slot + " for menu: " + this.options.name() + " exceeds the inventory size!", @@ -413,12 +441,12 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map= this.options.size()) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Item set to slot " + slot + " for menu: " + this.options.name() + " exceeds the inventory size!", @@ -436,9 +464,9 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map { + Bukkit.getScheduler().runTask(plugin, () -> { if (isInMenu(holder.getViewer())) { - closeMenu(holder.getViewer(), false); + closeMenu(plugin, holder.getViewer(), false); } viewer.openInventory(inventory); @@ -462,4 +490,8 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map activeItems; @@ -34,12 +36,14 @@ public class MenuHolder implements InventoryHolder { private boolean parsePlaceholdersAfterArguments; private Map typedArgs; - public MenuHolder(Player viewer) { + public MenuHolder(final @NotNull DeluxeMenus plugin, final @NotNull Player viewer) { + this.plugin = plugin; this.viewer = viewer; } - public MenuHolder(Player viewer, String menuName, - Set activeItems, Inventory inventory) { + public MenuHolder(final @NotNull DeluxeMenus plugin, final @NotNull Player viewer, final @NotNull String menuName, + final @NotNull Set<@NotNull MenuItem> activeItems, final @NotNull Inventory inventory) { + this.plugin = plugin; this.viewer = viewer; this.menuName = menuName; this.activeItems = activeItems; @@ -135,7 +139,7 @@ public void refreshMenu() { stopPlaceholderUpdate(); - Bukkit.getScheduler().runTaskAsynchronously(DeluxeMenus.getInstance(), () -> { + Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { final Set active = new HashSet<>(); @@ -170,10 +174,10 @@ public void refreshMenu() { } if (active.isEmpty()) { - Menu.closeMenu(getViewer(), true); + Menu.closeMenu(plugin, getViewer(), true); } - Bukkit.getScheduler().runTask(DeluxeMenus.getInstance(), () -> { + Bukkit.getScheduler().runTask(plugin, () -> { boolean update = false; @@ -255,7 +259,7 @@ public void run() { amt = 1; } } catch (Exception exception) { - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Something went wrong while updating item in slot " + item.options().slot() + ". Invalid dynamic amount: " + setPlaceholdersAndArguments(item.options().dynamicAmount().get()), exception @@ -286,7 +290,7 @@ public void run() { } } - }.runTaskTimerAsynchronously(DeluxeMenus.getInstance(), 20L, + }.runTaskTimerAsynchronously(plugin, 20L, 20L * Menu.getMenuByName(menuName) .map(Menu::options) .map(MenuOptions::updateInterval) @@ -341,4 +345,8 @@ public void setPlaceholderPlayer(Player placeholderPlayer) { public Player getPlaceholderPlayer() { return placeholderPlayer; } + + public @NotNull DeluxeMenus getPlugin() { + return plugin; + } } diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java index 07eeaae8..dc28bd14 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -54,9 +54,11 @@ public class MenuItem { - private final @NotNull MenuItemOptions options; + private final DeluxeMenus plugin; + private final MenuItemOptions options; - public MenuItem(@NotNull final MenuItemOptions options) { + public MenuItem(@NotNull final DeluxeMenus plugin, @NotNull final MenuItemOptions options) { + this.plugin = plugin; this.options = options; } @@ -88,7 +90,7 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { final int temporaryAmount = amount; final String finalMaterial = lowercaseStringMaterial; - final ItemHook pluginHook = DeluxeMenus.getInstance().getItemHooks().values() + final ItemHook pluginHook = plugin.getItemHooks().values() .stream() .filter(x -> finalMaterial.startsWith(x.getPrefix())) .findFirst() @@ -106,7 +108,7 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { if (itemStack == null) { final Material material = Material.getMaterial(stringMaterial.toUpperCase(Locale.ROOT)); if (material == null) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Material: " + stringMaterial + " is not valid! Setting to Stone." @@ -188,7 +190,7 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { } } } catch (final NumberFormatException exception) { - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Invalid damage found: " + parsedDamage + ".", exception ); @@ -275,7 +277,7 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { try { itemMeta.setRarity(ItemRarity.valueOf(rarity.toUpperCase())); } catch (IllegalArgumentException e) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Rarity " + rarity + " is not a valid!" @@ -309,7 +311,7 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { itemStack.setItemMeta(armorMeta); } else { if (trimMaterial == null) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Trim material " + trimMaterialName.get() + " is not a valid!" @@ -317,7 +319,7 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { } if (trimPattern == null) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Trim pattern " + trimPatternName.get() + " is not a valid!" @@ -325,13 +327,13 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { } } } else if (trimMaterialName.isPresent()) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Trim pattern is not set for item with trim material " + trimMaterialName.get() ); } else if (trimPatternName.isPresent()) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Trim material is not set for item with trim pattern " + trimPatternName.get() @@ -349,7 +351,7 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { Integer.parseInt(parts[2].trim()))); itemStack.setItemMeta(leatherArmorMeta); } catch (final Exception exception) { - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Invalid rgb colors found for leather armor: " + parts[0].trim() + ", " + parts[1].trim() + ", " + parts[2].trim(), exception @@ -365,7 +367,7 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { Integer.parseInt(parts[1].trim()), Integer.parseInt(parts[2].trim()))).build()); itemStack.setItemMeta(fireworkEffectMeta); } catch (final Exception exception) { - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Invalid rgb colors found for firework or firework star: " + parts[0].trim() + ", " + parts[1].trim() + ", " + parts[2].trim(), exception @@ -376,7 +378,7 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { for (final Map.Entry entry : this.options.enchantments().entrySet()) { final boolean result = enchantmentStorageMeta.addStoredEnchant(entry.getKey(), entry.getValue(), true); if (!result) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.INFO, "Failed to add enchantment " + entry.getKey().getName() + " to item " + itemStack.getType() @@ -402,14 +404,14 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { final int lightLevel = Math.min(Integer.parseInt(parsedLightLevel), light.getMaximumLevel()); light.setLevel(Math.max(lightLevel, 0)); if (lightLevel < 0) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.MEDIUM, Level.WARNING, "Invalid light level found for light block: " + parsedLightLevel + ". Setting to 0." ); } if (lightLevel > light.getMaximumLevel()) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.MEDIUM, Level.WARNING, "Invalid light level found for light block: " + parsedLightLevel + ". Setting to " + light.getMaximumLevel() + "." @@ -419,7 +421,7 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { blockDataMeta.setBlockData(light); itemStack.setItemMeta(blockDataMeta); } catch (final Exception exception) { - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Invalid light level found for light block: " + parsedLightLevel, exception ); @@ -528,9 +530,7 @@ private boolean isHeadItem(@NotNull final String material) { } private @NotNull Optional getItemFromHook(String hookName, String... args) { - return DeluxeMenus.getInstance() - .getItemHook(hookName) - .map(itemHook -> itemHook.getItem(args)); + return plugin.getItemHook(hookName).map(itemHook -> itemHook.getItem(args)); } private List getMenuItemLore(@NotNull final MenuHolder holder, @NotNull final List lore) { diff --git a/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java b/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java index 366bb06a..45768932 100644 --- a/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java +++ b/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java @@ -16,6 +16,13 @@ import org.jetbrains.annotations.Nullable; public class PersistentMetaHandler { + + private final DeluxeMenus plugin; + + public PersistentMetaHandler(@NotNull final DeluxeMenus plugin) { + this.plugin = plugin; + } + /** * Get a {@link PersistentDataType} from its name. * @@ -50,7 +57,7 @@ public class PersistentMetaHandler { final String[] split = key.split(":", 2); namespacedKey = new NamespacedKey(split[0], split[1]); } else { - namespacedKey = new NamespacedKey(DeluxeMenus.getInstance(), key); + namespacedKey = new NamespacedKey(plugin, key); } return namespacedKey; @@ -75,7 +82,7 @@ public class PersistentMetaHandler { try { namespacedKey = getKey(key); } catch (final IllegalArgumentException e) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Failed to get meta value for player " + player.getName() + " with key '" + key + "' and type '" + typeName.toUpperCase(Locale.ROOT) + "'. Reason: " + e.getMessage() @@ -92,7 +99,7 @@ public class PersistentMetaHandler { try { result = player.getPersistentDataContainer().get(namespacedKey, type); } catch (final IllegalArgumentException e) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Failed to get meta value for player " + player.getName() + " with key '" + key + "' and type '" + typeName.toUpperCase(Locale.ROOT) + "'. Reason: Saved tag can not be converted to type: " + typeName.toUpperCase(Locale.ROOT) @@ -136,7 +143,7 @@ public boolean setMeta(@NotNull final Player player, @NotNull final String input try { namespacedKey = getKey(args[1]); } catch (final IllegalArgumentException e) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Failed to set meta value for player " + player.getName() + " with key '" + args[1] + "' and type '" + args[2].toUpperCase(Locale.ROOT) + "'. Reason: " + e.getMessage() diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/HasExpRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/HasExpRequirement.java index e5271485..275a0a10 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/HasExpRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/HasExpRequirement.java @@ -3,14 +3,17 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.menu.MenuHolder; import com.extendedclip.deluxemenus.utils.ExpUtils; +import org.jetbrains.annotations.NotNull; public class HasExpRequirement extends Requirement { + private final DeluxeMenus plugin; private final boolean invert; private final boolean level; private final String amt; - public HasExpRequirement(String amt, boolean invert, boolean level) { + public HasExpRequirement(@NotNull final DeluxeMenus plugin, String amt, boolean invert, boolean level) { + this.plugin = plugin; this.amt = amt; this.invert = invert; this.level = level; @@ -23,7 +26,7 @@ public boolean evaluate(MenuHolder holder) { try { amount = Integer.parseInt(holder.setPlaceholdersAndArguments(amt)); } catch (final Exception exception) { - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Invalid amount found for has exp requirement: " + holder.setPlaceholdersAndArguments(amt), exception ); diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java index 8fa9f375..97b3d965 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java @@ -14,10 +14,12 @@ public class HasItemRequirement extends Requirement { + private final DeluxeMenus plugin; private final ItemWrapper wrapper; private final boolean invert; - public HasItemRequirement(ItemWrapper wrapper, boolean invert) { + public HasItemRequirement(final DeluxeMenus plugin, final ItemWrapper wrapper, final boolean invert) { + this.plugin = plugin; this.wrapper = wrapper; this.invert = invert; } @@ -28,7 +30,7 @@ public boolean evaluate(MenuHolder holder) { Material material = DeluxeMenus.MATERIALS.get(materialName.toUpperCase()); ItemHook pluginHook = null; if (material == null) { - pluginHook = DeluxeMenus.getInstance().getItemHooks().values() + pluginHook = plugin.getItemHooks().values() .stream() .filter(x -> materialName.toLowerCase().startsWith(x.getPrefix())) .findFirst() diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/HasMetaRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/HasMetaRequirement.java index af9d9c88..c8f1108b 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/HasMetaRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/HasMetaRequirement.java @@ -3,15 +3,18 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.menu.MenuHolder; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; public class HasMetaRequirement extends Requirement { + private final DeluxeMenus plugin; private final String key; private final String value; private final String type; private final boolean invert; - public HasMetaRequirement(String key, String type, String value, boolean invert) { + public HasMetaRequirement(@NotNull final DeluxeMenus plugin, String key, String type, String value, boolean invert) { + this.plugin = plugin; this.key = key; this.type = type.toUpperCase(); this.value = value; @@ -25,7 +28,7 @@ public boolean evaluate(MenuHolder holder) { return false; } String parsedKey = holder.setPlaceholdersAndArguments(key); - String metaVal = DeluxeMenus.getInstance().getPersistentMetaHandler() + String metaVal = plugin.getPersistentMetaHandler() .getMeta(player, parsedKey, type, null); if (metaVal == null) { return invert; diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/HasMoneyRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/HasMoneyRequirement.java index 9aae814b..81114b64 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/HasMoneyRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/HasMoneyRequirement.java @@ -2,14 +2,17 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.menu.MenuHolder; +import org.jetbrains.annotations.NotNull; public class HasMoneyRequirement extends Requirement { + private final DeluxeMenus plugin; private final boolean invert; private final String placeholder; private double amount; - public HasMoneyRequirement(double amount, boolean invert, String placeholder) { + public HasMoneyRequirement(@NotNull final DeluxeMenus plugin, double amount, boolean invert, String placeholder) { + this.plugin = plugin; this.amount = amount; this.invert = invert; this.placeholder = placeholder; @@ -17,7 +20,7 @@ public HasMoneyRequirement(double amount, boolean invert, String placeholder) { @Override public boolean evaluate(MenuHolder holder) { - if (getInstance().getVault() == null) { + if (plugin.getVault() == null) { return false; } @@ -26,16 +29,16 @@ public boolean evaluate(MenuHolder holder) { String expected = holder.setPlaceholdersAndArguments(placeholder); amount = Double.parseDouble(expected); } catch (final NumberFormatException exception) { - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Invalid amount found for has money requirement: " + holder.setPlaceholdersAndArguments(placeholder), exception ); } } if (invert) { - return !getInstance().getVault().hasEnough(holder.getViewer(), amount); + return !plugin.getVault().hasEnough(holder.getViewer(), amount); } else { - return getInstance().getVault().hasEnough(holder.getViewer(), amount); + return plugin.getVault().hasEnough(holder.getViewer(), amount); } } } diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/InputResultRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/InputResultRequirement.java index 3a372794..29ef2b93 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/InputResultRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/InputResultRequirement.java @@ -1,6 +1,5 @@ package com.extendedclip.deluxemenus.requirement; -import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.menu.MenuHolder; public class InputResultRequirement extends Requirement { @@ -44,7 +43,7 @@ public boolean evaluate(MenuHolder holder) { try { in = Double.parseDouble(parsedInput); } catch (final NumberFormatException exception) { - DeluxeMenus.printStacktrace( + holder.getPlugin().printStacktrace( "Input for comparison requirement is an invalid number: " + parsedInput, exception ); @@ -54,7 +53,7 @@ public boolean evaluate(MenuHolder holder) { try { res = Double.parseDouble(parsedResult); } catch (final NumberFormatException exception) { - DeluxeMenus.printStacktrace( + holder.getPlugin().printStacktrace( "Output for comparison requirement is an invalid number: " + parsedResult, exception ); diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/IsObjectRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/IsObjectRequirement.java index da1d2f8b..467908fa 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/IsObjectRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/IsObjectRequirement.java @@ -1,6 +1,5 @@ package com.extendedclip.deluxemenus.requirement; -import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.menu.MenuHolder; import com.extendedclip.deluxemenus.utils.DebugLevel; import com.google.common.primitives.Doubles; @@ -44,7 +43,7 @@ public boolean evaluate(MenuHolder holder) { return false; } default: - DeluxeMenus.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid object: " + object + " in \"is object\" check."); + holder.getPlugin().debug(DebugLevel.HIGHEST, Level.INFO, "Invalid object: " + object + " in \"is object\" check."); return false; } } diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/JavascriptRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/JavascriptRequirement.java index dc0b06b4..56a67760 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/JavascriptRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/JavascriptRequirement.java @@ -11,6 +11,7 @@ import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.ServicePriority; import org.bukkit.plugin.ServicesManager; +import org.jetbrains.annotations.NotNull; import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory; public class JavascriptRequirement extends Requirement { @@ -18,9 +19,11 @@ public class JavascriptRequirement extends Requirement { private final ScriptEngineFactory factory = new NashornScriptEngineFactory(); private final ServicesManager manager = Bukkit.getServer().getServicesManager(); private static ScriptEngineManager engine; + private final DeluxeMenus plugin; private final String expression; - public JavascriptRequirement(String expression) { + public JavascriptRequirement(final @NotNull DeluxeMenus plugin, String expression) { + this.plugin = plugin; this.expression = expression; if (engine == null) { if (manager.isProvidedFor(ScriptEngineManager.class)) { @@ -28,7 +31,7 @@ public JavascriptRequirement(String expression) { engine = (ScriptEngineManager) provider.getProvider(); } else { engine = new ScriptEngineManager(); - manager.register(ScriptEngineManager.class, engine, DeluxeMenus.getInstance(), ServicePriority.Highest); + manager.register(ScriptEngineManager.class, engine, plugin, ServicePriority.Highest); } engine.registerEngineName("JavaScript", factory); engine.put("BukkitServer", Bukkit.getServer()); @@ -45,7 +48,7 @@ public boolean evaluate(MenuHolder holder) { Object result = engine.getEngineByName("JavaScript").eval(exp); if (!(result instanceof Boolean)) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Requirement javascript <" + this.expression + "> is invalid and does not return a boolean!" @@ -56,13 +59,13 @@ public boolean evaluate(MenuHolder holder) { return (boolean) result; } catch (final ScriptException | NullPointerException exception) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Error in requirement javascript syntax - " + this.expression ); - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Error in requirement javascript syntax - " + this.expression, exception ); diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/Requirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/Requirement.java index 7c49198a..0f065ff4 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/Requirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/Requirement.java @@ -1,6 +1,5 @@ package com.extendedclip.deluxemenus.requirement; -import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.action.ClickHandler; import com.extendedclip.deluxemenus.menu.MenuHolder; @@ -40,10 +39,6 @@ public void setOptional(boolean optional) { this.optional = optional; } - public DeluxeMenus getInstance() { - return DeluxeMenus.getInstance(); - } - public ClickHandler getSuccessHandler() { return successHandler; } diff --git a/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java b/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java index 541a8f41..e30fe77b 100644 --- a/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java +++ b/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java @@ -1,6 +1,7 @@ package com.extendedclip.deluxemenus.updatechecker; import com.extendedclip.deluxemenus.DeluxeMenus; +import com.extendedclip.deluxemenus.listener.Listener; import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.Messages; import java.io.BufferedReader; @@ -10,16 +11,14 @@ import java.util.logging.Level; import java.util.regex.Pattern; import net.kyori.adventure.text.TextReplacementConfig; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.scheduler.BukkitRunnable; import org.jetbrains.annotations.NotNull; -public class UpdateChecker implements Listener { +public class UpdateChecker extends Listener { private static final TextReplacementConfig.Builder LATEST_VERSION_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); @@ -27,12 +26,11 @@ public class UpdateChecker implements Listener { = TextReplacementConfig.builder().matchLiteral(""); final int resourceId = 11734; - private final DeluxeMenus plugin; private String latestVersion = null; private boolean updateAvailable = false; public UpdateChecker(final @NotNull DeluxeMenus instance) { - plugin = instance; + super(instance); new BukkitRunnable() { @Override @@ -51,10 +49,6 @@ public void run() { }.runTaskAsynchronously(plugin); } - private void register() { - Bukkit.getPluginManager().registerEvents(this, plugin); - } - @EventHandler(priority = EventPriority.MONITOR) public void onJoin(final @NotNull PlayerJoinEvent event) { Player player = event.getPlayer(); @@ -85,7 +79,7 @@ private String getSpigotVersion() { connection.setRequestMethod("GET"); return new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine(); } catch (Exception ex) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGH, Level.INFO, "Failed to check for update on spigot!" diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java index dff45571..c4773ff9 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/AdventureUtils.java @@ -4,6 +4,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; public final class AdventureUtils { private final static GsonComponentSerializer gson = GsonComponentSerializer.gson(); @@ -12,8 +13,8 @@ private AdventureUtils() { throw new AssertionError("Util classes should not be initialized"); } - public static void sendJson(CommandSender sender, String json) { - DeluxeMenus.getInstance().adventure().sender(sender).sendMessage(fromJson(json)); + public static void sendJson(@NotNull final DeluxeMenus plugin, CommandSender sender, String json) { + plugin.audiences().sender(sender).sendMessage(fromJson(json)); } public static Component fromJson(String json) { diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/DumpUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/DumpUtils.java index 53a037e1..1b70d88b 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/DumpUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/DumpUtils.java @@ -119,7 +119,7 @@ private static boolean createMenuDump( final var guiMenus = config.getConfigurationSection("gui_menus"); if (guiMenus == null) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "No gui_menus section found in config.yml!" @@ -131,7 +131,7 @@ private static boolean createMenuDump( final Set keys = guiMenus.getKeys(false); if (!keys.contains(menuName)) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "File for the " + menuName + " menu is not declared in config.yml!" @@ -143,7 +143,7 @@ private static boolean createMenuDump( final String fileName = plugin.getConfig().getString("gui_menus." + menuName + ".file"); if (fileName == null) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "File for the " + menuName + " menu is not declared in config.yml!" @@ -153,7 +153,7 @@ private static boolean createMenuDump( } if (!fileName.endsWith(".yml")) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "File for the " + menuName + " menu is not declared in config.yml!" @@ -173,7 +173,7 @@ private static boolean createMenuDump( final var menuFile = new File(plugin.getConfiguration().getMenuDirector(), fileName); if (!menuFile.exists() || !menuFile.isFile()) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Could not find the " + fileName + " file in " + @@ -188,13 +188,13 @@ private static boolean createMenuDump( builder.append(line).append(System.lineSeparator()) ); } catch (final IOException exception) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Something went wrong while reading the the file: " + fileName ); - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Something went wrong while reading the the file: " + fileName, exception ); @@ -211,7 +211,7 @@ private static boolean createConfigDump( final File configFile = new File(plugin.getDataFolder(), "config.yml"); if (!configFile.exists() || !configFile.isFile()) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Could not find the " + configFile + " file in " + plugin.getDataFolder().getPath() @@ -230,13 +230,13 @@ private static boolean createConfigDump( builder.append(line).append(System.lineSeparator()) ); } catch (final IOException exception) { - DeluxeMenus.debug( + plugin.debug( DebugLevel.HIGHEST, Level.WARNING, "Something went wrong while reading the the file: " + configFile ); - DeluxeMenus.printStacktrace( + plugin.printStacktrace( "Something went wrong while reading the the file: " + configFile, exception ); diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java index 428e5cf2..6795a62c 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java @@ -6,12 +6,6 @@ import com.google.gson.JsonObject; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; -import java.lang.reflect.Field; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Base64; -import java.util.UUID; - import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.inventory.ItemStack; @@ -21,213 +15,222 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class SkullUtils { - - private static final Gson GSON = new Gson(); - - /** - * Helper method to get the encoded bytes for a full MC Texture - * - * @param url the url of the texture - * @return fully encoded texture url - */ - @NotNull - public static String getEncoded(@NotNull final String url) { - final byte[] encodedData = Base64.getEncoder().encode(String - .format("{textures:{SKIN:{url:\"%s\"}}}", "https://textures.minecraft.net/texture/" + url) - .getBytes()); - return new String(encodedData); - } - - /** - * Get the skull from a base64 encoded texture url - * - * @param base64Url base64 encoded url to use - * @return skull - */ - @NotNull - public static ItemStack getSkullByBase64EncodedTextureUrl(@NotNull final String base64Url) { - final ItemStack head = DeluxeMenus.getInstance().getHead().clone(); - if (base64Url.isEmpty()) { - return head; - } - - final SkullMeta headMeta = (SkullMeta) head.getItemMeta(); - if (headMeta == null) { - return head; - } - - if (VersionHelper.HAS_PLAYER_PROFILES) { - final PlayerProfile profile = getPlayerProfile(base64Url); - headMeta.setOwnerProfile(profile); - head.setItemMeta(headMeta); - return head; - } - - final GameProfile profile = getGameProfile(base64Url); - final Field profileField; - try { - profileField = headMeta.getClass().getDeclaredField("profile"); - profileField.setAccessible(true); - profileField.set(headMeta, profile); - } catch (final NoSuchFieldException | IllegalArgumentException | IllegalAccessException exception) { - DeluxeMenus.printStacktrace( - "Failed to get head item from base64 texture url", - exception - ); - } - head.setItemMeta(headMeta); - return head; - } - - public static String getTextureFromSkull(ItemStack item) { - if (!(item.getItemMeta() instanceof SkullMeta)) return null; - SkullMeta meta = (SkullMeta) item.getItemMeta(); - - if (VersionHelper.HAS_PLAYER_PROFILES) { - PlayerProfile profile = meta.getOwnerProfile(); - if (profile == null) return null; - - URL url = profile.getTextures().getSkin(); - if (url == null) return null; +import java.lang.reflect.Field; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Base64; +import java.util.UUID; - return url.toString().substring("https://textures.minecraft.net/texture/".length()-1); - } +public class SkullUtils { - GameProfile profile; - try { - final Field profileField = meta.getClass().getDeclaredField("profile"); - profileField.setAccessible(true); - profile = (GameProfile) profileField.get(meta); - } catch (final NoSuchFieldException | IllegalArgumentException | IllegalAccessException exception) { - DeluxeMenus.printStacktrace( - "Failed to get base64 texture url from head item", - exception - ); - return null; + private static final Gson GSON = new Gson(); + + /** + * Helper method to get the encoded bytes for a full MC Texture + * + * @param url the url of the texture + * @return fully encoded texture url + */ + @NotNull + public static String getEncoded(@NotNull final String url) { + final byte[] encodedData = Base64.getEncoder().encode(String + .format("{textures:{SKIN:{url:\"%s\"}}}", "https://textures.minecraft.net/texture/" + url) + .getBytes()); + return new String(encodedData); } - for (Property property : profile.getProperties().get("textures")) { - if (property.getName().equals("textures")) { - return decodeSkinUrl(property.getValue()); - } - } - return null; - } - - - /** - * Get the skull from a player name - * @param playerName the player name to use - * @return skull - */ - @NotNull - public static ItemStack getSkullByName(@NotNull final String playerName) { - final ItemStack head = DeluxeMenus.getInstance().getHead().clone(); - if (playerName.isEmpty()) { - return head; + /** + * Get the skull from a base64 encoded texture url + * + * @param base64Url base64 encoded url to use + * @return skull + */ + @NotNull + public static ItemStack getSkullByBase64EncodedTextureUrl(@NotNull final DeluxeMenus plugin, @NotNull final String base64Url) { + final ItemStack head = plugin.getHead().clone(); + if (base64Url.isEmpty()) { + return head; + } + + final SkullMeta headMeta = (SkullMeta) head.getItemMeta(); + if (headMeta == null) { + return head; + } + + if (VersionHelper.HAS_PLAYER_PROFILES) { + final PlayerProfile profile = getPlayerProfile(plugin, base64Url); + headMeta.setOwnerProfile(profile); + head.setItemMeta(headMeta); + return head; + } + + final GameProfile profile = getGameProfile(base64Url); + final Field profileField; + try { + profileField = headMeta.getClass().getDeclaredField("profile"); + profileField.setAccessible(true); + profileField.set(headMeta, profile); + } catch (final NoSuchFieldException | IllegalArgumentException | IllegalAccessException exception) { + plugin.printStacktrace( + "Failed to get head item from base64 texture url", + exception + ); + } + head.setItemMeta(headMeta); + return head; } - final SkullMeta headMeta = (SkullMeta) head.getItemMeta(); - if (headMeta == null) { - return head; + public static String getTextureFromSkull(final DeluxeMenus plugin, ItemStack item) { + if (!(item.getItemMeta() instanceof SkullMeta)) return null; + SkullMeta meta = (SkullMeta) item.getItemMeta(); + + if (VersionHelper.HAS_PLAYER_PROFILES) { + PlayerProfile profile = meta.getOwnerProfile(); + if (profile == null) return null; + + URL url = profile.getTextures().getSkin(); + if (url == null) return null; + + return url.toString().substring("https://textures.minecraft.net/texture/".length() - 1); + } + + GameProfile profile; + try { + final Field profileField = meta.getClass().getDeclaredField("profile"); + profileField.setAccessible(true); + profile = (GameProfile) profileField.get(meta); + } catch (final NoSuchFieldException | IllegalArgumentException | IllegalAccessException exception) { + plugin.printStacktrace( + "Failed to get base64 texture url from head item", + exception + ); + return null; + } + + for (Property property : profile.getProperties().get("textures")) { + if (property.getName().equals("textures")) { + return decodeSkinUrl(property.getValue()); + } + } + return null; } - final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerName); - if (!VersionHelper.IS_SKULL_OWNER_LEGACY) { - headMeta.setOwningPlayer(offlinePlayer); - } else { - headMeta.setOwner(offlinePlayer.getName()); + /** + * Get the skull from a player name + * + * @param playerName the player name to use + * @return skull + */ + @NotNull + public static ItemStack getSkullByName(@NotNull final DeluxeMenus plugin, @NotNull final String playerName) { + final ItemStack head = plugin.getHead().clone(); + if (playerName.isEmpty()) { + return head; + } + + final SkullMeta headMeta = (SkullMeta) head.getItemMeta(); + if (headMeta == null) { + return head; + } + + final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(playerName); + + if (!VersionHelper.IS_SKULL_OWNER_LEGACY) { + headMeta.setOwningPlayer(offlinePlayer); + } else { + headMeta.setOwner(offlinePlayer.getName()); + } + + head.setItemMeta(headMeta); + return head; } - head.setItemMeta(headMeta); - return head; - } + public static String getSkullOwner(ItemStack skull) { + if (skull == null || !(skull.getItemMeta() instanceof SkullMeta)) return null; + SkullMeta meta = (SkullMeta) skull.getItemMeta(); - public static String getSkullOwner(ItemStack skull) { - if (skull == null || !(skull.getItemMeta() instanceof SkullMeta)) return null; - SkullMeta meta = (SkullMeta) skull.getItemMeta(); + if (!VersionHelper.IS_SKULL_OWNER_LEGACY) { + if (meta.getOwningPlayer() == null) return null; + return meta.getOwningPlayer().getName(); + } - if (!VersionHelper.IS_SKULL_OWNER_LEGACY) { - if (meta.getOwningPlayer() == null) return null; - return meta.getOwningPlayer().getName(); + return meta.getOwner(); } - return meta.getOwner(); - } - - /** - * Create a game profile object - * - * @param base64Url the base64 encoded texture url to use - * @return game profile - */ - @NotNull - private static GameProfile getGameProfile(@NotNull final String base64Url) { - GameProfile profile = new GameProfile(UUID.randomUUID(), ""); - profile.getProperties().put("textures", new Property("textures", base64Url)); - return profile; - } - - /** - * Create a player profile object - * Player profile was introduced in 1.18.1+ - * @param base64Url the base64 encoded texture URL to use - * @return player profile - */ - @NotNull - private static PlayerProfile getPlayerProfile(@NotNull final String base64Url) { - final PlayerProfile profile = Bukkit.createPlayerProfile(UUID.randomUUID()); - - final String decodedBase64 = decodeSkinUrl(base64Url); - if (decodedBase64 == null) { - return profile; + /** + * Create a game profile object + * + * @param base64Url the base64 encoded texture url to use + * @return game profile + */ + @NotNull + private static GameProfile getGameProfile(@NotNull final String base64Url) { + GameProfile profile = new GameProfile(UUID.randomUUID(), ""); + profile.getProperties().put("textures", new Property("textures", base64Url)); + return profile; } - final PlayerTextures textures = profile.getTextures(); - - try { - textures.setSkin(new URL(decodedBase64)); - } catch (final MalformedURLException exception) { - DeluxeMenus.printStacktrace("Something went horribly wrong trying to create basehead URL", exception); + /** + * Create a player profile object + * Player profile was introduced in 1.18.1+ + * + * @param base64Url the base64 encoded texture URL to use + * @return player profile + */ + @NotNull + private static PlayerProfile getPlayerProfile(@NotNull final DeluxeMenus plugin, @NotNull final String base64Url) { + final PlayerProfile profile = Bukkit.createPlayerProfile(UUID.randomUUID()); + + final String decodedBase64 = decodeSkinUrl(base64Url); + if (decodedBase64 == null) { + return profile; + } + + final PlayerTextures textures = profile.getTextures(); + + try { + textures.setSkin(new URL(decodedBase64)); + } catch (final MalformedURLException exception) { + plugin.printStacktrace("Something went horribly wrong trying to create basehead URL", exception); + } + + profile.setTextures(textures); + return profile; } - profile.setTextures(textures); - return profile; - } - - /** - * Decode a base64 string and extract the url of the skin. Example: - *
- * - Base64: {@code eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGNlYjE3MDhkNTQwNGVmMzI2MTAzZTdiNjA1NTljOTE3OGYzZGNlNzI5MDA3YWM5YTBiNDk4YmRlYmU0NjEwNyJ9fX0=} - *
- * - JSON: {@code {"textures":{"SKIN":{"url":"http://textures.minecraft.net/texture/dceb1708d5404ef326103e7b60559c9178f3dce729007ac9a0b498bdebe46107"}}}} - *
- * - Result: {@code http://textures.minecraft.net/texture/dceb1708d5404ef326103e7b60559c9178f3dce729007ac9a0b498bdebe46107} - *
- * Credit: iGabyTM - * @param base64Texture the texture - * @return the url of the texture if found, otherwise {@code null} - */ - @Nullable - public static String decodeSkinUrl(@NotNull final String base64Texture) { - final String decoded = new String(Base64.getDecoder().decode(base64Texture)); - final JsonObject object = GSON.fromJson(decoded, JsonObject.class); - - final JsonElement textures = object.get("textures"); - - if (textures == null) { - return null; + /** + * Decode a base64 string and extract the url of the skin. Example: + *
+ * - Base64: {@code eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGNlYjE3MDhkNTQwNGVmMzI2MTAzZTdiNjA1NTljOTE3OGYzZGNlNzI5MDA3YWM5YTBiNDk4YmRlYmU0NjEwNyJ9fX0=} + *
+ * - JSON: {@code {"textures":{"SKIN":{"url":"http://textures.minecraft.net/texture/dceb1708d5404ef326103e7b60559c9178f3dce729007ac9a0b498bdebe46107"}}}} + *
+ * - Result: {@code http://textures.minecraft.net/texture/dceb1708d5404ef326103e7b60559c9178f3dce729007ac9a0b498bdebe46107} + *
+ * Credit: iGabyTM + * + * @param base64Texture the texture + * @return the url of the texture if found, otherwise {@code null} + */ + @Nullable + public static String decodeSkinUrl(@NotNull final String base64Texture) { + final String decoded = new String(Base64.getDecoder().decode(base64Texture)); + final JsonObject object = GSON.fromJson(decoded, JsonObject.class); + + final JsonElement textures = object.get("textures"); + + if (textures == null) { + return null; + } + + final JsonElement skin = textures.getAsJsonObject().get("SKIN"); + + if (skin == null) { + return null; + } + + final JsonElement url = skin.getAsJsonObject().get("url"); + return url == null ? null : url.getAsString(); } - - final JsonElement skin = textures.getAsJsonObject().get("SKIN"); - - if (skin == null) { - return null; - } - - final JsonElement url = skin.getAsJsonObject().get("url"); - return url == null ? null : url.getAsString(); - } } From cf907d4e4f4ca7f99dbc7b320e78488b5fc4f599 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Thu, 2 Jan 2025 16:39:13 +0200 Subject: [PATCH 20/25] fixed index out of bounds issue --- .../deluxemenus/command/subcommand/OpenCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/OpenCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/OpenCommand.java index 9e83eaab..224c0bb6 100644 --- a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/OpenCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/OpenCommand.java @@ -94,7 +94,7 @@ public void execute(final @NotNull CommandSender sender, final @NotNull List Date: Sun, 12 Jan 2025 07:26:00 +0200 Subject: [PATCH 21/25] Fix plugin reload only reloading main command. - Fixed reload of registered menu commands - Upgraded gradle and gradle plugins - Upgraded libraries --- build.gradle.kts | 8 +- gradle/libs.versions.toml | 20 +- gradle/wrapper/gradle-wrapper.jar | Bin 63721 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 16 +- gradlew.bat | 20 +- .../deluxemenus/config/DeluxeMenusConfig.java | 2 +- .../extendedclip/deluxemenus/menu/Menu.java | 137 ++----------- .../deluxemenus/menu/MenuItem.java | 1 + .../menu/command/RegistrableMenuCommand.java | 181 ++++++++++++++++++ .../menu/{ => options}/LoreAppendMode.java | 2 +- .../menu/options/MenuItemOptions.java | 1 - 12 files changed, 240 insertions(+), 150 deletions(-) create mode 100644 src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java rename src/main/java/com/extendedclip/deluxemenus/menu/{ => options}/LoreAppendMode.java (60%) diff --git a/build.gradle.kts b/build.gradle.kts index fea4ebfa..2b9d2bd9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,8 @@ plugins { java - id("com.github.johnrengelman.shadow") version("8.1.1") - id("com.github.ben-manes.versions") version "0.48.0" + id("com.gradleup.shadow") version("8.3.5") + id("com.github.ben-manes.versions") version("0.51.0") +// id("io.papermc.paperweight.userdev") version("2.0.0-beta.13") } // Change to true when releasing @@ -18,6 +19,7 @@ repositories { maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") maven("https://repo.glaremasters.me/repository/public/") maven("https://nexus.phoenixdevt.fr/repository/maven-public/") + maven("https://repo.oraxen.com/releases") maven("https://jitpack.io") } @@ -41,6 +43,8 @@ dependencies { implementation(libs.adventure.minimessage) compileOnly("org.jetbrains:annotations:23.0.0") + +// paperweight.paperDevBundle("1.21.3-R0.1-SNAPSHOT") } tasks { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e06b3db8..6e4d66a1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,20 +1,20 @@ [versions] # Compile only -spigot = "1.21.3-R0.1-SNAPSHOT" +spigot = "1.21.4-R0.1-SNAPSHOT" vault = "1.7.1" authlib = "1.5.25" -headdb = "1.3.1" -itemsadder = "3.2.5" -oraxen = "1.159.0" -mythiclib = "1.6.2-SNAPSHOT" -mmoitems = "6.9.5-SNAPSHOT" +headdb = "1.3.2" +itemsadder = "3.6.3-beta-14" +oraxen = "1.187.0" +mythiclib = "1.7.1-SNAPSHOT" +mmoitems = "6.10-SNAPSHOT" papi = "2.11.6" -score = "4.23.10.8" +score = "4.24.3.5" # Implementation -nashorn = "15.4" +nashorn = "15.6" adventure-platform = "4.3.4" -adventure-minimessage = "4.17.0" +adventure-minimessage = "4.18.0" [libraries] # Compile only @@ -23,7 +23,7 @@ vault = { module = "com.github.milkbowl:VaultAPI", version.ref = "vault" } authlib = { module = "com.mojang:authlib", version.ref = "authlib" } headdb = { module = "com.arcaniax:HeadDatabase-API", version.ref = "headdb" } itemsadder = { module = "com.github.LoneDev6:api-itemsadder", version.ref = "itemsadder" } -oraxen = { module = "com.github.oraxen:oraxen", version.ref = "oraxen" } +oraxen = { module = "io.th0rgal:oraxen", version.ref = "oraxen" } mythiclib = { module = "io.lumine:MythicLib-dist", version.ref = "mythiclib"} mmoitems = { module = "net.Indyuce:MMOItems-API", version.ref = "mmoitems" } papi = { module = "me.clip:placeholderapi", version.ref = "papi" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c49b765f8051ef9d0a6055ff8e46073d8..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4413138..cea7a793 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1a..b740cf13 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85b..7101f8e4 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index 16a79389..e2bdfa6c 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -6,7 +6,7 @@ import com.extendedclip.deluxemenus.action.ClickActionTask; import com.extendedclip.deluxemenus.action.ClickHandler; import com.extendedclip.deluxemenus.hooks.ItemHook; -import com.extendedclip.deluxemenus.menu.LoreAppendMode; +import com.extendedclip.deluxemenus.menu.options.LoreAppendMode; import com.extendedclip.deluxemenus.menu.Menu; import com.extendedclip.deluxemenus.menu.MenuHolder; import com.extendedclip.deluxemenus.menu.MenuItem; diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java index 2de43f5d..23d776e2 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java @@ -1,22 +1,17 @@ package com.extendedclip.deluxemenus.menu; import com.extendedclip.deluxemenus.DeluxeMenus; +import com.extendedclip.deluxemenus.menu.command.RegistrableMenuCommand; import com.extendedclip.deluxemenus.menu.options.MenuOptions; import com.extendedclip.deluxemenus.requirement.RequirementList; import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.StringUtils; -import java.lang.reflect.Field; import java.util.*; import java.util.Map.Entry; import java.util.logging.Level; -import me.clip.placeholderapi.util.Msg; import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandMap; -import org.bukkit.command.CommandSender; -import org.bukkit.command.SimpleCommandMap; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; @@ -24,12 +19,11 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class Menu extends Command { +public class Menu { private static final Map menus = new HashMap<>(); private static final Set menuHolders = new HashSet<>(); private static final Map lastOpenedMenus = new HashMap<>(); - private static CommandMap commandMap = null; private final DeluxeMenus plugin; private final MenuOptions options; @@ -37,26 +31,24 @@ public class Menu extends Command { // menu path starting from the plugin directory private final String path; + private RegistrableMenuCommand command = null; + public Menu( final @NotNull DeluxeMenus plugin, final @NotNull MenuOptions options, final @NotNull Map> items, final @NotNull String path ) { - super(options.commands().isEmpty() ? options.name() : options.commands().get(0)); - this.plugin = plugin; this.options = options; this.items = items; this.path = path; if (this.options.registerCommands()) { - if (this.options.commands().size() > 1) { - this.setAliases(this.options.commands().subList(1, this.options.commands().size())); - } - - addCommand(); + this.command = new RegistrableMenuCommand(plugin, this); + this.command.register(); } + menus.put(this.options.name(), this); } @@ -67,12 +59,12 @@ public static void unload(final @NotNull DeluxeMenus plugin, final @NotNull Stri } } - Optional menu = Menu.getMenuByName(name); - if (menu.isEmpty()) { + Optional optionalMenu = Menu.getMenuByName(name); + if (optionalMenu.isEmpty()) { return; } - menu.get().removeCommand(); + optionalMenu.get().unregisterCommand(); menus.remove(name); } @@ -83,13 +75,23 @@ public static void unload(final @NotNull DeluxeMenus plugin) { } } for (Menu menu : Menu.getAllMenus()) { - menu.removeCommand(); + menu.unregisterCommand(); } menus.clear(); menuHolders.clear(); lastOpenedMenus.clear(); } + private void unregisterCommand() { + if (this.command != null) { + this.command.unregister(); + } + + // WARNING! A reference to the command is stored by CraftBukkit for their `/help` command. There is currently + // no way to remove this reference! + this.command = null; + } + public static void unloadForShutdown(final @NotNull DeluxeMenus plugin) { for (Player player : Bukkit.getOnlinePlayers()) { if (isInMenu(player)) { @@ -208,103 +210,6 @@ public static void closeMenu(final @NotNull DeluxeMenus plugin, final @NotNull P closeMenu(plugin, player, close, false); } - private void addCommand() { - if (commandMap == null) { - try { - final Field f = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - f.setAccessible(true); - commandMap = (CommandMap) f.get(Bukkit.getServer()); - } catch (final @NotNull Exception exception) { - plugin.printStacktrace( - "Something went wrong while trying to register command: " + this.getName(), - exception - ); - return; - } - } - boolean registered = commandMap.register("DeluxeMenus", this); - if (registered) { - plugin.debug( - DebugLevel.LOW, - Level.INFO, - "Registered command: " + this.getName() + " for menu: " + this.options.name() - ); - } - } - - private void removeCommand() { - if (commandMap != null && this.options.registerCommands()) { - Field cMap; - Field knownCommands; - try { - cMap = Bukkit.getServer().getClass().getDeclaredField("commandMap"); - cMap.setAccessible(true); - knownCommands = SimpleCommandMap.class.getDeclaredField("knownCommands"); - knownCommands.setAccessible(true); - ((Map) knownCommands.get((SimpleCommandMap) cMap.get(Bukkit.getServer()))) - .remove(this.getName()); - boolean unregistered = this.unregister((CommandMap) cMap.get(Bukkit.getServer())); - this.unregister(commandMap); - if (unregistered) { - plugin.debug( - DebugLevel.HIGH, - Level.INFO, - "Successfully unregistered command: " + this.getName() - ); - } else { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Failed to unregister command: " + this.getName() - ); - } - } catch (final @NotNull Exception exception) { - plugin.printStacktrace( - "Something went wrong while trying to unregister command: " + this.getName(), - exception - ); - } - } - } - - @Override - public boolean execute(final @NotNull CommandSender sender, final @NotNull String commandLabel, final @NotNull String[] typedArgs) { - if (!(sender instanceof Player)) { - Msg.msg(sender, "Menus can only be opened by players!"); - return true; - } - - Map argMap = null; - - if (!this.options.arguments().isEmpty()) { - plugin.debug(DebugLevel.LOWEST, Level.INFO, "has args"); - if (typedArgs.length < this.options.arguments().size()) { - if (this.options.argumentsUsageMessage().isPresent()) { - Msg.msg(sender, this.options.argumentsUsageMessage().get()); - } - return true; - } - argMap = new HashMap<>(); - int index = 0; - for (String arg : this.options.arguments()) { - if (index + 1 == this.options.arguments().size()) { - String last = String.join(" ", Arrays.asList(typedArgs).subList(index, typedArgs.length)); - plugin.debug(DebugLevel.LOWEST, Level.INFO, "arg: " + arg + " => " + last); - argMap.put(arg, last); - } else { - argMap.put(arg, typedArgs[index]); - plugin.debug(DebugLevel.LOWEST, Level.INFO, "arg: " + arg + " => " + typedArgs[index]); - } - index++; - } - } - - Player player = (Player) sender; - plugin.debug(DebugLevel.LOWEST, Level.INFO, "opening menu: " + this.options.name()); - openMenu(player, argMap, null); - return true; - } - private boolean hasOpenBypassPerm(final @NotNull Player viewer) { return viewer.hasPermission("deluxemenus.openrequirement.bypass." + this.options.name()) || viewer.hasPermission("deluxemenus.openrequirement.bypass.*"); diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java index dc28bd14..a786436c 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -3,6 +3,7 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.hooks.ItemHook; import com.extendedclip.deluxemenus.menu.options.HeadType; +import com.extendedclip.deluxemenus.menu.options.LoreAppendMode; import com.extendedclip.deluxemenus.menu.options.MenuItemOptions; import com.extendedclip.deluxemenus.nbt.NbtProvider; import com.extendedclip.deluxemenus.utils.DebugLevel; diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java b/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java new file mode 100644 index 00000000..3edf522b --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java @@ -0,0 +1,181 @@ +package com.extendedclip.deluxemenus.menu.command; + +import com.extendedclip.deluxemenus.DeluxeMenus; +import com.extendedclip.deluxemenus.menu.Menu; +import com.extendedclip.deluxemenus.utils.DebugLevel; +import me.clip.placeholderapi.util.Msg; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandMap; +import org.bukkit.command.CommandSender; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.logging.Level; + +public class RegistrableMenuCommand extends Command { + + private static final String FALLBACK_PREFIX = "DeluxeMenus".toLowerCase(Locale.ROOT).trim(); + private static CommandMap commandMap = null; + + private final DeluxeMenus plugin; + + private Menu menu; + private boolean registered = false; + private boolean unregistered = false; + + public RegistrableMenuCommand(final @NotNull DeluxeMenus plugin, + final @NotNull Menu menu) { + super(menu.options().commands().isEmpty() ? menu.options().name() : menu.options().commands().get(0)); + this.plugin = plugin; + this.menu = menu; + + if (menu.options().commands().size() > 1) { + this.setAliases(menu.options().commands().subList(1, menu.options().commands().size())); + } + } + + @Override + public boolean execute(final @NotNull CommandSender sender, final @NotNull String commandLabel, final @NotNull String[] typedArgs) { + if (this.unregistered) { + throw new IllegalStateException("This command was unregistered!"); + } + + if (!(sender instanceof Player)) { + Msg.msg(sender, "Menus can only be opened by players!"); + return true; + } + + Map argMap = null; + + if (!menu.options().arguments().isEmpty()) { + plugin.debug(DebugLevel.LOWEST, Level.INFO, "has args"); + if (typedArgs.length < menu.options().arguments().size()) { + if (menu.options().argumentsUsageMessage().isPresent()) { + Msg.msg(sender, menu.options().argumentsUsageMessage().get()); + } + return true; + } + argMap = new HashMap<>(); + int index = 0; + for (String arg : menu.options().arguments()) { + if (index + 1 == menu.options().arguments().size()) { + String last = String.join(" ", Arrays.asList(typedArgs).subList(index, typedArgs.length)); + plugin.debug(DebugLevel.LOWEST, Level.INFO, "arg: " + arg + " => " + last); + argMap.put(arg, last); + } else { + argMap.put(arg, typedArgs[index]); + plugin.debug(DebugLevel.LOWEST, Level.INFO, "arg: " + arg + " => " + typedArgs[index]); + } + index++; + } + } + + Player player = (Player) sender; + plugin.debug(DebugLevel.LOWEST, Level.INFO, "opening menu: " + menu.options().name()); + menu.openMenu(player, argMap, null); + return true; + } + + public void register() { + if (registered) { + throw new IllegalStateException("This command was already registered!"); + } + + registered = true; + + if (commandMap == null) { + try { + final Field f = Bukkit.getServer().getClass().getDeclaredField("commandMap"); + f.setAccessible(true); + commandMap = (CommandMap) f.get(Bukkit.getServer()); + } catch (final @NotNull Exception exception) { + plugin.printStacktrace( + "Something went wrong while trying to register command: " + this.getName(), + exception + ); + return; + } + } + + boolean registered = commandMap.register(FALLBACK_PREFIX, this); + if (registered) { + plugin.debug( + DebugLevel.LOW, + Level.INFO, + "Registered command: " + this.getName() + " for menu: " + menu.options().name() + ); + } + } + + public void unregister() { + if (!registered) { + throw new IllegalStateException("This command was not registered!"); + } + + if (unregistered) { + throw new IllegalStateException("This command was already unregistered!"); + } + + unregistered = true; + + if (commandMap == null) { + this.menu = null; + return; + } + + Field cMap; + Field knownCommands; + try { + cMap = Bukkit.getServer().getClass().getDeclaredField("commandMap"); + cMap.setAccessible(true); + knownCommands = SimpleCommandMap.class.getDeclaredField("knownCommands"); + knownCommands.setAccessible(true); + + final Map knownCommandsMap = (Map) knownCommands.get(cMap.get(Bukkit.getServer())); + + // We need to remove every single alias because CommandMap#register() adds them all to the map. + // If we do not remove them, then we will have dangling references to the command. + knownCommandsMap.remove(this.getName()); + knownCommandsMap.remove(FALLBACK_PREFIX + ":" + this.getName()); + + for (String alias : this.getAliases()) { + knownCommandsMap.remove(alias); + knownCommandsMap.remove(FALLBACK_PREFIX + ":" + alias); + } + + boolean unregistered = this.unregister((CommandMap) cMap.get(Bukkit.getServer())); + this.unregister(commandMap); + if (unregistered) { + plugin.debug( + DebugLevel.HIGH, + Level.INFO, + "Successfully unregistered command: " + this.getName() + ); + } else { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Failed to unregister command: " + this.getName() + ); + } + } catch (final @NotNull Exception exception) { + plugin.printStacktrace( + "Something went wrong while trying to unregister command: " + this.getName(), + exception + ); + } + + this.menu = null; + } + + public boolean registered() { + return registered; + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/LoreAppendMode.java b/src/main/java/com/extendedclip/deluxemenus/menu/options/LoreAppendMode.java similarity index 60% rename from src/main/java/com/extendedclip/deluxemenus/menu/LoreAppendMode.java rename to src/main/java/com/extendedclip/deluxemenus/menu/options/LoreAppendMode.java index 9b0547d3..b59ce59a 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/LoreAppendMode.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/options/LoreAppendMode.java @@ -1,4 +1,4 @@ -package com.extendedclip.deluxemenus.menu; +package com.extendedclip.deluxemenus.menu.options; public enum LoreAppendMode { OVERRIDE, diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java b/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java index 21cdc158..3377bec1 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuItemOptions.java @@ -2,7 +2,6 @@ import com.extendedclip.deluxemenus.action.ClickHandler; import com.extendedclip.deluxemenus.config.DeluxeMenusConfig; -import com.extendedclip.deluxemenus.menu.LoreAppendMode; import com.extendedclip.deluxemenus.requirement.RequirementList; import org.bukkit.DyeColor; import org.bukkit.block.banner.Pattern; From 63bde0dd31b5439112ce96ce419dda09eed2e482 Mon Sep 17 00:00:00 2001 From: MemencioPerez Date: Sun, 12 Jan 2025 04:00:53 -0400 Subject: [PATCH 22/25] Catch only the specific types of exceptions that may be thrown when attempting to access the Sound#valueOf method through Reflection --- .../java/com/extendedclip/deluxemenus/utils/SoundUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java index 97a218e3..3ffc4842 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/SoundUtils.java @@ -2,6 +2,7 @@ import org.bukkit.Sound; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class SoundUtils { @@ -12,7 +13,7 @@ public static Sound getSound(String name) { // This fixes java.lang.IncompatibleClassChangeError when trying to use versions prior to 1.21.3. Method valueOfMethod = Class.forName("org.bukkit.Sound").getMethod("valueOf", String.class); return (Sound) valueOfMethod.invoke(null, name); - } catch (Exception e) { + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { // Use the Sound#valueOf method if Reflection fails. return Sound.valueOf(name); } From 4a0e28b96a643a79e434a21675a3a9ddee1b7b74 Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Sun, 12 Jan 2025 18:15:58 +0200 Subject: [PATCH 23/25] removed unnecessary comments --- build.gradle.kts | 3 --- 1 file changed, 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2b9d2bd9..46b52127 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,6 @@ plugins { java id("com.gradleup.shadow") version("8.3.5") id("com.github.ben-manes.versions") version("0.51.0") -// id("io.papermc.paperweight.userdev") version("2.0.0-beta.13") } // Change to true when releasing @@ -43,8 +42,6 @@ dependencies { implementation(libs.adventure.minimessage) compileOnly("org.jetbrains:annotations:23.0.0") - -// paperweight.paperDevBundle("1.21.3-R0.1-SNAPSHOT") } tasks { From d535240408a92c96623e4e58fff99261aef515eb Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:14:10 +0200 Subject: [PATCH 24/25] Downgrade oraxen api version to fix java incompatibility problems --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6e4d66a1..55219e48 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ vault = "1.7.1" authlib = "1.5.25" headdb = "1.3.2" itemsadder = "3.6.3-beta-14" -oraxen = "1.187.0" +oraxen = "1.159.0" mythiclib = "1.7.1-SNAPSHOT" mmoitems = "6.10-SNAPSHOT" papi = "2.11.6" From cd0b028228c2f76866a278ef3d14ea80ec75661d Mon Sep 17 00:00:00 2001 From: BlitzOffline <52609756+BlitzOffline@users.noreply.github.com> Date: Tue, 14 Jan 2025 00:17:36 +0200 Subject: [PATCH 25/25] Downgrade oraxen api version to fix java incompatibility problems --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 55219e48..624deb8c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ vault = { module = "com.github.milkbowl:VaultAPI", version.ref = "vault" } authlib = { module = "com.mojang:authlib", version.ref = "authlib" } headdb = { module = "com.arcaniax:HeadDatabase-API", version.ref = "headdb" } itemsadder = { module = "com.github.LoneDev6:api-itemsadder", version.ref = "itemsadder" } -oraxen = { module = "io.th0rgal:oraxen", version.ref = "oraxen" } +oraxen = { module = "com.github.oraxen:oraxen", version.ref = "oraxen" } mythiclib = { module = "io.lumine:MythicLib-dist", version.ref = "mythiclib"} mmoitems = { module = "net.Indyuce:MMOItems-API", version.ref = "mmoitems" } papi = { module = "me.clip:placeholderapi", version.ref = "papi" }