From 1752bbd658fde6181130417cf31a0a6f5412934b Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Wed, 20 Aug 2025 00:27:26 -0400 Subject: [PATCH 01/24] Implement UniversalScheduler Folia Support --- build.gradle.kts | 1 + gradle/libs.versions.toml | 2 +- .../extendedclip/deluxemenus/DeluxeMenus.java | 19 +- .../deluxemenus/action/ClickActionTask.java | 656 +++++++++--------- .../command/DeluxeMenusCommand.java | 1 - .../command/subcommand/ExecuteCommand.java | 1 - .../command/subcommand/RefreshCommand.java | 2 +- .../deluxemenus/config/DeluxeMenusConfig.java | 2 +- .../deluxemenus/dupe/DupeFixer.java | 28 +- .../events/DeluxeMenusOpenMenuEvent.java | 1 - .../deluxemenus/hooks/BaseHeadHook.java | 1 - .../hooks/ExecutableItemsHook.java | 1 - .../deluxemenus/hooks/ItemsAdderHook.java | 1 - .../deluxemenus/hooks/MMOItemsHook.java | 4 +- .../deluxemenus/hooks/NamedHeadHook.java | 1 - .../deluxemenus/listener/PlayerListener.java | 5 +- .../extendedclip/deluxemenus/menu/Menu.java | 39 +- .../deluxemenus/menu/MenuHolder.java | 157 +++-- .../deluxemenus/menu/MenuItem.java | 12 +- .../requirement/JavascriptRequirement.java | 2 +- .../scheduler/UniversalRunnable.java | 176 +++++ .../scheduler/UniversalScheduler.java | 42 ++ .../scheduler/bukkit/BukkitScheduledTask.java | 71 ++ .../scheduler/bukkit/BukkitScheduler.java | 129 ++++ .../scheduler/folia/FoliaScheduledTask.java | 57 ++ .../scheduler/folia/FoliaScheduler.java | 220 ++++++ .../scheduler/paper/PaperScheduler.java | 41 ++ .../scheduling/schedulers/TaskScheduler.java | 346 +++++++++ .../scheduling/tasks/MyScheduledTask.java | 54 ++ .../deluxemenus/scheduler/utils/JavaUtil.java | 12 + .../updatechecker/UpdateChecker.java | 19 +- .../deluxemenus/utils/ItemUtils.java | 1 - .../deluxemenus/utils/StringUtils.java | 1 - src/main/resources/plugin.yml | 3 +- 34 files changed, 1624 insertions(+), 484 deletions(-) create mode 100644 src/main/java/com/extendedclip/deluxemenus/scheduler/UniversalRunnable.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/scheduler/UniversalScheduler.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/scheduler/bukkit/BukkitScheduledTask.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/scheduler/bukkit/BukkitScheduler.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/scheduler/folia/FoliaScheduledTask.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/scheduler/folia/FoliaScheduler.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/scheduler/paper/PaperScheduler.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/scheduler/scheduling/schedulers/TaskScheduler.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/scheduler/scheduling/tasks/MyScheduledTask.java create mode 100644 src/main/java/com/extendedclip/deluxemenus/scheduler/utils/JavaUtil.java diff --git a/build.gradle.kts b/build.gradle.kts index 643c40c2..70dcaa54 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,6 +20,7 @@ repositories { maven("https://nexus.phoenixdevt.fr/repository/maven-public/") maven("https://repo.nexomc.com/releases/") maven("https://repo.oraxen.com/releases") + maven("https://repo.papermc.io/repository/maven-public/") maven("https://jitpack.io") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bd6d8d7d..12eab0ea 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ adventure-minimessage = "4.24.0" [libraries] # Compile only -spigot = { module = "org.spigotmc:spigot-api", version.ref = "spigot" } +spigot = { module = "io.papermc.paper:paper-api", version.ref = "spigot" } 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" } diff --git a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java index f812f937..7196d51e 100644 --- a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java +++ b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java @@ -15,6 +15,8 @@ import com.extendedclip.deluxemenus.nbt.NbtProvider; import com.extendedclip.deluxemenus.persistentmeta.PersistentMetaHandler; import com.extendedclip.deluxemenus.placeholder.Expansion; +import com.extendedclip.deluxemenus.scheduler.UniversalScheduler; +import com.extendedclip.deluxemenus.scheduler.scheduling.schedulers.TaskScheduler; import com.extendedclip.deluxemenus.updatechecker.UpdateChecker; import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.Messages; @@ -35,7 +37,13 @@ import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.TreeMap; import java.util.function.Function; import java.util.logging.Level; import java.util.stream.Collectors; @@ -59,6 +67,9 @@ public class DeluxeMenus extends JavaPlugin { private final GeneralConfig generalConfig = new GeneralConfig(this); private DeluxeMenusConfig menuConfig; + @NotNull + private final TaskScheduler scheduler = UniversalScheduler.getScheduler(this); + @Override public void onLoad() { if (NbtProvider.isAvailable()) { @@ -113,7 +124,7 @@ public void onEnable() { public void onDisable() { Bukkit.getMessenger().unregisterOutgoingPluginChannel(this, "BungeeCord"); - Bukkit.getScheduler().cancelTasks(this); + scheduler.cancelTasks(this); if (this.audiences != null) { this.audiences.close(); @@ -217,6 +228,10 @@ public GeneralConfig getGeneralConfig() { return generalConfig; } + public @NotNull TaskScheduler getScheduler() { + return scheduler; + } + private boolean hookIntoPlaceholderAPI() { final boolean canHook = Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null; if (!canHook) { diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index d0feb75c..6e717ba5 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -4,29 +4,18 @@ import com.extendedclip.deluxemenus.menu.Menu; import com.extendedclip.deluxemenus.menu.MenuHolder; import com.extendedclip.deluxemenus.persistentmeta.PersistentMetaHandler; -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 com.extendedclip.deluxemenus.scheduler.UniversalRunnable; +import com.extendedclip.deluxemenus.utils.*; import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; import org.bukkit.Sound; import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerCommandPreprocessEvent; -import org.bukkit.scheduler.BukkitRunnable; import org.jetbrains.annotations.NotNull; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; +import java.util.*; import java.util.logging.Level; -public class ClickActionTask extends BukkitRunnable { +public class ClickActionTask extends UniversalRunnable { private final DeluxeMenus plugin; private final UUID uuid; @@ -62,417 +51,418 @@ public void run() { return; } - final Optional holder = Menu.getMenuHolder(player); - final Player target = holder.isPresent() && holder.get().getPlaceholderPlayer() != null - ? holder.get().getPlaceholderPlayer() - : player; + plugin.getScheduler().runTask(player, () -> { + final Optional holder = Menu.getMenuHolder(player); + final Player target = holder.isPresent() && holder.get().getPlaceholderPlayer() != null + ? holder.get().getPlaceholderPlayer() + : player; - final String executable = StringUtils.replacePlaceholdersAndArguments( - this.exec, - this.arguments, - target, - this.parsePlaceholdersInArguments, - this.parsePlaceholdersAfterArguments); + final String executable = StringUtils.replacePlaceholdersAndArguments( + this.exec, + this.arguments, + target, + this.parsePlaceholdersInArguments, + this.parsePlaceholdersAfterArguments); - switch (actionType) { - case META: - if (!VersionHelper.IS_PDC_VERSION || plugin.getPersistentMetaHandler() == null) { - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Meta action not supported on this server version."); - break; - } - final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().parseAndExecuteMetaActionFromString(player, executable); - switch (result) { - case INVALID_SYNTAX: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Make sure you have the right syntax."); - break; - case NEW_VALUE_IS_DIFFERENT_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! New value is a different type than the old value!"); - break; - case INVALID_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! The specified type is not supported for the specified action!"); - break; - case EXISTENT_VALUE_IS_DIFFERENT_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Existent value is a different type than the new value!"); - break; - case VALUE_NOT_FOUND: - case SUCCESS: - default: + switch (actionType) { + case META: + if (!VersionHelper.IS_PDC_VERSION || plugin.getPersistentMetaHandler() == null) { + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Meta action not supported on this server version."); break; - } - break; + } + final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().parseAndExecuteMetaActionFromString(player, executable); + switch (result) { + case INVALID_SYNTAX: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Make sure you have the right syntax."); + break; + case NEW_VALUE_IS_DIFFERENT_TYPE: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! New value is a different type than the old value!"); + break; + case INVALID_TYPE: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! The specified type is not supported for the specified action!"); + break; + case EXISTENT_VALUE_IS_DIFFERENT_TYPE: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Existent value is a different type than the new value!"); + break; + case VALUE_NOT_FOUND: + case SUCCESS: + default: + break; + } + break; + + case PLAYER: + case PLAYER_COMMAND_EVENT: + player.chat("/" + executable); + break; - case PLAYER: - case PLAYER_COMMAND_EVENT: - player.chat("/" + executable); - break; + case PLACEHOLDER: + holder.ifPresent(it -> it.setPlaceholders(executable)); + break; - case PLACEHOLDER: - holder.ifPresent(it -> it.setPlaceholders(executable)); - break; + case CHAT: + player.chat(executable); + break; - case CHAT: - player.chat(executable); - break; + case CONSOLE: + plugin.getScheduler().runTask(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable)); + break; + + case MINI_MESSAGE: + plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); + break; - case CONSOLE: - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable); - break; + case MINI_BROADCAST: + plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); + break; - case MINI_MESSAGE: - plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); - break; + case MESSAGE: + player.sendMessage(StringUtils.color(executable)); + break; - case MINI_BROADCAST: - plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); - break; + case LOG: + final String[] logParts = executable.split(" ", 2); - case MESSAGE: - player.sendMessage(StringUtils.color(executable)); - break; + if (logParts.length == 0 || logParts[0].isBlank()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "LOG command requires at least a message"); + break; + } - case LOG: - final String[] logParts = executable.split(" ", 2); + Level logLevel; + String message; - if (logParts.length == 0 || logParts[0].isBlank()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "LOG command requires at least a message"); + if (logParts.length == 1) { + logLevel = Level.INFO; + message = logParts[0]; + } else { + message = logParts[1]; + + try { + logLevel = Level.parse(logParts[0].toUpperCase()); + } catch (IllegalArgumentException e) { + logLevel = Level.INFO; + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Log level " + logParts[0] + " is not a valid log level! Using INFO instead."); + } + } + + plugin.getLogger().log(logLevel, String.format("[%s]: %s", holder.map(MenuHolder::getMenuName).orElse("Unknown Menu"), message)); break; - } - Level logLevel; - String message; + case BROADCAST: + Bukkit.broadcastMessage(StringUtils.color(executable)); + break; - if(logParts.length == 1) { - logLevel = Level.INFO; - message = logParts[0]; - } else { - message = logParts[1]; + case CLOSE: + Menu.closeMenu(plugin, player, true, true); + break; - try { - logLevel = Level.parse(logParts[0].toUpperCase()); - } catch (IllegalArgumentException e) { - logLevel = Level.INFO; - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Log level " + logParts[0] + " is not a valid log level! Using INFO instead."); + case OPEN_GUI_MENU: + case OPEN_MENU: + final String temporaryExecutable = executable.replaceAll("\\s+", " ").replace(" ", " "); + final String[] executableParts = temporaryExecutable.split(" ", 2); + + if (executableParts.length == 0) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); + break; } - } - plugin.getLogger().log(logLevel, String.format("[%s]: %s", holder.map(MenuHolder::getMenuName).orElse("Unknown Menu"), message)); - break; + final String menuName = executableParts[0]; - case BROADCAST: - Bukkit.broadcastMessage(StringUtils.color(executable)); - break; + final Optional optionalMenuToOpen = Menu.getMenuByName(menuName); - case CLOSE: - Menu.closeMenu(plugin, player, true, true); - break; + if (optionalMenuToOpen.isEmpty()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); + break; + } - case OPEN_GUI_MENU: - case OPEN_MENU: - final String temporaryExecutable = executable.replaceAll("\\s+", " ").replace(" ", " "); - final String[] executableParts = temporaryExecutable.split(" ", 2); + final Menu menuToOpen = optionalMenuToOpen.get(); - if (executableParts.length == 0) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); - break; - } + final List menuArgumentNames = menuToOpen.options().arguments(); - final String menuName = executableParts[0]; + String[] passedArgumentValues = null; + if (executableParts.length > 1) { + passedArgumentValues = executableParts[1].split(" "); + } - final Optional optionalMenuToOpen = Menu.getMenuByName(menuName); + if (menuArgumentNames.isEmpty()) { + if (passedArgumentValues != null && passedArgumentValues.length > 0) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Arguments were given for menu " + menuName + " in action [openguimenu] or [openmenu], but the menu does not support arguments!" + ); + } - if (optionalMenuToOpen.isEmpty()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); - break; - } + if (holder.isEmpty()) { + menuToOpen.openMenu(player); + break; + } - final Menu menuToOpen = optionalMenuToOpen.get(); + menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); + break; + } - final List menuArgumentNames = menuToOpen.options().arguments(); + if (passedArgumentValues == null || passedArgumentValues.length == 0) { + // Replicate old behavior: If no arguments are given, open the menu with the arguments from the current menu + if (holder.isEmpty()) { + menuToOpen.openMenu(player); + break; + } - String[] passedArgumentValues = null; - if (executableParts.length > 1) { - passedArgumentValues = executableParts[1].split(" "); - } + menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); + break; + } - if (menuArgumentNames.isEmpty()) { - if (passedArgumentValues != null && passedArgumentValues.length > 0) { + if (passedArgumentValues.length < menuArgumentNames.size()) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Arguments were given for menu " + menuName + " in action [openguimenu] or [openmenu], but the menu does not support arguments!" + "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" ); + break; } - if (holder.isEmpty()) { - menuToOpen.openMenu(player); - break; + final Map argumentsMap = new HashMap<>(); + if (holder.isPresent() && holder.get().getTypedArgs() != null) { + // Pass the arguments from the current menu to the new menu. If the new menu has arguments with the + // same name, they will be overwritten + argumentsMap.putAll(holder.get().getTypedArgs()); } - menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); - break; - } + for (int index = 0; index < menuArgumentNames.size(); index++) { + final String argumentName = menuArgumentNames.get(index); + + if (passedArgumentValues.length <= index) { + // This should never be the case! + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" + ); + break; + } + + if (menuArgumentNames.size() == index + 1) { + // If this is the last argument, get all remaining values and join them + final String lastArgumentValue = String.join(" ", Arrays.asList(passedArgumentValues).subList(index, passedArgumentValues.length)); + argumentsMap.put(argumentName, lastArgumentValue); + break; + } + + argumentsMap.put(argumentName, passedArgumentValues[index]); + } - if (passedArgumentValues == null || passedArgumentValues.length == 0) { - // Replicate old behavior: If no arguments are given, open the menu with the arguments from the current menu if (holder.isEmpty()) { - menuToOpen.openMenu(player); + menuToOpen.openMenu(player, argumentsMap, null); break; } - menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); + menuToOpen.openMenu(player, argumentsMap, holder.get().getPlaceholderPlayer()); break; - } - - if (passedArgumentValues.length < menuArgumentNames.size()) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" - ); + + case CONNECT: + plugin.connect(player, executable); break; - } - final Map argumentsMap = new HashMap<>(); - if (holder.isPresent() && holder.get().getTypedArgs() != null) { - // Pass the arguments from the current menu to the new menu. If the new menu has arguments with the - // same name, they will be overwritten - argumentsMap.putAll(holder.get().getTypedArgs()); - } + case JSON_MESSAGE: + AdventureUtils.sendJson(plugin, player, executable); + break; - for (int index = 0; index < menuArgumentNames.size(); index++) { - final String argumentName = menuArgumentNames.get(index); + case JSON_BROADCAST: + case BROADCAST_JSON: + plugin.audiences().all().sendMessage(AdventureUtils.fromJson(executable)); + break; - if (passedArgumentValues.length <= index) { - // This should never be the case! + case REFRESH: + if (holder.isEmpty()) { plugin.debug( - DebugLevel.HIGHEST, + DebugLevel.MEDIUM, Level.WARNING, - "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" + player.getName() + " does not have menu open! Nothing to refresh!" ); break; } - if (menuArgumentNames.size() == index + 1) { - // If this is the last argument, get all remaining values and join them - final String lastArgumentValue = String.join(" ", Arrays.asList(passedArgumentValues).subList(index, passedArgumentValues.length)); - argumentsMap.put(argumentName, lastArgumentValue); + holder.get().refreshMenu(); + break; + + case TAKE_MONEY: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot take money!"); break; } - argumentsMap.put(argumentName, passedArgumentValues[index]); - } - - if (holder.isEmpty()) { - menuToOpen.openMenu(player, argumentsMap, null); - break; - } - - menuToOpen.openMenu(player, argumentsMap, holder.get().getPlaceholderPlayer()); - break; - - case CONNECT: - plugin.connect(player, executable); - break; - - case JSON_MESSAGE: - AdventureUtils.sendJson(plugin, player, executable); - break; - - case JSON_BROADCAST: - case BROADCAST_JSON: - plugin.audiences().all().sendMessage(AdventureUtils.fromJson(executable)); - break; - - case REFRESH: - if (holder.isEmpty()) { - plugin.debug( - DebugLevel.MEDIUM, - Level.WARNING, - player.getName() + " does not have menu open! Nothing to refresh!" - ); + try { + plugin.getVault().takeMoney(player, Double.parseDouble(executable)); + } catch (final NumberFormatException exception) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for take money action: " + executable + ", is not a valid number!" + ); + } break; - } - - holder.get().refreshMenu(); - break; - case TAKE_MONEY: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot take money!"); - break; - } - - try { - plugin.getVault().takeMoney(player, Double.parseDouble(executable)); - } catch (final NumberFormatException exception) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for take money action: " + executable + ", is not a valid number!" - ); - } - break; - - case GIVE_MONEY: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot give money!"); - break; - } - - try { - plugin.getVault().giveMoney(player, Double.parseDouble(executable)); - } catch (final NumberFormatException exception) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for give money action: " + executable + ", is not a valid number!" - ); - } - break; - - case TAKE_EXP: - case GIVE_EXP: - final String lowerCaseExecutable = executable.toLowerCase(); - - try { - if (Integer.parseInt(lowerCaseExecutable.replaceAll("l", "")) <= 0) break; - - if (actionType == ActionType.TAKE_EXP) { - ExpUtils.setExp(player, "-" + lowerCaseExecutable); + case GIVE_MONEY: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot give money!"); break; } - ExpUtils.setExp(player, lowerCaseExecutable); - break; - - } catch (final NumberFormatException exception) { - if (actionType == ActionType.TAKE_EXP) { + try { + plugin.getVault().giveMoney(player, Double.parseDouble(executable)); + } catch (final NumberFormatException exception) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Amount for take exp action: " + executable + ", is not a valid number!" + "Amount for give money action: " + executable + ", is not a valid number!" ); - break; } - - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for give exp action: " + executable + ", is not a valid number!" - ); - break; - } - - case GIVE_PERM: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Vault not hooked! Cannot give permission: " + executable + "!"); break; - } - plugin.getVault().givePermission(player, executable); - break; + case TAKE_EXP: + case GIVE_EXP: + final String lowerCaseExecutable = executable.toLowerCase(); - case TAKE_PERM: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Vault not hooked! Cannot take permission: " + executable + "!"); - break; - } + try { + if (Integer.parseInt(lowerCaseExecutable.replaceAll("l", "")) <= 0) break; - plugin.getVault().takePermission(player, executable); - break; + if (actionType == ActionType.TAKE_EXP) { + ExpUtils.setExp(player, "-" + lowerCaseExecutable); + break; + } - case BROADCAST_SOUND: - case BROADCAST_WORLD_SOUND: - case PLAY_SOUND: - final Sound sound; - float volume = 1; - float pitch = 1; + ExpUtils.setExp(player, lowerCaseExecutable); + break; - if (!executable.contains(" ")) { - try { - sound = SoundUtils.getSound(executable.toUpperCase()); - } catch (final IllegalArgumentException exception) { - plugin.printStacktrace( - "Sound name given for sound action: " + executable + ", is not a valid sound!", - exception + } catch (final NumberFormatException exception) { + if (actionType == ActionType.TAKE_EXP) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for take exp action: " + executable + ", is not a valid number!" + ); + break; + } + + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for give exp action: " + executable + ", is not a valid number!" ); break; } - } else { - String[] parts = executable.split(" ", 3); - try { - sound = SoundUtils.getSound(parts[0].toUpperCase()); - } catch (final IllegalArgumentException exception) { - plugin.printStacktrace( - "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", - exception - ); + case GIVE_PERM: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Vault not hooked! Cannot give permission: " + executable + "!"); break; } - if (parts.length == 3) { + plugin.getVault().givePermission(player, executable); + break; + + case TAKE_PERM: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Vault not hooked! Cannot take permission: " + executable + "!"); + break; + } + + plugin.getVault().takePermission(player, executable); + break; + + case BROADCAST_SOUND: + case BROADCAST_WORLD_SOUND: + case PLAY_SOUND: + final Sound sound; + float volume = 1; + float pitch = 1; + + if (!executable.contains(" ")) { + try { + sound = SoundUtils.getSound(executable.toUpperCase()); + } catch (final IllegalArgumentException exception) { + plugin.printStacktrace( + "Sound name given for sound action: " + executable + ", is not a valid sound!", + exception + ); + break; + } + } else { + String[] parts = executable.split(" ", 3); + try { - pitch = Float.parseFloat(parts[2]); + sound = SoundUtils.getSound(parts[0].toUpperCase()); + } catch (final IllegalArgumentException exception) { + plugin.printStacktrace( + "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", + exception + ); + break; + } + + if (parts.length == 3) { + try { + pitch = Float.parseFloat(parts[2]); + } catch (final NumberFormatException exception) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Pitch given for sound action: " + parts[2] + ", is not a valid number!" + ); + + plugin.printStacktrace( + "Pitch given for sound action: " + parts[2] + ", is not a valid number!", + exception + ); + } + } + + try { + volume = Float.parseFloat(parts[1]); } catch (final NumberFormatException exception) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Pitch given for sound action: " + parts[2] + ", is not a valid number!" + "Volume given for sound action: " + parts[1] + ", is not a valid number!" ); plugin.printStacktrace( - "Pitch given for sound action: " + parts[2] + ", is not a valid number!", + "Volume given for sound action: " + parts[1] + ", is not a valid number!", exception ); } } - - try { - volume = Float.parseFloat(parts[1]); - } catch (final NumberFormatException exception) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Volume given for sound action: " + parts[1] + ", is not a valid number!" - ); - - plugin.printStacktrace( - "Volume given for sound action: " + parts[1] + ", is not a valid number!", - exception - ); + switch (actionType) { + case BROADCAST_SOUND: + for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { + broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); + } + break; + + case BROADCAST_WORLD_SOUND: + for (final Player broadcastTarget : player.getWorld().getPlayers()) { + broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); + } + break; + + case PLAY_SOUND: + player.playSound(player.getLocation(), sound, volume, pitch); + break; } - } - - switch (actionType) { - case BROADCAST_SOUND: - for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { - broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); - } - break; - - case BROADCAST_WORLD_SOUND: - for (final Player broadcastTarget : player.getWorld().getPlayers()) { - broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); - } - break; - - case PLAY_SOUND: - player.playSound(player.getLocation(), sound, volume, pitch); - break; - } - break; + break; - default: - break; - } + default: + break; + } + }); } -} \ No newline at end of file +} diff --git a/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java index 9aa91a97..e360bde8 100644 --- a/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java @@ -2,7 +2,6 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.command.subcommand.*; -import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.Messages; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextReplacementConfig; diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ExecuteCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ExecuteCommand.java index c1ce0da1..c9dee3f2 100644 --- a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ExecuteCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/ExecuteCommand.java @@ -17,7 +17,6 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; public class ExecuteCommand extends SubCommand { diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/RefreshCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/RefreshCommand.java index 162957ba..f2816c65 100644 --- a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/RefreshCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/RefreshCommand.java @@ -51,7 +51,7 @@ public void execute(final @NotNull CommandSender sender, final @NotNull List { - for (final ItemStack itemStack : event.getPlayer().getInventory().getContents()) { - if (itemStack == null) continue; - if (!marker.isMarked(itemStack)) continue; + plugin.getScheduler().runTaskLater(() -> { + for (final ItemStack itemStack : event.getPlayer().getInventory().getContents()) { + if (itemStack == null) continue; + if (!marker.isMarked(itemStack)) continue; - plugin.debug( - DebugLevel.LOWEST, - Level.INFO, - "Player logged in with a DeluxeMenus item in their inventory. Removing it." - ); - event.getPlayer().getInventory().remove(itemStack); - }}, - 10L + plugin.debug( + DebugLevel.LOWEST, + Level.INFO, + "Player logged in with a DeluxeMenus item in their inventory. Removing it." + ); + event.getPlayer().getInventory().remove(itemStack); + } + }, 10L ); } -} \ No newline at end of file +} diff --git a/src/main/java/com/extendedclip/deluxemenus/events/DeluxeMenusOpenMenuEvent.java b/src/main/java/com/extendedclip/deluxemenus/events/DeluxeMenusOpenMenuEvent.java index 655c4831..ec8ca03e 100644 --- a/src/main/java/com/extendedclip/deluxemenus/events/DeluxeMenusOpenMenuEvent.java +++ b/src/main/java/com/extendedclip/deluxemenus/events/DeluxeMenusOpenMenuEvent.java @@ -1,6 +1,5 @@ package com.extendedclip.deluxemenus.events; -import com.extendedclip.deluxemenus.menu.Menu; import com.extendedclip.deluxemenus.menu.MenuHolder; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java index 0178ec75..5aaf2128 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/BaseHeadHook.java @@ -5,7 +5,6 @@ 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; diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableItemsHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableItemsHook.java index 9bdf1266..3054c1da 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableItemsHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableItemsHook.java @@ -5,7 +5,6 @@ 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; diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/ItemsAdderHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/ItemsAdderHook.java index ac3760b0..bae2d184 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/ItemsAdderHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/ItemsAdderHook.java @@ -4,7 +4,6 @@ import dev.lone.itemsadder.api.CustomStack; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; - import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java index 69d51653..f9e89b7b 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java @@ -7,10 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.logging.Level; - import net.Indyuce.mmoitems.MMOItems; import net.Indyuce.mmoitems.api.Type; -import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -47,7 +45,7 @@ public ItemStack getItem(@NotNull final String... arguments) { ItemStack mmoItem = null; try { - mmoItem = Bukkit.getScheduler().callSyncMethod(plugin, () -> { + mmoItem = plugin.getScheduler().callSyncMethod(() -> { ItemStack item = MMOItems.plugin.getItem(itemType, splitArgs[1]); if (item == null) { diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java index aa880ba5..4b306fd9 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/NamedHeadHook.java @@ -6,7 +6,6 @@ import com.extendedclip.deluxemenus.utils.SkullUtils; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; - import org.bukkit.event.EventHandler; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.ItemStack; diff --git a/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java b/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java index af69231b..87a7c1f6 100644 --- a/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java +++ b/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java @@ -8,7 +8,6 @@ import com.extendedclip.deluxemenus.requirement.RequirementList; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -92,8 +91,8 @@ public void onClose(InventoryCloseEvent event) { final Player player = (Player) event.getPlayer(); if (Menu.isInMenu(player)) { - Menu.closeMenu(plugin, player, false); - Bukkit.getScheduler().runTaskLater(plugin, () -> { + plugin.getScheduler().runTaskLater(player, () -> { + Menu.closeMenu(plugin, player, false); Menu.cleanInventory(plugin, player); player.updateInventory(); }, 3L); diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java index 278681c4..be0de82f 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java @@ -1,8 +1,6 @@ package com.extendedclip.deluxemenus.menu; import com.extendedclip.deluxemenus.DeluxeMenus; -import com.extendedclip.deluxemenus.action.ClickHandler; -import com.extendedclip.deluxemenus.dupe.MenuItemMarker; import com.extendedclip.deluxemenus.events.DeluxeMenusOpenMenuEvent; import com.extendedclip.deluxemenus.events.DeluxeMenusPreOpenMenuEvent; import com.extendedclip.deluxemenus.menu.command.RegistrableMenuCommand; @@ -10,11 +8,18 @@ import com.extendedclip.deluxemenus.requirement.RequirementList; import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.StringUtils; - -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; - import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryType; @@ -25,9 +30,9 @@ 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 final Map menus = new ConcurrentHashMap<>(); + private static final Set menuHolders = ConcurrentHashMap.newKeySet(); + private static final Map lastOpenedMenus = new ConcurrentHashMap<>(); private final DeluxeMenus plugin; private final MenuOptions options; @@ -50,7 +55,7 @@ public Menu( if (this.options.registerCommands()) { this.command = new RegistrableMenuCommand(plugin, this); - this.command.register(); + plugin.getScheduler().runTask(() -> this.command.register()); } menus.put(this.options.name(), this); @@ -199,7 +204,7 @@ public static void closeMenu(final @NotNull DeluxeMenus plugin, final @NotNull P } if (close) { - Bukkit.getScheduler().runTask(plugin, () -> { + plugin.getScheduler().runTask(player, () -> { player.closeInventory(); cleanInventory(plugin, player); }); @@ -274,11 +279,13 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map { + plugin.getScheduler().runTaskAsynchronously(() -> { Set activeItems = new HashSet<>(); @@ -383,7 +390,7 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map { + plugin.getScheduler().runTask(viewer, () -> { if(options.refresh()) { holder.startRefreshTask(); } @@ -400,7 +407,7 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map { + plugin.getScheduler().runTask(viewer, () -> { DeluxeMenusOpenMenuEvent openEvent = new DeluxeMenusOpenMenuEvent(viewer, holder); Bukkit.getPluginManager().callEvent(openEvent); }); diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java index adc23192..0f216e95 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java @@ -2,20 +2,15 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.menu.options.MenuOptions; +import com.extendedclip.deluxemenus.scheduler.scheduling.tasks.MyScheduledTask; import com.extendedclip.deluxemenus.utils.StringUtils; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -29,8 +24,8 @@ public class MenuHolder implements InventoryHolder { private Player placeholderPlayer; private String menuName; private Set activeItems; - private BukkitTask updateTask = null; - private BukkitTask refreshTask = null; + private MyScheduledTask updateTask = null; + private MyScheduledTask refreshTask = null; private Inventory inventory; private boolean updating; private boolean parsePlaceholdersInArguments; @@ -55,7 +50,7 @@ public String getViewerName() { return viewer.getName(); } - public BukkitTask getUpdateTask() { + public MyScheduledTask getUpdateTask() { return updateTask; } @@ -138,45 +133,51 @@ public void refreshMenu() { setUpdating(true); - Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { + plugin.getScheduler().runTaskAsynchronously(() -> { final Set active = new HashSet<>(); + final Set slotsToClear = new HashSet<>(); for (int i = 0; i < getInventory().getSize(); i++) { TreeMap e = menu.getMenuItems().get(i); if (e == null) { - getInventory().setItem(i, null); + slotsToClear.add(i); continue; } - boolean m = false; + boolean matched = false; for (MenuItem item : e.values()) { if (item.options().viewRequirements().isPresent()) { if (item.options().viewRequirements().get().evaluate(this)) { - m = true; + matched = true; active.add(item); break; } } else { - m = true; + matched = true; active.add(item); break; } } - if (!m) { - getInventory().setItem(i, null); + if (!matched) { + slotsToClear.add(i); } } if (active.isEmpty()) { - Menu.closeMenu(plugin, getViewer(), true); + plugin.getScheduler().runTask(getViewer(), () -> Menu.closeMenu(plugin, getViewer(), true)); + return; } - Bukkit.getScheduler().runTask(plugin, () -> { + plugin.getScheduler().runTask(getViewer(), () -> { + + for (int slot : slotsToClear) { + getInventory().setItem(slot, null); + } boolean update = false; @@ -201,7 +202,7 @@ public void refreshMenu() { if (update && updateTask == null) { startUpdatePlaceholdersTask(); - } else if(!update && updateTask != null) { + } else if (!update && updateTask != null) { stopPlaceholderUpdate(); } @@ -221,7 +222,7 @@ public void stopPlaceholderUpdate() { } public void stopRefreshTask() { - if(refreshTask != null) { + if (refreshTask != null) { try { refreshTask.cancel(); } catch (Exception ignored) { @@ -231,20 +232,21 @@ public void stopRefreshTask() { } public void startRefreshTask() { - if(refreshTask != null) { + if (refreshTask != null) { stopRefreshTask(); } - refreshTask = new BukkitRunnable() { - @Override - public void run() { - refreshMenu(); - } - }.runTaskTimerAsynchronously(plugin, 20L, - 20L * Menu.getMenuByName(menuName) - .map(Menu::options) - .map(MenuOptions::refreshInterval) - .orElse(10)); + long initialDelay = 20L; + long period = 20L * Menu.getMenuByName(menuName) + .map(Menu::options) + .map(MenuOptions::refreshInterval) + .orElse(10); + + refreshTask = plugin.getScheduler().runTaskTimerAsynchronously( + this::refreshMenu, + initialDelay, + period + ); } public void startUpdatePlaceholdersTask() { @@ -253,69 +255,70 @@ public void startUpdatePlaceholdersTask() { stopPlaceholderUpdate(); } - updateTask = new BukkitRunnable() { + long initialDelay = 20L; + long period = 20L * Menu.getMenuByName(menuName) + .map(Menu::options) + .map(MenuOptions::updateInterval) + .orElse(10); - @Override - public void run() { + updateTask = plugin.getScheduler().runTaskTimer( + getViewer(), + () -> { - if (updating) { - return; - } + if (updating) { + return; + } - Set items = getActiveItems(); + Set items = getActiveItems(); - if (items == null) { - return; - } + if (items == null) { + return; + } - for (MenuItem item : items) { + for (MenuItem item : items) { - if (item.options().updatePlaceholders()) { + if (item.options().updatePlaceholders()) { - ItemStack i = inventory.getItem(item.options().slot()); + ItemStack i = inventory.getItem(item.options().slot()); - if (i == null) { - continue; - } - - int amt = i.getAmount(); + if (i == null) { + continue; + } - if (item.options().dynamicAmount().isPresent()) { - try { - amt = Integer.parseInt(setPlaceholdersAndArguments(item.options().dynamicAmount().get())); - if (amt <= 0) { - amt = 1; + int amt = i.getAmount(); + + if (item.options().dynamicAmount().isPresent()) { + try { + amt = Integer.parseInt(setPlaceholdersAndArguments(item.options().dynamicAmount().get())); + if (amt <= 0) { + amt = 1; + } + } catch (Exception exception) { + plugin.printStacktrace( + "Something went wrong while updating item in slot " + item.options().slot() + + ". Invalid dynamic amount: " + setPlaceholdersAndArguments(item.options().dynamicAmount().get()), + exception + ); } - } catch (Exception exception) { - plugin.printStacktrace( - "Something went wrong while updating item in slot " + item.options().slot() + - ". Invalid dynamic amount: " + setPlaceholdersAndArguments(item.options().dynamicAmount().get()), - exception - ); } - } - ItemMeta meta = i.getItemMeta(); + ItemMeta meta = i.getItemMeta(); - if (item.options().displayNameHasPlaceholders() && item.options().displayName().isPresent()) { - meta.setDisplayName(StringUtils.color(setPlaceholdersAndArguments(item.options().displayName().get()))); - } + if (item.options().displayNameHasPlaceholders() && item.options().displayName().isPresent()) { + meta.setDisplayName(StringUtils.color(setPlaceholdersAndArguments(item.options().displayName().get()))); + } - if (item.options().loreHasPlaceholders()) { - meta.setLore(item.getMenuItemLore(getHolder(), item.options().lore())); - } + if (item.options().loreHasPlaceholders()) { + meta.setLore(item.getMenuItemLore(getHolder(), item.options().lore())); + } - i.setItemMeta(meta); - i.setAmount(amt); + i.setItemMeta(meta); + i.setAmount(amt); + } } - } - } - }.runTaskTimerAsynchronously(plugin, 20L, - 20L * Menu.getMenuByName(menuName) - .map(Menu::options) - .map(MenuOptions::updateInterval) - .orElse(10)); + }, initialDelay, period + ); } public boolean isUpdating() { diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java index 2e49d5cd..5b0a8680 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -2,10 +2,10 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.hooks.ItemHook; +import com.extendedclip.deluxemenus.menu.options.CustomModelDataComponent; 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.menu.options.CustomModelDataComponent; import com.extendedclip.deluxemenus.nbt.NbtProvider; import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.ItemUtils; @@ -15,8 +15,8 @@ import org.bukkit.Color; import org.bukkit.FireworkEffect; import org.bukkit.Material; -import org.bukkit.Registry; import org.bukkit.NamespacedKey; +import org.bukkit.Registry; import org.bukkit.block.Banner; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.type.Light; @@ -45,14 +45,14 @@ import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.Base64; -import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Optional; import java.util.Objects; +import java.util.Optional; import java.util.logging.Level; import java.util.stream.Collectors; @@ -89,6 +89,7 @@ public static ItemStack base64ToItemStack(String data) { return null; } } + public ItemStack getItemStack(@NotNull final MenuHolder holder) { final Player viewer = holder.getViewer(); @@ -112,7 +113,6 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { } } - if (ItemUtils.isPlayerItem(lowercaseStringMaterial)) { final ItemStack playerItem = INVENTORY_ITEM_ACCESSORS.get(lowercaseStringMaterial).apply(viewer.getInventory()); diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/JavascriptRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/JavascriptRequirement.java index 56a67760..9fcc1ff2 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/JavascriptRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/JavascriptRequirement.java @@ -3,10 +3,10 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.menu.MenuHolder; import com.extendedclip.deluxemenus.utils.DebugLevel; -import java.util.logging.Level; import javax.script.ScriptEngineFactory; import javax.script.ScriptEngineManager; import javax.script.ScriptException; +import java.util.logging.Level; import org.bukkit.Bukkit; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.ServicePriority; diff --git a/src/main/java/com/extendedclip/deluxemenus/scheduler/UniversalRunnable.java b/src/main/java/com/extendedclip/deluxemenus/scheduler/UniversalRunnable.java new file mode 100644 index 00000000..adcb1cff --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/scheduler/UniversalRunnable.java @@ -0,0 +1,176 @@ +/* + * MIT License + * + * Copyright (c) 2023 Sevastjan + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.extendedclip.deluxemenus.scheduler; + +import com.extendedclip.deluxemenus.scheduler.scheduling.schedulers.TaskScheduler; +import com.extendedclip.deluxemenus.scheduler.scheduling.tasks.MyScheduledTask; +import org.bukkit.plugin.Plugin; + +/** Just modified BukkitRunnable */ +public abstract class UniversalRunnable implements Runnable { + MyScheduledTask task; + + public synchronized void cancel() throws IllegalStateException { + checkScheduled(); + task.cancel(); + } + + /** + * Returns true if this task has been cancelled. + * + * @return true if the task has been cancelled + * @throws IllegalStateException if task was not scheduled yet + */ + public synchronized boolean isCancelled() throws IllegalStateException { + checkScheduled(); + return task.isCancelled(); + } + + /** + * Schedules this in the Bukkit scheduler to run on next tick. + * + * @param plugin the reference to the plugin scheduling task + * @return {@link MyScheduledTask} + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see TaskScheduler#runTask(Runnable) + */ + + public synchronized MyScheduledTask runTask(Plugin plugin) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(UniversalScheduler.getScheduler(plugin).runTask(this)); + } + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

+ * Schedules this in the Bukkit scheduler to run asynchronously. + * + * @param plugin the reference to the plugin scheduling task + * @return {@link MyScheduledTask} + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see TaskScheduler#runTaskAsynchronously(Runnable) + */ + + public synchronized MyScheduledTask runTaskAsynchronously(Plugin plugin) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(UniversalScheduler.getScheduler(plugin).runTaskAsynchronously(this)); + } + + /** + * Schedules this to run after the specified number of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param delay the ticks to wait before running the task + * @return {@link MyScheduledTask} + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see TaskScheduler#runTaskLater(Runnable, long) + */ + + public synchronized MyScheduledTask runTaskLater(Plugin plugin, long delay) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(UniversalScheduler.getScheduler(plugin).runTaskLater(this, delay)); + } + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

+ * Schedules this to run asynchronously after the specified number of + * server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param delay the ticks to wait before running the task + * @return {@link MyScheduledTask} + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see TaskScheduler#runTaskLaterAsynchronously(Runnable, long) + */ + + public synchronized MyScheduledTask runTaskLaterAsynchronously(Plugin plugin, long delay) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(UniversalScheduler.getScheduler(plugin).runTaskLaterAsynchronously(this, delay)); + } + + /** + * Schedules this to repeatedly run until cancelled, starting after the + * specified number of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param delay the ticks to wait before running the task + * @param period the ticks to wait between runs + * @return {@link MyScheduledTask} + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see TaskScheduler#runTaskTimer(Runnable, long, long) + */ + + public synchronized MyScheduledTask runTaskTimer(Plugin plugin, long delay, long period) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(UniversalScheduler.getScheduler(plugin).runTaskTimer(this, delay, period)); + } + + /** + * Asynchronous tasks should never access any API in Bukkit. Great care + * should be taken to assure the thread-safety of asynchronous tasks. + *

+ * Schedules this to repeatedly run asynchronously until cancelled, + * starting after the specified number of server ticks. + * + * @param plugin the reference to the plugin scheduling task + * @param delay the ticks to wait before running the task for the first + * time + * @param period the ticks to wait between runs + * @return {@link MyScheduledTask} + * @throws IllegalArgumentException if plugin is null + * @throws IllegalStateException if this was already scheduled + * @see TaskScheduler#runTaskTimerAsynchronously(Runnable, long, long) + */ + + public synchronized MyScheduledTask runTaskTimerAsynchronously(Plugin plugin, long delay, long period) throws IllegalArgumentException, IllegalStateException { + checkNotYetScheduled(); + return setupTask(UniversalScheduler.getScheduler(plugin).runTaskTimerAsynchronously(this, delay, period)); + } + + private void checkScheduled() { + if (task == null) { + throw new IllegalStateException("Not scheduled yet"); + } + } + + private void checkNotYetScheduled() { + if (task != null) { + throw new IllegalStateException("Already scheduled"); + } + } + + private MyScheduledTask setupTask(final MyScheduledTask task) { + this.task = task; + return task; + } + +} diff --git a/src/main/java/com/extendedclip/deluxemenus/scheduler/UniversalScheduler.java b/src/main/java/com/extendedclip/deluxemenus/scheduler/UniversalScheduler.java new file mode 100644 index 00000000..8949a140 --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/scheduler/UniversalScheduler.java @@ -0,0 +1,42 @@ +/* + * MIT License + * + * Copyright (c) 2023 Sevastjan + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.extendedclip.deluxemenus.scheduler; + +import com.extendedclip.deluxemenus.scheduler.bukkit.BukkitScheduler; +import com.extendedclip.deluxemenus.scheduler.folia.FoliaScheduler; +import com.extendedclip.deluxemenus.scheduler.paper.PaperScheduler; +import com.extendedclip.deluxemenus.scheduler.scheduling.schedulers.TaskScheduler; +import com.extendedclip.deluxemenus.scheduler.utils.JavaUtil; +import org.bukkit.plugin.Plugin; + +public class UniversalScheduler { + private static final boolean IS_FOLIA = JavaUtil.classExists("io.papermc.paper.threadedregions.RegionizedServer"); + private static final boolean IS_CANVAS = JavaUtil.classExists("io.canvasmc.canvas.server.ThreadedServer"); + private static final boolean IS_EXPANDED_SCHEDULING_AVAILABLE = JavaUtil.classExists("io.papermc.paper.threadedregions.scheduler.ScheduledTask"); + + public static TaskScheduler getScheduler(Plugin plugin) { + return IS_FOLIA || IS_CANVAS ? new FoliaScheduler(plugin) : (IS_EXPANDED_SCHEDULING_AVAILABLE ? new PaperScheduler(plugin) : new BukkitScheduler(plugin)); + } + +} diff --git a/src/main/java/com/extendedclip/deluxemenus/scheduler/bukkit/BukkitScheduledTask.java b/src/main/java/com/extendedclip/deluxemenus/scheduler/bukkit/BukkitScheduledTask.java new file mode 100644 index 00000000..b79deffb --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/scheduler/bukkit/BukkitScheduledTask.java @@ -0,0 +1,71 @@ +/* + * MIT License + * + * Copyright (c) 2023 Sevastjan + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.extendedclip.deluxemenus.scheduler.bukkit; + +import com.extendedclip.deluxemenus.scheduler.scheduling.tasks.MyScheduledTask; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitTask; + +public class BukkitScheduledTask implements MyScheduledTask { + + BukkitTask task; + + boolean isRepeating; + + public BukkitScheduledTask(final BukkitTask task) { + this.task = task; + this.isRepeating = false; + } + + public BukkitScheduledTask(final BukkitTask task, boolean isRepeating) { + this.task = task; + this.isRepeating = isRepeating; + } + + @Override + public void cancel() { + task.cancel(); + } + + @Override + public boolean isCancelled() { + return task.isCancelled(); + } + + @Override + public Plugin getOwningPlugin() { + return task.getOwner(); + } + + @Override + public boolean isCurrentlyRunning() { + return Bukkit.getServer().getScheduler().isCurrentlyRunning(this.task.getTaskId()); //There's no other way. Fuck bukkit + } + + @Override + public boolean isRepeatingTask() { + return isRepeating; + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/scheduler/bukkit/BukkitScheduler.java b/src/main/java/com/extendedclip/deluxemenus/scheduler/bukkit/BukkitScheduler.java new file mode 100644 index 00000000..4c562dd0 --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/scheduler/bukkit/BukkitScheduler.java @@ -0,0 +1,129 @@ +/* + * MIT License + * + * Copyright (c) 2023 Sevastjan + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.extendedclip.deluxemenus.scheduler.bukkit; + +import com.extendedclip.deluxemenus.scheduler.scheduling.schedulers.TaskScheduler; +import com.extendedclip.deluxemenus.scheduler.scheduling.tasks.MyScheduledTask; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.Plugin; + +public class BukkitScheduler implements TaskScheduler { + final Plugin plugin; + + public BukkitScheduler(Plugin plugin) { + this.plugin = plugin; + } + + @Override + public boolean isGlobalThread() { + return Bukkit.getServer().isPrimaryThread(); + } + + @Override + public boolean isEntityThread(Entity entity) { + return Bukkit.getServer().isPrimaryThread(); + } + + @Override + public boolean isRegionThread(Location location) { + return Bukkit.getServer().isPrimaryThread(); + } + + @Override + public MyScheduledTask runTask(Runnable runnable) { + return new BukkitScheduledTask(Bukkit.getScheduler().runTask(plugin, runnable)); + } + + @Override + public MyScheduledTask runTaskLater(Runnable runnable, long delay) { + return new BukkitScheduledTask(Bukkit.getScheduler().runTaskLater(plugin, runnable, delay)); + } + + @Override + public MyScheduledTask runTaskTimer(Runnable runnable, long delay, long period) { + return new BukkitScheduledTask(Bukkit.getScheduler().runTaskTimer(plugin, runnable, delay, period)); + } + + @Override + public MyScheduledTask runTaskAsynchronously(Runnable runnable) { + return new BukkitScheduledTask(Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable)); + } + + @Override + public MyScheduledTask runTaskLaterAsynchronously(Runnable runnable, long delay) { + return new BukkitScheduledTask(Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, runnable, delay)); + } + + @Override + public MyScheduledTask runTaskTimerAsynchronously(Runnable runnable, long delay, long period) { + return new BukkitScheduledTask(Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, runnable, delay, period)); + } + + //Useless? Or... + public MyScheduledTask runTask(Plugin plugin, Runnable runnable) { + return new BukkitScheduledTask(Bukkit.getScheduler().runTask(plugin, runnable)); + } + + @Override + public MyScheduledTask runTaskLater(Plugin plugin, Runnable runnable, long delay) { + return new BukkitScheduledTask(Bukkit.getScheduler().runTaskLater(plugin, runnable, delay)); + } + + @Override + public MyScheduledTask runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) { + return new BukkitScheduledTask(Bukkit.getScheduler().runTaskTimer(plugin, runnable, delay, period)); + } + + @Override + public MyScheduledTask runTaskAsynchronously(Plugin plugin, Runnable runnable) { + return new BukkitScheduledTask(Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable)); + } + + @Override + public MyScheduledTask runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) { + return new BukkitScheduledTask(Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, runnable, delay)); + } + + @Override + public MyScheduledTask runTaskTimerAsynchronously(Plugin plugin, Runnable runnable, long delay, long period) { + return new BukkitScheduledTask(Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, runnable, delay, period)); + } + + @Override + public void execute(Runnable runnable) { + Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, runnable); + } + + @Override + public void cancelTasks() { + Bukkit.getScheduler().cancelTasks(plugin); + } + + @Override + public void cancelTasks(Plugin plugin) { + Bukkit.getScheduler().cancelTasks(plugin); + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/scheduler/folia/FoliaScheduledTask.java b/src/main/java/com/extendedclip/deluxemenus/scheduler/folia/FoliaScheduledTask.java new file mode 100644 index 00000000..aaa517af --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/scheduler/folia/FoliaScheduledTask.java @@ -0,0 +1,57 @@ +/* + * MIT License + * + * Copyright (c) 2023 Sevastjan + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.extendedclip.deluxemenus.scheduler.folia; + +import com.extendedclip.deluxemenus.scheduler.scheduling.tasks.MyScheduledTask; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import org.bukkit.plugin.Plugin; + +public class FoliaScheduledTask implements MyScheduledTask { + private final ScheduledTask task; + + public FoliaScheduledTask(final ScheduledTask task) { + this.task = task; + } + + public void cancel() { + this.task.cancel(); + } + + public boolean isCancelled() { + return this.task.isCancelled(); + } + + public Plugin getOwningPlugin() { + return this.task.getOwningPlugin(); + } + + public boolean isCurrentlyRunning() { + final ScheduledTask.ExecutionState state = this.task.getExecutionState(); + return state == ScheduledTask.ExecutionState.RUNNING || state == ScheduledTask.ExecutionState.CANCELLED_RUNNING; + } + + public boolean isRepeatingTask() { + return this.task.isRepeatingTask(); + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/scheduler/folia/FoliaScheduler.java b/src/main/java/com/extendedclip/deluxemenus/scheduler/folia/FoliaScheduler.java new file mode 100644 index 00000000..cbc3d26c --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/scheduler/folia/FoliaScheduler.java @@ -0,0 +1,220 @@ +/* + * MIT License + * + * Copyright (c) 2023 Sevastjan + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.extendedclip.deluxemenus.scheduler.folia; + +import com.extendedclip.deluxemenus.scheduler.scheduling.schedulers.TaskScheduler; +import com.extendedclip.deluxemenus.scheduler.scheduling.tasks.MyScheduledTask; +import io.papermc.paper.threadedregions.scheduler.AsyncScheduler; +import io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler; +import io.papermc.paper.threadedregions.scheduler.RegionScheduler; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.Plugin; + +import java.util.concurrent.TimeUnit; + +public class FoliaScheduler implements TaskScheduler { + + final Plugin plugin; + + public FoliaScheduler(Plugin plugin) { + this.plugin = plugin; + } + + private final RegionScheduler regionScheduler = Bukkit.getServer().getRegionScheduler(); + private final GlobalRegionScheduler globalRegionScheduler = Bukkit.getServer().getGlobalRegionScheduler(); + private final AsyncScheduler asyncScheduler = Bukkit.getServer().getAsyncScheduler(); + + @Override + public boolean isGlobalThread() { + return Bukkit.getServer().isGlobalTickThread(); + } + + @Override + public boolean isTickThread() { + return Bukkit.getServer().isPrimaryThread(); // The Paper implementation checks whether this is a tick thread, this method exists to avoid confusion. + } + + @Override + public boolean isEntityThread(Entity entity) { + return Bukkit.getServer().isOwnedByCurrentRegion(entity); + } + + @Override + public boolean isRegionThread(Location location) { + return Bukkit.getServer().isOwnedByCurrentRegion(location); + } + + @Override + public MyScheduledTask runTask(Runnable runnable) { + return new FoliaScheduledTask(globalRegionScheduler.run(plugin, task -> runnable.run())); + } + + @Override + public MyScheduledTask runTaskLater(Runnable runnable, long delay) { + //Folia exception: Delay ticks may not be <= 0 + if (delay <= 0) { + return runTask(runnable); + } + return new FoliaScheduledTask(globalRegionScheduler.runDelayed(plugin, task -> runnable.run(), delay)); + } + + @Override + public MyScheduledTask runTaskTimer(Runnable runnable, long delay, long period) { + //Folia exception: Delay ticks may not be <= 0 + delay = getOneIfNotPositive(delay); + return new FoliaScheduledTask(globalRegionScheduler.runAtFixedRate(plugin, task -> runnable.run(), delay, period)); + } + + @Override + public MyScheduledTask runTask(Plugin plugin, Runnable runnable) { + return new FoliaScheduledTask(globalRegionScheduler.run(plugin, task -> runnable.run())); + } + + @Override + public MyScheduledTask runTaskLater(Plugin plugin, Runnable runnable, long delay) { + //Folia exception: Delay ticks may not be <= 0 + if (delay <= 0) { + return runTask(plugin, runnable); + } + return new FoliaScheduledTask(globalRegionScheduler.runDelayed(plugin, task -> runnable.run(), delay)); + } + + @Override + public MyScheduledTask runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) { + //Folia exception: Delay ticks may not be <= 0 + delay = getOneIfNotPositive(delay); + return new FoliaScheduledTask(globalRegionScheduler.runAtFixedRate(plugin, task -> runnable.run(), delay, period)); + } + + @Override + public MyScheduledTask runTask(Location location, Runnable runnable) { + return new FoliaScheduledTask(regionScheduler.run(plugin, location, task -> runnable.run())); + } + + @Override + public MyScheduledTask runTaskLater(Location location, Runnable runnable, long delay) { + //Folia exception: Delay ticks may not be <= 0 + if (delay <= 0) { + return runTask(runnable); + } + return new FoliaScheduledTask(regionScheduler.runDelayed(plugin, location, task -> runnable.run(), delay)); + } + + @Override + public MyScheduledTask runTaskTimer(Location location, Runnable runnable, long delay, long period) { + //Folia exception: Delay ticks may not be <= 0 + delay = getOneIfNotPositive(delay); + return new FoliaScheduledTask(regionScheduler.runAtFixedRate(plugin, location, task -> runnable.run(), delay, period)); + } + + @Override + public MyScheduledTask runTask(Entity entity, Runnable runnable) { + return new FoliaScheduledTask(entity.getScheduler().run(plugin, task -> runnable.run(), null)); + } + + @Override + public MyScheduledTask runTaskLater(Entity entity, Runnable runnable, long delay) { + //Folia exception: Delay ticks may not be <= 0 + if (delay <= 0) { + return runTask(entity, runnable); + } + return new FoliaScheduledTask(entity.getScheduler().runDelayed(plugin, task -> runnable.run(), null, delay)); + } + + @Override + public MyScheduledTask runTaskTimer(Entity entity, Runnable runnable, long delay, long period) { + //Folia exception: Delay ticks may not be <= 0 + delay = getOneIfNotPositive(delay); + return new FoliaScheduledTask(entity.getScheduler().runAtFixedRate(plugin, task -> runnable.run(), null, delay, period)); + } + + @Override + public MyScheduledTask runTaskAsynchronously(Runnable runnable) { + return new FoliaScheduledTask(asyncScheduler.runNow(plugin, task -> runnable.run())); + } + + @Override + public MyScheduledTask runTaskLaterAsynchronously(Runnable runnable, long delay) { + //Folia exception: Delay ticks may not be <= 0 + delay = getOneIfNotPositive(delay); + return new FoliaScheduledTask(asyncScheduler.runDelayed(plugin, task -> runnable.run(), delay * 50L, TimeUnit.MILLISECONDS)); + } + + @Override + public MyScheduledTask runTaskTimerAsynchronously(Runnable runnable, long delay, long period) { + return new FoliaScheduledTask(asyncScheduler.runAtFixedRate(plugin, task -> runnable.run(), delay * 50, period * 50, TimeUnit.MILLISECONDS)); + } + + @Override + public MyScheduledTask runTaskAsynchronously(Plugin plugin, Runnable runnable) { + return new FoliaScheduledTask(asyncScheduler.runNow(plugin, task -> runnable.run())); + } + + @Override + public MyScheduledTask runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) { + //Folia exception: Delay ticks may not be <= 0 + delay = getOneIfNotPositive(delay); + return new FoliaScheduledTask(asyncScheduler.runDelayed(plugin, task -> runnable.run(), delay * 50L, TimeUnit.MILLISECONDS)); + } + + @Override + public MyScheduledTask runTaskTimerAsynchronously(Plugin plugin, Runnable runnable, long delay, long period) { + //Folia exception: Delay ticks may not be <= 0 + delay = getOneIfNotPositive(delay); + return new FoliaScheduledTask(asyncScheduler.runAtFixedRate(plugin, task -> runnable.run(), delay * 50, period * 50, TimeUnit.MILLISECONDS)); + } + + @Override + public void execute(Runnable runnable) { + globalRegionScheduler.execute(plugin, runnable); + } + + @Override + public void execute(Location location, Runnable runnable) { + regionScheduler.execute(plugin, location, runnable); + } + + @Override + public void execute(Entity entity, Runnable runnable) { + entity.getScheduler().execute(plugin, runnable, null, 1L); + } + + @Override + public void cancelTasks() { + globalRegionScheduler.cancelTasks(plugin); + asyncScheduler.cancelTasks(plugin); + } + + @Override + public void cancelTasks(Plugin plugin) { + globalRegionScheduler.cancelTasks(plugin); + asyncScheduler.cancelTasks(plugin); + } + + private long getOneIfNotPositive(long x) { + return x <= 0 ? 1L : x; + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/scheduler/paper/PaperScheduler.java b/src/main/java/com/extendedclip/deluxemenus/scheduler/paper/PaperScheduler.java new file mode 100644 index 00000000..6920ff97 --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/scheduler/paper/PaperScheduler.java @@ -0,0 +1,41 @@ +/* + * MIT License + * + * Copyright (c) 2023 Sevastjan + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.extendedclip.deluxemenus.scheduler.paper; + +import com.extendedclip.deluxemenus.scheduler.folia.FoliaScheduler; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +// Thanks to Towny +public class PaperScheduler extends FoliaScheduler { + public PaperScheduler(Plugin plugin) { + super(plugin); + } + + @Override + public boolean isGlobalThread() { + // isGlobalThread does not exist on paper, match the bukkit task scheduler's behaviour. + return Bukkit.getServer().isPrimaryThread(); + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/scheduler/scheduling/schedulers/TaskScheduler.java b/src/main/java/com/extendedclip/deluxemenus/scheduler/scheduling/schedulers/TaskScheduler.java new file mode 100644 index 00000000..22b77198 --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/scheduler/scheduling/schedulers/TaskScheduler.java @@ -0,0 +1,346 @@ +/* + * MIT License + * + * Copyright (c) 2023 Sevastjan + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.extendedclip.deluxemenus.scheduler.scheduling.schedulers; + +import com.extendedclip.deluxemenus.scheduler.scheduling.tasks.MyScheduledTask; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.Plugin; + +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +public interface TaskScheduler { + + /** + * Folia: Returns whether the current thread is ticking the global region
+ * Paper & Bukkit: Returns {@link org.bukkit.Server#isPrimaryThread} + */ + boolean isGlobalThread(); + + /** + * @return {@link org.bukkit.Server#isPrimaryThread} + */ + default boolean isTickThread() { + return Bukkit.getServer().isPrimaryThread(); + } + + /** + * Folia & Paper: Returns whether the current thread is ticking a region and that the region + * being ticked owns the specified entity. Note that this function is the only appropriate method of + * checking for ownership of an entity, as retrieving the entity's location is undefined unless the + * entity is owned by the current region + *

+ * Bukkit: returns {@link org.bukkit.Server#isPrimaryThread} + * + * @param entity Specified entity + */ + boolean isEntityThread(Entity entity); + + /** + * Folia & Paper: Returns whether the current thread is ticking a region and that the region + * being ticked owns the chunk at the specified world and block position as included in the specified location + *

+ * Bukkit: returns {@link org.bukkit.Server#isPrimaryThread} + * + * @param location Specified location, must have a non-null world. + */ + boolean isRegionThread(Location location); + + /** + * Schedules a task to be executed on the next tick
+ * Folia & Paper: ...on the global region
+ * Bukkit: ...on the main thread + * + * @param runnable The task to execute + */ + MyScheduledTask runTask(Runnable runnable); + + /** + * Schedules a task to be executed after the specified delay in ticks
+ * Folia & Paper: ...on the global region
+ * Bukkit: ...on the main thread + * + * @param runnable The task to execute + * @param delay The delay, in ticks + */ + MyScheduledTask runTaskLater(Runnable runnable, long delay); + + /** + * Schedules a repeating task to be executed after the initial delay with the specified period
+ * Folia & Paper: ...on the global region
+ * Bukkit: ...on the main thread + * + * @param runnable The task to execute + * @param delay The initial delay, in ticks. + * @param period The period, in ticks. + */ + MyScheduledTask runTaskTimer(Runnable runnable, long delay, long period); + + /** + * Deprecated: use {@link #runTask(Runnable)} + */ + @Deprecated + default MyScheduledTask runTask(Plugin plugin, Runnable runnable) { + return runTask(runnable); + } + + /** + * Deprecated: use {@link #runTaskLater(Runnable, long)} + */ + @Deprecated + default MyScheduledTask runTaskLater(Plugin plugin, Runnable runnable, long delay) { + return runTaskLater(runnable, delay); + } + + /** + * Deprecated: use {@link #runTaskTimer(Runnable, long, long)} + */ + @Deprecated + default MyScheduledTask runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) { + return runTaskTimer(runnable, delay, period); + } + + /** + * Folia & Paper: Schedules a task to be executed on the region which owns the location on the next tick + *

+ * Bukkit: same as {@link #runTask(Runnable)} + * + * @param location The location which the region executing should own + * @param runnable The task to execute + */ + default MyScheduledTask runTask(Location location, Runnable runnable) { + return runTask(runnable); + } + + /** + * Folia & Paper: Schedules a task to be executed on the region which owns the location after the + * specified delay in ticks + *

+ * Bukkit: same as {@link #runTaskLater(Runnable, long)} + * + * @param location The location which the region executing should own + * @param runnable The task to execute + * @param delay The delay, in ticks. + */ + default MyScheduledTask runTaskLater(Location location, Runnable runnable, long delay) { + return runTaskLater(runnable, delay); + } + + /** + * Folia & Paper: Schedules a repeating task to be executed on the region which owns the location + * after the initial delay with the specified period + *

+ * Bukkit: same as {@link #runTaskTimer(Runnable, long, long)} + * + * @param location The location which the region executing should own + * @param runnable The task to execute + * @param delay The initial delay, in ticks. + * @param period The period, in ticks. + */ + default MyScheduledTask runTaskTimer(Location location, Runnable runnable, long delay, long period) { + return runTaskTimer(runnable, delay, period); + } + + /** + * Deprecated: use {@link #runTaskLater(Runnable, long)} + */ + @Deprecated + default MyScheduledTask scheduleSyncDelayedTask(Runnable runnable, long delay) { + return runTaskLater(runnable, delay); + } + + /** + * Deprecated: use {@link #execute(Runnable)} or {@link #runTask(Runnable)} + */ + @Deprecated + default MyScheduledTask scheduleSyncDelayedTask(Runnable runnable) { + return runTask(runnable); + } + + /** + * Deprecated: use {@link #runTaskTimer(Runnable, long, long)} + */ + @Deprecated + default MyScheduledTask scheduleSyncRepeatingTask(Runnable runnable, long delay, long period) { + return runTaskTimer(runnable, delay, period); + } + + /** + * Folia & Paper: Schedules a task to be executed on the region which owns the location + * of given entity on the next tick + *

+ * Bukkit: same as {@link #runTask(Runnable)} + * + * @param entity The entity whose location the region executing should own + * @param runnable The task to execute + */ + default MyScheduledTask runTask(Entity entity, Runnable runnable) { + return runTask(runnable); + } + + /** + * Folia & Paper: Schedules a task to be executed on the region which owns the location + * of given entity after the specified delay in ticks + *

+ * Bukkit: same as {@link #runTaskLater(Runnable, long)} + * + * @param entity The entity whose location the region executing should own + * @param runnable The task to execute + * @param delay The delay, in ticks. + */ + default MyScheduledTask runTaskLater(Entity entity, Runnable runnable, long delay) { + return runTaskLater(runnable, delay); + } + + /** + * Folia & Paper: Schedules a repeating task to be executed on the region which owns the + * location of given entity after the initial delay with the specified period + *

+ * Bukkit: same as {@link #runTaskTimer(Runnable, long, long)} + * + * @param entity The entity whose location the region executing should own + * @param runnable The task to execute + * @param delay The initial delay, in ticks. + * @param period The period, in ticks. + */ + default MyScheduledTask runTaskTimer(Entity entity, Runnable runnable, long delay, long period) { + return runTaskTimer(runnable, delay, period); + } + + /** + * Schedules the specified task to be executed asynchronously immediately + * + * @param runnable The task to execute + * @return The {@link MyScheduledTask} that represents the scheduled task + */ + MyScheduledTask runTaskAsynchronously(Runnable runnable); + + /** + * Schedules the specified task to be executed asynchronously after the time delay has passed + * + * @param runnable The task to execute + * @param delay The time delay to pass before the task should be executed + * @return The {@link MyScheduledTask} that represents the scheduled task + */ + MyScheduledTask runTaskLaterAsynchronously(Runnable runnable, long delay); + + /** + * Schedules the specified task to be executed asynchronously after the initial delay has passed, + * and then periodically executed with the specified period + * + * @param runnable The task to execute + * @param delay The time delay to pass before the first execution of the task, in ticks + * @param period The time between task executions after the first execution of the task, in ticks + * @return The {@link MyScheduledTask} that represents the scheduled task + */ + MyScheduledTask runTaskTimerAsynchronously(Runnable runnable, long delay, long period); + + /** + * Deprecated: use {@link #runTaskAsynchronously(Runnable)} + */ + @Deprecated + default MyScheduledTask runTaskAsynchronously(Plugin plugin, Runnable runnable) { + return runTaskAsynchronously(runnable); + } + + /** + * Deprecated: use {@link #runTaskLaterAsynchronously(Runnable, long)} + */ + @Deprecated + default MyScheduledTask runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) { + return runTaskLaterAsynchronously(runnable, delay); + } + + /** + * Deprecated: use {@link #runTaskTimerAsynchronously(Runnable, long, long)} + */ + @Deprecated + default MyScheduledTask runTaskTimerAsynchronously(Plugin plugin, Runnable runnable, long delay, long period) { + return runTaskTimerAsynchronously(runnable, delay, period); + } + + /** + * Calls a method on the main thread and returns a Future object. This task will be executed + * by the main(Bukkit)/global(Folia&Paper) server thread. + *

+ * Note: The Future.get() methods must NOT be called from the main thread. + *

+ * Note2: There is at least an average of 10ms latency until the isDone() method returns true. + * + * @param task Task to be executed + */ + default Future callSyncMethod(final Callable task) { + CompletableFuture completableFuture = new CompletableFuture<>(); + execute(() -> { + try { + completableFuture.complete(task.call()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + return completableFuture; + } + + /** + * Schedules a task to be executed on the global region + * + * @param runnable The task to execute + */ + void execute(Runnable runnable); + + /** + * Schedules a task to be executed on the region which owns the location + * + * @param location The location which the region executing should own + * @param runnable The task to execute + */ + default void execute(Location location, Runnable runnable) { + execute(runnable); + } + + /** + * Schedules a task to be executed on the region which owns the location of given entity + * + * @param entity The entity which location the region executing should own + * @param runnable The task to execute + */ + default void execute(Entity entity, Runnable runnable) { + execute(runnable); + } + + /** + * Attempts to cancel all tasks scheduled by this plugin + */ + void cancelTasks(); + + /** + * Attempts to cancel all tasks scheduled by the specified plugin + * + * @param plugin specified plugin + */ + void cancelTasks(Plugin plugin); +} diff --git a/src/main/java/com/extendedclip/deluxemenus/scheduler/scheduling/tasks/MyScheduledTask.java b/src/main/java/com/extendedclip/deluxemenus/scheduler/scheduling/tasks/MyScheduledTask.java new file mode 100644 index 00000000..47ae40fa --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/scheduler/scheduling/tasks/MyScheduledTask.java @@ -0,0 +1,54 @@ +/* + * MIT License + * + * Copyright (c) 2023 Sevastjan + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.extendedclip.deluxemenus.scheduler.scheduling.tasks; + +import org.bukkit.plugin.Plugin; + +public interface MyScheduledTask { + + /** + * Cancels executing task + */ + void cancel(); + + /** + * @return true if task is cancelled, false otherwise + */ + boolean isCancelled(); + + /** + * @return The plugin under which the task was scheduled. + */ + Plugin getOwningPlugin(); + + /** + * @return true if task is currently executing, false otherwise + */ + boolean isCurrentlyRunning(); + + /** + * @return true if task is repeating, false otherwise + */ + boolean isRepeatingTask(); +} diff --git a/src/main/java/com/extendedclip/deluxemenus/scheduler/utils/JavaUtil.java b/src/main/java/com/extendedclip/deluxemenus/scheduler/utils/JavaUtil.java new file mode 100644 index 00000000..b5b85152 --- /dev/null +++ b/src/main/java/com/extendedclip/deluxemenus/scheduler/utils/JavaUtil.java @@ -0,0 +1,12 @@ +package com.extendedclip.deluxemenus.scheduler.utils; + +public class JavaUtil { + public static boolean classExists(String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java b/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java index e30fe77b..4db347af 100644 --- a/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java +++ b/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java @@ -15,7 +15,6 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.scheduler.BukkitRunnable; import org.jetbrains.annotations.NotNull; public class UpdateChecker extends Listener { @@ -32,21 +31,11 @@ public class UpdateChecker extends Listener { public UpdateChecker(final @NotNull DeluxeMenus instance) { super(instance); - new BukkitRunnable() { - @Override - public void run() { - if (check()) { - new BukkitRunnable() { - - @Override - public void run() { - register(); - } - }.runTask(plugin); - } + plugin.getScheduler().runTaskAsynchronously(() -> { + if (check()) { + plugin.getScheduler().runTask(this::register); } - - }.runTaskAsynchronously(plugin); + }); } @EventHandler(priority = EventPriority.MONITOR) diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java index 870e8312..b9cab3cc 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java @@ -33,7 +33,6 @@ public static boolean isPlaceholderOption(@NotNull final String material) { /** * Checks if the string starts with the substring "stack-". The check is case-insensitive. * - * @param itemstack The string to check * @return true if the string starts with "stack-", false otherwise */ public static boolean isItemStackOption(@NotNull final String material) { diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java index 6ae95804..81fd30f8 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/StringUtils.java @@ -3,7 +3,6 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; - import me.clip.placeholderapi.PlaceholderAPI; import net.md_5.bungee.api.ChatColor; import org.bukkit.Color; diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 22d79f63..8c7c2d83 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,7 +1,8 @@ -api-version: 1.13 name: DeluxeMenus main: com.extendedclip.deluxemenus.DeluxeMenus version: ${version} +api-version: 1.13 +folia-supported: true authors: [ HelpChat ] softdepend: [ PlaceholderAPI, Vault, HeadDatabase, ItemsAdder, Nexo, Oraxen, ExecutableItems, ExecutableBlocks, Score, SimpleItemGenerator, MMOItems ] description: All in one inventory menu system From 3ee0889da39c4a103aafff8bd13ec50915f17685 Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Wed, 20 Aug 2025 00:39:26 -0400 Subject: [PATCH 02/24] Revert import modification --- .../deluxemenus/action/ClickActionTask.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index 6e717ba5..aedeb629 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -5,14 +5,24 @@ import com.extendedclip.deluxemenus.menu.MenuHolder; import com.extendedclip.deluxemenus.persistentmeta.PersistentMetaHandler; import com.extendedclip.deluxemenus.scheduler.UniversalRunnable; -import com.extendedclip.deluxemenus.utils.*; +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; import org.bukkit.Bukkit; import org.bukkit.Sound; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import java.util.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; import java.util.logging.Level; public class ClickActionTask extends UniversalRunnable { From 7f75a295dc38d972ebd07b57fac5ede4b614228e Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Wed, 20 Aug 2025 00:53:29 -0400 Subject: [PATCH 03/24] Codestyle change --- src/main/java/com/extendedclip/deluxemenus/menu/Menu.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java index be0de82f..0c81fe11 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java @@ -391,7 +391,7 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map { - if(options.refresh()) { + if (options.refresh()) { holder.startRefreshTask(); } From a6ee4bba0f4d962e0704dbfa9ca75595a11b0ad3 Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Fri, 22 Aug 2025 20:22:36 -0400 Subject: [PATCH 04/24] Correct/adjust Javadoc --- src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java index b9cab3cc..08de3186 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java @@ -33,6 +33,7 @@ public static boolean isPlaceholderOption(@NotNull final String material) { /** * Checks if the string starts with the substring "stack-". The check is case-insensitive. * + * @param material The string to check * @return true if the string starts with "stack-", false otherwise */ public static boolean isItemStackOption(@NotNull final String material) { From cd40a017273a665dc000931a0edba9b84a2e83e1 Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Fri, 22 Aug 2025 21:05:00 -0400 Subject: [PATCH 05/24] Correct scheduling format for all classes --- .../deluxemenus/action/ClickActionTask.java | 7 +++-- .../deluxemenus/dupe/DupeFixer.java | 5 ++- .../deluxemenus/hooks/MMOItemsHook.java | 5 ++- .../deluxemenus/listener/PlayerListener.java | 5 ++- .../extendedclip/deluxemenus/menu/Menu.java | 31 ++++++++++--------- .../deluxemenus/menu/MenuHolder.java | 14 ++++++--- .../updatechecker/UpdateChecker.java | 11 ++++--- 7 files changed, 50 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index aedeb629..a493cd1a 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -5,6 +5,7 @@ import com.extendedclip.deluxemenus.menu.MenuHolder; import com.extendedclip.deluxemenus.persistentmeta.PersistentMetaHandler; import com.extendedclip.deluxemenus.scheduler.UniversalRunnable; +import com.extendedclip.deluxemenus.scheduler.scheduling.schedulers.TaskScheduler; import com.extendedclip.deluxemenus.utils.AdventureUtils; import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.ExpUtils; @@ -28,6 +29,7 @@ public class ClickActionTask extends UniversalRunnable { private final DeluxeMenus plugin; + private final TaskScheduler scheduler; private final UUID uuid; private final ActionType actionType; private final String exec; @@ -46,6 +48,7 @@ public ClickActionTask( final boolean parsePlaceholdersAfterArguments ) { this.plugin = plugin; + this.scheduler = plugin.getScheduler(); this.uuid = uuid; this.actionType = actionType; this.exec = exec; @@ -61,7 +64,7 @@ public void run() { return; } - plugin.getScheduler().runTask(player, () -> { + scheduler.runTask(player, () -> { final Optional holder = Menu.getMenuHolder(player); final Player target = holder.isPresent() && holder.get().getPlaceholderPlayer() != null @@ -116,7 +119,7 @@ public void run() { break; case CONSOLE: - plugin.getScheduler().runTask(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable)); + scheduler.runTask(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable)); break; case MINI_MESSAGE: diff --git a/src/main/java/com/extendedclip/deluxemenus/dupe/DupeFixer.java b/src/main/java/com/extendedclip/deluxemenus/dupe/DupeFixer.java index 4b8739cf..14193418 100644 --- a/src/main/java/com/extendedclip/deluxemenus/dupe/DupeFixer.java +++ b/src/main/java/com/extendedclip/deluxemenus/dupe/DupeFixer.java @@ -2,6 +2,7 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.listener.Listener; +import com.extendedclip.deluxemenus.scheduler.scheduling.schedulers.TaskScheduler; import com.extendedclip.deluxemenus.utils.DebugLevel; import org.bukkit.event.EventHandler; import org.bukkit.event.entity.EntityPickupItemEvent; @@ -18,10 +19,12 @@ */ public class DupeFixer extends Listener { + private final TaskScheduler scheduler; private final MenuItemMarker marker; public DupeFixer(@NotNull final DeluxeMenus plugin, @NotNull final MenuItemMarker marker) { super(plugin); + this.scheduler = plugin.getScheduler(); this.marker = marker; } @@ -55,7 +58,7 @@ private void onDrop(@NotNull final PlayerDropItemEvent event) { @EventHandler private void onLogin(@NotNull final PlayerJoinEvent event) { - plugin.getScheduler().runTaskLater(() -> { + scheduler.runTaskLater(() -> { for (final ItemStack itemStack : event.getPlayer().getInventory().getContents()) { if (itemStack == null) continue; if (!marker.isMarked(itemStack)) continue; diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java index f9e89b7b..ba570781 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java @@ -2,6 +2,7 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.cache.SimpleCache; +import com.extendedclip.deluxemenus.scheduler.scheduling.schedulers.TaskScheduler; import com.extendedclip.deluxemenus.utils.DebugLevel; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -17,9 +18,11 @@ public class MMOItemsHook implements ItemHook, SimpleCache { private final Map cache = new ConcurrentHashMap<>(); private final DeluxeMenus plugin; + private final TaskScheduler scheduler; public MMOItemsHook(final @NotNull DeluxeMenus plugin) { this.plugin = plugin; + this.scheduler = plugin.getScheduler(); } @Override @@ -45,7 +48,7 @@ public ItemStack getItem(@NotNull final String... arguments) { ItemStack mmoItem = null; try { - mmoItem = plugin.getScheduler().callSyncMethod(() -> { + mmoItem = scheduler.callSyncMethod(() -> { ItemStack item = MMOItems.plugin.getItem(itemType, splitArgs[1]); if (item == null) { diff --git a/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java b/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java index 87a7c1f6..89b417b2 100644 --- a/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java +++ b/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java @@ -6,6 +6,7 @@ import com.extendedclip.deluxemenus.menu.MenuHolder; import com.extendedclip.deluxemenus.menu.MenuItem; import com.extendedclip.deluxemenus.requirement.RequirementList; +import com.extendedclip.deluxemenus.scheduler.scheduling.schedulers.TaskScheduler; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import org.bukkit.entity.Player; @@ -25,6 +26,7 @@ public class PlayerListener extends Listener { + private final TaskScheduler scheduler; private final Cache cache = CacheBuilder.newBuilder().expireAfterWrite(75, TimeUnit.MILLISECONDS).build(); // This is so dumb. Mojang fix your shit. @@ -32,6 +34,7 @@ public class PlayerListener extends Listener { public PlayerListener(@NotNull final DeluxeMenus plugin) { super(plugin); + this.scheduler = plugin.getScheduler(); } @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) @@ -91,7 +94,7 @@ public void onClose(InventoryCloseEvent event) { final Player player = (Player) event.getPlayer(); if (Menu.isInMenu(player)) { - plugin.getScheduler().runTaskLater(player, () -> { + scheduler.runTaskLater(player, () -> { Menu.closeMenu(plugin, player, false); Menu.cleanInventory(plugin, player); player.updateInventory(); diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java index 0c81fe11..6ec238bc 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java @@ -6,6 +6,7 @@ import com.extendedclip.deluxemenus.menu.command.RegistrableMenuCommand; import com.extendedclip.deluxemenus.menu.options.MenuOptions; import com.extendedclip.deluxemenus.requirement.RequirementList; +import com.extendedclip.deluxemenus.scheduler.scheduling.schedulers.TaskScheduler; import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.StringUtils; import java.util.ArrayList; @@ -35,6 +36,7 @@ public class Menu { private static final Map lastOpenedMenus = new ConcurrentHashMap<>(); private final DeluxeMenus plugin; + private final TaskScheduler scheduler; private final MenuOptions options; private final Map> items; // menu path starting from the plugin directory @@ -49,13 +51,14 @@ public Menu( final @NotNull String path ) { this.plugin = plugin; + this.scheduler = plugin.getScheduler(); this.options = options; this.items = items; this.path = path; if (this.options.registerCommands()) { this.command = new RegistrableMenuCommand(plugin, this); - plugin.getScheduler().runTask(() -> this.command.register()); + scheduler.runTask(() -> this.command.register()); } menus.put(this.options.name(), this); @@ -301,7 +304,7 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map { + scheduler.runTaskAsynchronously(() -> { Set activeItems = new HashSet<>(); @@ -390,7 +393,7 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map { + scheduler.runTask(viewer, () -> { if (options.refresh()) { holder.startRefreshTask(); } @@ -402,17 +405,17 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map { - DeluxeMenusOpenMenuEvent openEvent = new DeluxeMenusOpenMenuEvent(viewer, holder); - Bukkit.getPluginManager().callEvent(openEvent); - }); - }); - } + if (updatePlaceholders) { + holder.startUpdatePlaceholdersTask(); + } + }); + + scheduler.runTask(viewer, () -> { + DeluxeMenusOpenMenuEvent openEvent = new DeluxeMenusOpenMenuEvent(viewer, holder); + Bukkit.getPluginManager().callEvent(openEvent); + }); + }); + } public void refreshForAll() { menuHolders.stream().filter(menuHolder -> menuHolder.getMenuName().equalsIgnoreCase(options.name())).forEach(MenuHolder::refreshMenu); diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java index 0f216e95..5dc3222a 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java @@ -2,6 +2,7 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.menu.options.MenuOptions; +import com.extendedclip.deluxemenus.scheduler.scheduling.schedulers.TaskScheduler; import com.extendedclip.deluxemenus.scheduler.scheduling.tasks.MyScheduledTask; import com.extendedclip.deluxemenus.utils.StringUtils; import org.bukkit.entity.Player; @@ -19,6 +20,7 @@ public class MenuHolder implements InventoryHolder { private final DeluxeMenus plugin; + private final TaskScheduler scheduler; private final Player viewer; private Player placeholderPlayer; @@ -34,12 +36,14 @@ public class MenuHolder implements InventoryHolder { public MenuHolder(final @NotNull DeluxeMenus plugin, final @NotNull Player viewer) { this.plugin = plugin; + this.scheduler = plugin.getScheduler(); this.viewer = viewer; } 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.scheduler = plugin.getScheduler(); this.viewer = viewer; this.menuName = menuName; this.activeItems = activeItems; @@ -133,7 +137,7 @@ public void refreshMenu() { setUpdating(true); - plugin.getScheduler().runTaskAsynchronously(() -> { + scheduler.runTaskAsynchronously(() -> { final Set active = new HashSet<>(); final Set slotsToClear = new HashSet<>(); @@ -169,11 +173,11 @@ public void refreshMenu() { } if (active.isEmpty()) { - plugin.getScheduler().runTask(getViewer(), () -> Menu.closeMenu(plugin, getViewer(), true)); + scheduler.runTask(getViewer(), () -> Menu.closeMenu(plugin, getViewer(), true)); return; } - plugin.getScheduler().runTask(getViewer(), () -> { + scheduler.runTask(getViewer(), () -> { for (int slot : slotsToClear) { getInventory().setItem(slot, null); @@ -242,7 +246,7 @@ public void startRefreshTask() { .map(MenuOptions::refreshInterval) .orElse(10); - refreshTask = plugin.getScheduler().runTaskTimerAsynchronously( + refreshTask = scheduler.runTaskTimerAsynchronously( this::refreshMenu, initialDelay, period @@ -261,7 +265,7 @@ public void startUpdatePlaceholdersTask() { .map(MenuOptions::updateInterval) .orElse(10); - updateTask = plugin.getScheduler().runTaskTimer( + updateTask = scheduler.runTaskTimer( getViewer(), () -> { diff --git a/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java b/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java index 4db347af..4ebd0d6d 100644 --- a/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java +++ b/src/main/java/com/extendedclip/deluxemenus/updatechecker/UpdateChecker.java @@ -2,6 +2,7 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.listener.Listener; +import com.extendedclip.deluxemenus.scheduler.scheduling.schedulers.TaskScheduler; import com.extendedclip.deluxemenus.utils.DebugLevel; import com.extendedclip.deluxemenus.utils.Messages; import java.io.BufferedReader; @@ -24,16 +25,18 @@ public class UpdateChecker extends Listener { private static final TextReplacementConfig.Builder CURRENT_VERSION_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); - final int resourceId = 11734; + private static final int RESOURCE_ID = 11734; + private final TaskScheduler scheduler; private String latestVersion = null; private boolean updateAvailable = false; public UpdateChecker(final @NotNull DeluxeMenus instance) { super(instance); + this.scheduler = plugin.getScheduler(); - plugin.getScheduler().runTaskAsynchronously(() -> { + scheduler.runTaskAsynchronously(() -> { if (check()) { - plugin.getScheduler().runTask(this::register); + scheduler.runTask(this::register); } }); } @@ -63,7 +66,7 @@ public void onJoin(final @NotNull PlayerJoinEvent event) { private String getSpigotVersion() { try { HttpURLConnection connection = (HttpURLConnection) new URL( - "https://api.spigotmc.org/legacy/update.php?resource=" + resourceId).openConnection(); + "https://api.spigotmc.org/legacy/update.php?resource=" + RESOURCE_ID).openConnection(); connection.setDoOutput(true); connection.setRequestMethod("GET"); return new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine(); From ed06aced255827774f0ea43e9f85249716ff6c8a Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Wed, 3 Sep 2025 03:08:13 -0400 Subject: [PATCH 06/24] Implement custom logic, so I can deprecate the other DeluxeMenus branch --- build.gradle.kts | 4 +- .../extendedclip/deluxemenus/DeluxeMenus.java | 22 +++++++ .../deluxemenus/action/ClickActionTask.java | 2 +- .../deluxemenus/config/DeluxeMenusConfig.java | 20 ++++++ .../deluxemenus/dupe/DupeFixer.java | 18 +++++- .../deluxemenus/listener/PlayerListener.java | 61 +++++++++++++----- .../extendedclip/deluxemenus/menu/Menu.java | 63 ++++++++++++++++++- .../deluxemenus/menu/MenuItem.java | 4 ++ .../deluxemenus/menu/options/MenuOptions.java | 26 ++++++++ 9 files changed, 195 insertions(+), 25 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 70dcaa54..04d79ed0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -58,8 +58,8 @@ tasks { archiveFileName.set("DeluxeMenus-${rootProject.version}.jar") } java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 disableAutoTargetJvm() } diff --git a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java index 7196d51e..d12e1b3a 100644 --- a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java +++ b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java @@ -30,10 +30,13 @@ import org.bstats.charts.SingleLineChart; import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; @@ -362,4 +365,23 @@ private void setUpMetrics() { return results; })); } + + public boolean isDupeProtectionFlagged(@NotNull ItemStack item) { + if (!item.hasItemMeta()) return false; + + return item.getItemMeta().getPersistentDataContainer() + .has(new NamespacedKey(this, "deluxemenus.item.dupeprotection"), PersistentDataType.BYTE); + } + + public void markDupeProtection(@NotNull ItemStack item) { + ItemMeta meta = item.getItemMeta(); + if (meta == null) return; + + meta.getPersistentDataContainer().set( + new NamespacedKey(this, "deluxemenus.item.dupeprotection"), + PersistentDataType.BYTE, + (byte) 1 + ); + item.setItemMeta(meta); + } } diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index a493cd1a..53f2db64 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -167,7 +167,7 @@ public void run() { break; case CLOSE: - Menu.closeMenu(plugin, player, true, true); + Menu.closeMenu(plugin, player, true, true, false); break; case OPEN_GUI_MENU: diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index 6e21b91b..5fe7da94 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -498,6 +498,26 @@ public void loadMenu(FileConfiguration c, String key, boolean mainConfig, final builder.argumentRequirements(argumentRequirements); builder.argumentsUsageMessage(c.getString(pre + "args_usage_message", null)); + if (c.contains(pre + "gui_close_command")) { + final List guiCloseCommands = new ArrayList<>(); + if (c.isList(pre + "gui_close_command")) { + guiCloseCommands.addAll(c.getStringList(pre + "gui_close_command")); + } else { + guiCloseCommands.add(c.getString(pre + "gui_close_command")); + } + builder.guiCloseCommands(guiCloseCommands); + } + + if (c.contains(pre + "gui_open_command")) { + final List guiOpenCommands = new ArrayList<>(); + if (c.isList(pre + "gui_open_command")) { + guiOpenCommands.addAll(c.getStringList(pre + "gui_open_command")); + } else { + guiOpenCommands.add(c.getString(pre + "gui_open_command")); + } + builder.guiOpenCommands(guiOpenCommands); + } + int size = 54; if (type == InventoryType.CHEST) { if (!c.contains(pre + "size")) { diff --git a/src/main/java/com/extendedclip/deluxemenus/dupe/DupeFixer.java b/src/main/java/com/extendedclip/deluxemenus/dupe/DupeFixer.java index 14193418..9285ad92 100644 --- a/src/main/java/com/extendedclip/deluxemenus/dupe/DupeFixer.java +++ b/src/main/java/com/extendedclip/deluxemenus/dupe/DupeFixer.java @@ -2,6 +2,7 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.listener.Listener; +import com.extendedclip.deluxemenus.nbt.NbtProvider; import com.extendedclip.deluxemenus.scheduler.scheduling.schedulers.TaskScheduler; import com.extendedclip.deluxemenus.utils.DebugLevel; import org.bukkit.event.EventHandler; @@ -30,7 +31,8 @@ public DupeFixer(@NotNull final DeluxeMenus plugin, @NotNull final MenuItemMarke @EventHandler private void onPickup(@NotNull final EntityPickupItemEvent event) { - if (!marker.isMarked(event.getItem().getItemStack())) { + ItemStack stack = event.getItem().getItemStack(); + if (!marker.isMarked(stack) && isDupeProtectionFlagged(stack)) { return; } @@ -44,7 +46,8 @@ private void onPickup(@NotNull final EntityPickupItemEvent event) { @EventHandler private void onDrop(@NotNull final PlayerDropItemEvent event) { - if (!marker.isMarked(event.getItemDrop().getItemStack())) { + ItemStack stack = event.getItemDrop().getItemStack(); + if (!marker.isMarked(stack) && isDupeProtectionFlagged(stack)) { return; } @@ -61,7 +64,7 @@ private void onLogin(@NotNull final PlayerJoinEvent event) { scheduler.runTaskLater(() -> { for (final ItemStack itemStack : event.getPlayer().getInventory().getContents()) { if (itemStack == null) continue; - if (!marker.isMarked(itemStack)) continue; + if (!marker.isMarked(itemStack) && isDupeProtectionFlagged(itemStack)) continue; plugin.debug( DebugLevel.LOWEST, @@ -73,4 +76,13 @@ private void onLogin(@NotNull final PlayerJoinEvent event) { }, 10L ); } + + private boolean isDupeProtectionFlagged(ItemStack itemStack) { + if (NbtProvider.isAvailable()) { + String value = NbtProvider.getString(itemStack, "deluxemenus.item.dupeprotection"); + return !"true".equals(value); + } + + return true; + } } diff --git a/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java b/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java index 89b417b2..54b73069 100644 --- a/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java +++ b/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java @@ -7,6 +7,7 @@ import com.extendedclip.deluxemenus.menu.MenuItem; import com.extendedclip.deluxemenus.requirement.RequirementList; import com.extendedclip.deluxemenus.scheduler.scheduling.schedulers.TaskScheduler; +import com.extendedclip.deluxemenus.utils.DebugLevel; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import org.bukkit.entity.Player; @@ -18,11 +19,13 @@ import org.bukkit.event.inventory.InventoryOpenEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; +import java.util.logging.Level; public class PlayerListener extends Listener { @@ -37,7 +40,7 @@ public PlayerListener(@NotNull final DeluxeMenus plugin) { this.scheduler = plugin.getScheduler(); } - @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onCommandExecute(PlayerCommandPreprocessEvent event) { final String cmd = event.getMessage().substring(1); @@ -69,12 +72,10 @@ public void onLeave(PlayerQuitEvent event) { @EventHandler public void onOpen(InventoryOpenEvent event) { - if (!(event.getPlayer() instanceof Player)) { + if (!(event.getPlayer() instanceof Player player)) { return; } - final Player player = (Player) event.getPlayer(); - if (player.isSleeping()) { event.setCancelled(true); } @@ -86,31 +87,59 @@ public void onOpen(InventoryOpenEvent event) { @EventHandler public void onClose(InventoryCloseEvent event) { - - if (!(event.getPlayer() instanceof Player)) { + if (!(event.getPlayer() instanceof Player player)) { return; } - final Player player = (Player) event.getPlayer(); - if (Menu.isInMenu(player)) { - scheduler.runTaskLater(player, () -> { - Menu.closeMenu(plugin, player, false); - Menu.cleanInventory(plugin, player); - player.updateInventory(); - }, 3L); + Menu.closeMenu(plugin, player, false); } + + scheduler.runTaskLater(player, () -> { + Menu.cleanInventory(plugin, player); + + boolean removed = false; + + for (ItemStack itemStack : player.getInventory().getContents()) { + if (itemStack == null) continue; + if (!plugin.getMenuItemMarker().isMarked(itemStack)) continue; + if (!plugin.isDupeProtectionFlagged(itemStack)) continue; + + player.getInventory().remove(itemStack); + removed = true; + + plugin.debug( + DebugLevel.LOWEST, + Level.INFO, + "DeluxeMenus item found in main inventory on close. Removing it." + ); + } + + ItemStack offhand = player.getInventory().getItemInOffHand(); + if (plugin.getMenuItemMarker().isMarked(offhand) && plugin.isDupeProtectionFlagged(offhand)) { + player.getInventory().setItemInOffHand(null); + removed = true; + + plugin.debug( + DebugLevel.LOWEST, + Level.INFO, + "DeluxeMenus item found in offhand on close. Removing it." + ); + } + + if (removed) { + player.updateInventory(); + } + }, 1L); } @EventHandler(priority = EventPriority.LOW) public void onClick(InventoryClickEvent event) { - if (!(event.getWhoClicked() instanceof Player)) { + if (!(event.getWhoClicked() instanceof Player player)) { return; } - final Player player = (Player) event.getWhoClicked(); - final Optional optionalHolder = Menu.getMenuHolder(player); if (optionalHolder.isEmpty()) { diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java index 6ec238bc..ba2405a3 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java @@ -1,6 +1,10 @@ package com.extendedclip.deluxemenus.menu; 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.events.DeluxeMenusOpenMenuEvent; import com.extendedclip.deluxemenus.events.DeluxeMenusPreOpenMenuEvent; import com.extendedclip.deluxemenus.menu.command.RegistrableMenuCommand; @@ -21,6 +25,9 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryType; @@ -191,7 +198,7 @@ public static void cleanInventory(final @NotNull DeluxeMenus plugin, final @NotN player.updateInventory(); } - public static void closeMenu(final @NotNull DeluxeMenus plugin, 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, final boolean runCloseCommmands) { Optional optionalHolder = getMenuHolder(player); if (optionalHolder.isEmpty()) { return; @@ -214,6 +221,10 @@ public static void closeMenu(final @NotNull DeluxeMenus plugin, final @NotNull P } menuHolders.remove(holder); lastOpenedMenus.put(player.getUniqueId(), holder.getMenu().orElse(null)); + + if (runCloseCommmands) { + holder.getMenu().map(Menu::options).map(MenuOptions::guiCloseCommands).ifPresent(commands -> executeCommands(plugin, player, commands, holder)); + } } public static void closeMenuForShutdown(final @NotNull DeluxeMenus plugin, final @NotNull Player player) { @@ -224,7 +235,7 @@ public static void closeMenuForShutdown(final @NotNull DeluxeMenus plugin, final } public static void closeMenu(final @NotNull DeluxeMenus plugin, final @NotNull Player player, final boolean close) { - closeMenu(plugin, player, close, false); + closeMenu(plugin, player, close, true, true); } private boolean hasOpenBypassPerm(final @NotNull Player viewer) { @@ -371,6 +382,7 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map executeCommands(plugin, viewer, commands, holder)); + scheduler.runTask(viewer, () -> { if (options.refresh()) { holder.startRefreshTask(); @@ -417,6 +431,49 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map commands, final @NotNull MenuHolder holder) { + for (String command : commands) { + ActionType type = ActionType.getByStart(command); + if (type == null) continue; + + command = command.replaceFirst(Pattern.quote(type.getIdentifier()), "").trim(); + + ClickAction action = new ClickAction(type, command); + + Matcher d = DeluxeMenusConfig.DELAY_MATCHER.matcher(command); + + if (d.find()) { + action.setDelay(d.group(1)); + command = command.replaceFirst(Pattern.quote(d.group()), ""); + } + + Matcher ch = DeluxeMenusConfig.CHANCE_MATCHER.matcher(command); + + if (ch.find()) { + action.setChance(ch.group(1)); + command = command.replaceFirst(Pattern.quote(ch.group()), ""); + } + + action.setExecutable(command); + + final ClickActionTask actionTask = new ClickActionTask( + plugin, + viewer.getUniqueId(), + action.getType(), + command, + holder.getTypedArgs(), + true, + true + ); + + if (action.hasDelay()) { + actionTask.runTaskLater(plugin, action.getDelay(holder)); + } else { + actionTask.runTask(plugin); + } + } + } + public void refreshForAll() { menuHolders.stream().filter(menuHolder -> menuHolder.getMenuName().equalsIgnoreCase(options.name())).forEach(MenuHolder::refreshMenu); } diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java index 5b0a8680..0e881c9c 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -540,6 +540,10 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { itemStack = NbtProvider.setInt(itemStack, parts[0], Integer.parseInt(parts[1])); } } + + if (NbtProvider.isAvailable()) { + itemStack = NbtProvider.setString(itemStack, "deluxemenus.item.dupeprotection", "true"); + } } return itemStack; diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuOptions.java b/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuOptions.java index 26c5b039..bf556b7b 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuOptions.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/options/MenuOptions.java @@ -22,6 +22,8 @@ public class MenuOptions { private final boolean parsePlaceholdersAfterArguments; private final List commands; + private final List guiCloseCommands; + private final List guiOpenCommands; private final boolean registerCommands; private final List arguments; private final List argumentRequirements; @@ -43,6 +45,8 @@ private MenuOptions(final @NotNull MenuOptionsBuilder builder) { this.parsePlaceholdersAfterArguments = builder.parsePlaceholdersAfterArguments; this.commands = builder.commands; + this.guiCloseCommands = builder.guiCloseCommands; + this.guiOpenCommands = builder.guiOpenCommands; this.registerCommands = builder.registerCommands; this.arguments = builder.arguments; this.argumentRequirements = builder.argumentRequirements; @@ -97,6 +101,14 @@ public boolean parsePlaceholdersAfterArguments() { return this.commands; } + public @Nullable List guiCloseCommands() { + return this.guiCloseCommands; + } + + public @Nullable List guiOpenCommands() { + return this.guiOpenCommands; + } + public boolean registerCommands() { return this.registerCommands; } @@ -135,6 +147,8 @@ public boolean registerCommands() { .parsePlaceholdersInArguments(this.parsePlaceholdersInArguments) .parsePlaceholdersAfterArguments(this.parsePlaceholdersAfterArguments) .commands(this.commands) + .guiCloseCommands(this.guiCloseCommands) + .guiOpenCommands(this.guiOpenCommands) .registerCommands(this.registerCommands) .arguments(this.arguments) .argumentRequirements(this.argumentRequirements) @@ -157,6 +171,8 @@ public static class MenuOptionsBuilder { private boolean parsePlaceholdersAfterArguments = false; private List commands = List.of(); + private List guiCloseCommands; + private List guiOpenCommands; private boolean registerCommands = false; private List arguments = List.of(); private List argumentRequirements = List.of(); @@ -221,6 +237,16 @@ public MenuOptionsBuilder commands(final @NotNull List<@NotNull String> commands return this; } + public MenuOptionsBuilder guiCloseCommands(final @Nullable List guiCloseCommands) { + this.guiCloseCommands = guiCloseCommands; + return this; + } + + public MenuOptionsBuilder guiOpenCommands(final @Nullable List guiOpenCommands) { + this.guiOpenCommands = guiOpenCommands; + return this; + } + public MenuOptionsBuilder registerCommands(final boolean registerCommands) { this.registerCommands = registerCommands; return this; From c0fd08dbb3c091d24828a1cbe65cd665434e0781 Mon Sep 17 00:00:00 2001 From: r00tb33rman Date: Sat, 13 Sep 2025 23:29:50 -0400 Subject: [PATCH 07/24] Make click actions "secure," so duplicate actions cannot be executed whilst a Folia server is lagging --- .../deluxemenus/action/ClickAction.java | 1 - .../deluxemenus/action/ClickActionTask.java | 652 +++++++++--------- .../deluxemenus/listener/PlayerListener.java | 3 +- 3 files changed, 336 insertions(+), 320 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickAction.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickAction.java index 59d61685..ace0bf84 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickAction.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickAction.java @@ -76,7 +76,6 @@ public void setDelay(@Nullable final String delay) { this.delay = delay; } - /** * Get the unparsed chance of this action. * diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index a493cd1a..86555602 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -24,10 +24,13 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; public class ClickActionTask extends UniversalRunnable { + private static final ConcurrentHashMap IN_ACTION = new ConcurrentHashMap<>(); + private final DeluxeMenus plugin; private final TaskScheduler scheduler; private final UUID uuid; @@ -37,6 +40,7 @@ public class ClickActionTask extends UniversalRunnable { private final Map arguments; private final boolean parsePlaceholdersInArguments; private final boolean parsePlaceholdersAfterArguments; + private boolean actionHandled = false; public ClickActionTask( @NotNull final DeluxeMenus plugin, @@ -64,417 +68,431 @@ public void run() { return; } + if (this.actionHandled) { + return; + } + + final String inActionKey = this.uuid + ":" + this.actionType.name() + ":" + this.exec; + if (IN_ACTION.putIfAbsent(inActionKey, Boolean.TRUE) != null) { + return; + } + + this.actionHandled = true; + scheduler.runTask(player, () -> { + try { + final Optional holder = Menu.getMenuHolder(player); + final Player target = holder.isPresent() && holder.get().getPlaceholderPlayer() != null + ? holder.get().getPlaceholderPlayer() + : player; + + final String executable = StringUtils.replacePlaceholdersAndArguments( + this.exec, + this.arguments, + target, + this.parsePlaceholdersInArguments, + this.parsePlaceholdersAfterArguments); + + switch (actionType) { + case META: + if (!VersionHelper.IS_PDC_VERSION || plugin.getPersistentMetaHandler() == null) { + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Meta action not supported on this server version."); + break; + } + final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().parseAndExecuteMetaActionFromString(player, executable); + switch (result) { + case INVALID_SYNTAX: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Make sure you have the right syntax."); + break; + case NEW_VALUE_IS_DIFFERENT_TYPE: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! New value is a different type than the old value!"); + break; + case INVALID_TYPE: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! The specified type is not supported for the specified action!"); + break; + case EXISTENT_VALUE_IS_DIFFERENT_TYPE: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Existent value is a different type than the new value!"); + break; + case VALUE_NOT_FOUND: + case SUCCESS: + default: + break; + } + break; - final Optional holder = Menu.getMenuHolder(player); - final Player target = holder.isPresent() && holder.get().getPlaceholderPlayer() != null - ? holder.get().getPlaceholderPlayer() - : player; - - final String executable = StringUtils.replacePlaceholdersAndArguments( - this.exec, - this.arguments, - target, - this.parsePlaceholdersInArguments, - this.parsePlaceholdersAfterArguments); - - switch (actionType) { - case META: - if (!VersionHelper.IS_PDC_VERSION || plugin.getPersistentMetaHandler() == null) { - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Meta action not supported on this server version."); + case PLAYER: + case PLAYER_COMMAND_EVENT: + player.chat("/" + executable); + break; + + case PLACEHOLDER: + holder.ifPresent(it -> it.setPlaceholders(executable)); + break; + + case CHAT: + player.chat(executable); + break; + + case CONSOLE: + scheduler.runTask(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable)); + break; + + case MINI_MESSAGE: + plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); break; - } - final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().parseAndExecuteMetaActionFromString(player, executable); - switch (result) { - case INVALID_SYNTAX: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Make sure you have the right syntax."); - break; - case NEW_VALUE_IS_DIFFERENT_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! New value is a different type than the old value!"); - break; - case INVALID_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! The specified type is not supported for the specified action!"); - break; - case EXISTENT_VALUE_IS_DIFFERENT_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Existent value is a different type than the new value!"); - break; - case VALUE_NOT_FOUND: - case SUCCESS: - default: - break; - } - break; - case PLAYER: - case PLAYER_COMMAND_EVENT: - player.chat("/" + executable); - break; + case MINI_BROADCAST: + plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); + break; - case PLACEHOLDER: - holder.ifPresent(it -> it.setPlaceholders(executable)); - break; + case MESSAGE: + player.sendMessage(StringUtils.color(executable)); + break; - case CHAT: - player.chat(executable); - break; + case LOG: + final String[] logParts = executable.split(" ", 2); - case CONSOLE: - scheduler.runTask(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable)); - break; + if (logParts.length == 0 || logParts[0].isBlank()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "LOG command requires at least a message"); + break; + } - case MINI_MESSAGE: - plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); - break; + Level logLevel; + String message; - case MINI_BROADCAST: - plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); - break; + if (logParts.length == 1) { + logLevel = Level.INFO; + message = logParts[0]; + } else { + message = logParts[1]; - case MESSAGE: - player.sendMessage(StringUtils.color(executable)); - break; + try { + logLevel = Level.parse(logParts[0].toUpperCase()); + } catch (IllegalArgumentException e) { + logLevel = Level.INFO; + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Log level " + logParts[0] + " is not a valid log level! Using INFO instead."); + } + } - case LOG: - final String[] logParts = executable.split(" ", 2); + plugin.getLogger().log(logLevel, String.format("[%s]: %s", holder.map(MenuHolder::getMenuName).orElse("Unknown Menu"), message)); + break; - if (logParts.length == 0 || logParts[0].isBlank()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "LOG command requires at least a message"); + case BROADCAST: + Bukkit.broadcastMessage(StringUtils.color(executable)); break; - } - Level logLevel; - String message; + case CLOSE: + Menu.closeMenu(plugin, player, true, true); + break; - if (logParts.length == 1) { - logLevel = Level.INFO; - message = logParts[0]; - } else { - message = logParts[1]; + case OPEN_GUI_MENU: + case OPEN_MENU: + final String temporaryExecutable = executable.replaceAll("\\s+", " ").replace(" ", " "); + final String[] executableParts = temporaryExecutable.split(" ", 2); - try { - logLevel = Level.parse(logParts[0].toUpperCase()); - } catch (IllegalArgumentException e) { - logLevel = Level.INFO; - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Log level " + logParts[0] + " is not a valid log level! Using INFO instead."); + if (executableParts.length == 0) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); + break; } - } - plugin.getLogger().log(logLevel, String.format("[%s]: %s", holder.map(MenuHolder::getMenuName).orElse("Unknown Menu"), message)); - break; + final String menuName = executableParts[0]; - case BROADCAST: - Bukkit.broadcastMessage(StringUtils.color(executable)); - break; + final Optional

optionalMenuToOpen = Menu.getMenuByName(menuName); - case CLOSE: - Menu.closeMenu(plugin, player, true, true); - break; + if (optionalMenuToOpen.isEmpty()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); + break; + } - case OPEN_GUI_MENU: - case OPEN_MENU: - final String temporaryExecutable = executable.replaceAll("\\s+", " ").replace(" ", " "); - final String[] executableParts = temporaryExecutable.split(" ", 2); + final Menu menuToOpen = optionalMenuToOpen.get(); - if (executableParts.length == 0) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); - break; - } + final List menuArgumentNames = menuToOpen.options().arguments(); - final String menuName = executableParts[0]; + String[] passedArgumentValues = null; + if (executableParts.length > 1) { + passedArgumentValues = executableParts[1].split(" "); + } - final Optional optionalMenuToOpen = Menu.getMenuByName(menuName); + if (menuArgumentNames.isEmpty()) { + if (passedArgumentValues != null && passedArgumentValues.length > 0) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Arguments were given for menu " + menuName + " in action [openguimenu] or [openmenu], but the menu does not support arguments!" + ); + } - if (optionalMenuToOpen.isEmpty()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); - break; - } + if (holder.isEmpty()) { + menuToOpen.openMenu(player); + break; + } - final Menu menuToOpen = optionalMenuToOpen.get(); + menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); + break; + } - final List menuArgumentNames = menuToOpen.options().arguments(); + if (passedArgumentValues == null || passedArgumentValues.length == 0) { + // Replicate old behavior: If no arguments are given, open the menu with the arguments from the current menu + if (holder.isEmpty()) { + menuToOpen.openMenu(player); + break; + } - String[] passedArgumentValues = null; - if (executableParts.length > 1) { - passedArgumentValues = executableParts[1].split(" "); - } + menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); + break; + } - if (menuArgumentNames.isEmpty()) { - if (passedArgumentValues != null && passedArgumentValues.length > 0) { + if (passedArgumentValues.length < menuArgumentNames.size()) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Arguments were given for menu " + menuName + " in action [openguimenu] or [openmenu], but the menu does not support arguments!" + "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" ); + break; } - if (holder.isEmpty()) { - menuToOpen.openMenu(player); - break; + final Map argumentsMap = new HashMap<>(); + if (holder.isPresent() && holder.get().getTypedArgs() != null) { + // Pass the arguments from the current menu to the new menu. If the new menu has arguments with the + // same name, they will be overwritten + argumentsMap.putAll(holder.get().getTypedArgs()); } - menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); - break; - } + for (int index = 0; index < menuArgumentNames.size(); index++) { + final String argumentName = menuArgumentNames.get(index); + + if (passedArgumentValues.length <= index) { + // This should never be the case! + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" + ); + break; + } + + if (menuArgumentNames.size() == index + 1) { + // If this is the last argument, get all remaining values and join them + final String lastArgumentValue = String.join(" ", Arrays.asList(passedArgumentValues).subList(index, passedArgumentValues.length)); + argumentsMap.put(argumentName, lastArgumentValue); + break; + } + + argumentsMap.put(argumentName, passedArgumentValues[index]); + } - if (passedArgumentValues == null || passedArgumentValues.length == 0) { - // Replicate old behavior: If no arguments are given, open the menu with the arguments from the current menu if (holder.isEmpty()) { - menuToOpen.openMenu(player); + menuToOpen.openMenu(player, argumentsMap, null); break; } - menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); + menuToOpen.openMenu(player, argumentsMap, holder.get().getPlaceholderPlayer()); break; - } - - if (passedArgumentValues.length < menuArgumentNames.size()) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" - ); + + case CONNECT: + plugin.connect(player, executable); break; - } - final Map argumentsMap = new HashMap<>(); - if (holder.isPresent() && holder.get().getTypedArgs() != null) { - // Pass the arguments from the current menu to the new menu. If the new menu has arguments with the - // same name, they will be overwritten - argumentsMap.putAll(holder.get().getTypedArgs()); - } + case JSON_MESSAGE: + AdventureUtils.sendJson(plugin, player, executable); + break; - for (int index = 0; index < menuArgumentNames.size(); index++) { - final String argumentName = menuArgumentNames.get(index); + case JSON_BROADCAST: + case BROADCAST_JSON: + plugin.audiences().all().sendMessage(AdventureUtils.fromJson(executable)); + break; - if (passedArgumentValues.length <= index) { - // This should never be the case! + case REFRESH: + if (holder.isEmpty()) { plugin.debug( - DebugLevel.HIGHEST, + DebugLevel.MEDIUM, Level.WARNING, - "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" + player.getName() + " does not have menu open! Nothing to refresh!" ); break; } - if (menuArgumentNames.size() == index + 1) { - // If this is the last argument, get all remaining values and join them - final String lastArgumentValue = String.join(" ", Arrays.asList(passedArgumentValues).subList(index, passedArgumentValues.length)); - argumentsMap.put(argumentName, lastArgumentValue); + holder.get().refreshMenu(); + break; + + case TAKE_MONEY: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot take money!"); break; } - argumentsMap.put(argumentName, passedArgumentValues[index]); - } - - if (holder.isEmpty()) { - menuToOpen.openMenu(player, argumentsMap, null); - break; - } - - menuToOpen.openMenu(player, argumentsMap, holder.get().getPlaceholderPlayer()); - break; - - case CONNECT: - plugin.connect(player, executable); - break; - - case JSON_MESSAGE: - AdventureUtils.sendJson(plugin, player, executable); - break; - - case JSON_BROADCAST: - case BROADCAST_JSON: - plugin.audiences().all().sendMessage(AdventureUtils.fromJson(executable)); - break; - - case REFRESH: - if (holder.isEmpty()) { - plugin.debug( - DebugLevel.MEDIUM, - Level.WARNING, - player.getName() + " does not have menu open! Nothing to refresh!" - ); + try { + plugin.getVault().takeMoney(player, Double.parseDouble(executable)); + } catch (final NumberFormatException exception) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for take money action: " + executable + ", is not a valid number!" + ); + } break; - } - - holder.get().refreshMenu(); - break; - case TAKE_MONEY: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot take money!"); - break; - } - - try { - plugin.getVault().takeMoney(player, Double.parseDouble(executable)); - } catch (final NumberFormatException exception) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for take money action: " + executable + ", is not a valid number!" - ); - } - break; - - case GIVE_MONEY: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot give money!"); - break; - } - - try { - plugin.getVault().giveMoney(player, Double.parseDouble(executable)); - } catch (final NumberFormatException exception) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for give money action: " + executable + ", is not a valid number!" - ); - } - break; - - case TAKE_EXP: - case GIVE_EXP: - final String lowerCaseExecutable = executable.toLowerCase(); - - try { - if (Integer.parseInt(lowerCaseExecutable.replaceAll("l", "")) <= 0) break; - - if (actionType == ActionType.TAKE_EXP) { - ExpUtils.setExp(player, "-" + lowerCaseExecutable); + case GIVE_MONEY: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot give money!"); break; } - ExpUtils.setExp(player, lowerCaseExecutable); - break; - - } catch (final NumberFormatException exception) { - if (actionType == ActionType.TAKE_EXP) { + try { + plugin.getVault().giveMoney(player, Double.parseDouble(executable)); + } catch (final NumberFormatException exception) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Amount for take exp action: " + executable + ", is not a valid number!" + "Amount for give money action: " + executable + ", is not a valid number!" ); - break; } - - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for give exp action: " + executable + ", is not a valid number!" - ); break; - } - - case GIVE_PERM: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Vault not hooked! Cannot give permission: " + executable + "!"); - break; - } - plugin.getVault().givePermission(player, executable); - break; + case TAKE_EXP: + case GIVE_EXP: + final String lowerCaseExecutable = executable.toLowerCase(); - case TAKE_PERM: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Vault not hooked! Cannot take permission: " + executable + "!"); - break; - } + try { + if (Integer.parseInt(lowerCaseExecutable.replaceAll("l", "")) <= 0) break; + + if (actionType == ActionType.TAKE_EXP) { + ExpUtils.setExp(player, "-" + lowerCaseExecutable); + break; + } - plugin.getVault().takePermission(player, executable); - break; + ExpUtils.setExp(player, lowerCaseExecutable); + break; - case BROADCAST_SOUND: - case BROADCAST_WORLD_SOUND: - case PLAY_SOUND: - final Sound sound; - float volume = 1; - float pitch = 1; + } catch (final NumberFormatException exception) { + if (actionType == ActionType.TAKE_EXP) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for take exp action: " + executable + ", is not a valid number!" + ); + break; + } - if (!executable.contains(" ")) { - try { - sound = SoundUtils.getSound(executable.toUpperCase()); - } catch (final IllegalArgumentException exception) { - plugin.printStacktrace( - "Sound name given for sound action: " + executable + ", is not a valid sound!", - exception + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for give exp action: " + executable + ", is not a valid number!" ); break; } - } else { - String[] parts = executable.split(" ", 3); - try { - sound = SoundUtils.getSound(parts[0].toUpperCase()); - } catch (final IllegalArgumentException exception) { - plugin.printStacktrace( - "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", - exception - ); + case GIVE_PERM: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Vault not hooked! Cannot give permission: " + executable + "!"); break; } - if (parts.length == 3) { + plugin.getVault().givePermission(player, executable); + break; + + case TAKE_PERM: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Vault not hooked! Cannot take permission: " + executable + "!"); + break; + } + + plugin.getVault().takePermission(player, executable); + break; + + case BROADCAST_SOUND: + case BROADCAST_WORLD_SOUND: + case PLAY_SOUND: + final Sound sound; + float volume = 1; + float pitch = 1; + + if (!executable.contains(" ")) { + try { + sound = SoundUtils.getSound(executable.toUpperCase()); + } catch (final IllegalArgumentException exception) { + plugin.printStacktrace( + "Sound name given for sound action: " + executable + ", is not a valid sound!", + exception + ); + break; + } + } else { + String[] parts = executable.split(" ", 3); + try { - pitch = Float.parseFloat(parts[2]); + sound = SoundUtils.getSound(parts[0].toUpperCase()); + } catch (final IllegalArgumentException exception) { + plugin.printStacktrace( + "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", + exception + ); + break; + } + + if (parts.length == 3) { + try { + pitch = Float.parseFloat(parts[2]); + } catch (final NumberFormatException exception) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Pitch given for sound action: " + parts[2] + ", is not a valid number!" + ); + + plugin.printStacktrace( + "Pitch given for sound action: " + parts[2] + ", is not a valid number!", + exception + ); + } + } + + try { + volume = Float.parseFloat(parts[1]); } catch (final NumberFormatException exception) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Pitch given for sound action: " + parts[2] + ", is not a valid number!" + "Volume given for sound action: " + parts[1] + ", is not a valid number!" ); plugin.printStacktrace( - "Pitch given for sound action: " + parts[2] + ", is not a valid number!", + "Volume given for sound action: " + parts[1] + ", is not a valid number!", exception ); } } - try { - volume = Float.parseFloat(parts[1]); - } catch (final NumberFormatException exception) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Volume given for sound action: " + parts[1] + ", is not a valid number!" - ); - - plugin.printStacktrace( - "Volume given for sound action: " + parts[1] + ", is not a valid number!", - exception - ); + switch (actionType) { + case BROADCAST_SOUND: + for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { + broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); + } + break; + + case BROADCAST_WORLD_SOUND: + for (final Player broadcastTarget : player.getWorld().getPlayers()) { + broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); + } + break; + + case PLAY_SOUND: + player.playSound(player.getLocation(), sound, volume, pitch); + break; } - } - - switch (actionType) { - case BROADCAST_SOUND: - for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { - broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); - } - break; - - case BROADCAST_WORLD_SOUND: - for (final Player broadcastTarget : player.getWorld().getPlayers()) { - broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); - } - break; - - case PLAY_SOUND: - player.playSound(player.getLocation(), sound, volume, pitch); - break; - } - break; + break; - default: - break; + default: + break; + } + } finally { + IN_ACTION.remove(inActionKey); } }); } diff --git a/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java b/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java index 89b417b2..f2d7d5f3 100644 --- a/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java +++ b/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java @@ -183,8 +183,7 @@ public void onClick(InventoryClickEvent event) { } if (event.getClick() == ClickType.MIDDLE) { - if (handleClick(player, holder, item.options().middleClickHandler(), item.options().middleClickRequirements())) { - } + handleClick(player, holder, item.options().middleClickHandler(), item.options().middleClickRequirements()); } } From 7352b2679453042c39c05d56b406f49ce0032a3c Mon Sep 17 00:00:00 2001 From: r00tb33rman Date: Sat, 13 Sep 2025 23:33:05 -0400 Subject: [PATCH 08/24] Rebase --- .../deluxemenus/action/ClickActionTask.java | 652 +++++++++--------- 1 file changed, 335 insertions(+), 317 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index 53f2db64..baf7da3d 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -24,10 +24,13 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; public class ClickActionTask extends UniversalRunnable { + private static final ConcurrentHashMap IN_ACTION = new ConcurrentHashMap<>(); + private final DeluxeMenus plugin; private final TaskScheduler scheduler; private final UUID uuid; @@ -37,6 +40,7 @@ public class ClickActionTask extends UniversalRunnable { private final Map arguments; private final boolean parsePlaceholdersInArguments; private final boolean parsePlaceholdersAfterArguments; + private boolean actionHandled = false; public ClickActionTask( @NotNull final DeluxeMenus plugin, @@ -64,417 +68,431 @@ public void run() { return; } + if (this.actionHandled) { + return; + } + + final String inActionKey = this.uuid + ":" + this.actionType.name() + ":" + this.exec; + if (IN_ACTION.putIfAbsent(inActionKey, Boolean.TRUE) != null) { + return; + } + + this.actionHandled = true; + scheduler.runTask(player, () -> { + try { + final Optional holder = Menu.getMenuHolder(player); + final Player target = holder.isPresent() && holder.get().getPlaceholderPlayer() != null + ? holder.get().getPlaceholderPlayer() + : player; + + final String executable = StringUtils.replacePlaceholdersAndArguments( + this.exec, + this.arguments, + target, + this.parsePlaceholdersInArguments, + this.parsePlaceholdersAfterArguments); + + switch (actionType) { + case META: + if (!VersionHelper.IS_PDC_VERSION || plugin.getPersistentMetaHandler() == null) { + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Meta action not supported on this server version."); + break; + } + final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().parseAndExecuteMetaActionFromString(player, executable); + switch (result) { + case INVALID_SYNTAX: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Make sure you have the right syntax."); + break; + case NEW_VALUE_IS_DIFFERENT_TYPE: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! New value is a different type than the old value!"); + break; + case INVALID_TYPE: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! The specified type is not supported for the specified action!"); + break; + case EXISTENT_VALUE_IS_DIFFERENT_TYPE: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Existent value is a different type than the new value!"); + break; + case VALUE_NOT_FOUND: + case SUCCESS: + default: + break; + } + break; - final Optional holder = Menu.getMenuHolder(player); - final Player target = holder.isPresent() && holder.get().getPlaceholderPlayer() != null - ? holder.get().getPlaceholderPlayer() - : player; - - final String executable = StringUtils.replacePlaceholdersAndArguments( - this.exec, - this.arguments, - target, - this.parsePlaceholdersInArguments, - this.parsePlaceholdersAfterArguments); - - switch (actionType) { - case META: - if (!VersionHelper.IS_PDC_VERSION || plugin.getPersistentMetaHandler() == null) { - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Meta action not supported on this server version."); + case PLAYER: + case PLAYER_COMMAND_EVENT: + player.chat("/" + executable); + break; + + case PLACEHOLDER: + holder.ifPresent(it -> it.setPlaceholders(executable)); + break; + + case CHAT: + player.chat(executable); + break; + + case CONSOLE: + scheduler.runTask(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable)); + break; + + case MINI_MESSAGE: + plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); break; - } - final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().parseAndExecuteMetaActionFromString(player, executable); - switch (result) { - case INVALID_SYNTAX: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Make sure you have the right syntax."); - break; - case NEW_VALUE_IS_DIFFERENT_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! New value is a different type than the old value!"); - break; - case INVALID_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! The specified type is not supported for the specified action!"); - break; - case EXISTENT_VALUE_IS_DIFFERENT_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Existent value is a different type than the new value!"); - break; - case VALUE_NOT_FOUND: - case SUCCESS: - default: - break; - } - break; - case PLAYER: - case PLAYER_COMMAND_EVENT: - player.chat("/" + executable); - break; + case MINI_BROADCAST: + plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); + break; - case PLACEHOLDER: - holder.ifPresent(it -> it.setPlaceholders(executable)); - break; + case MESSAGE: + player.sendMessage(StringUtils.color(executable)); + break; - case CHAT: - player.chat(executable); - break; + case LOG: + final String[] logParts = executable.split(" ", 2); - case CONSOLE: - scheduler.runTask(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable)); - break; + if (logParts.length == 0 || logParts[0].isBlank()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "LOG command requires at least a message"); + break; + } - case MINI_MESSAGE: - plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); - break; + Level logLevel; + String message; - case MINI_BROADCAST: - plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); - break; + if (logParts.length == 1) { + logLevel = Level.INFO; + message = logParts[0]; + } else { + message = logParts[1]; - case MESSAGE: - player.sendMessage(StringUtils.color(executable)); - break; + try { + logLevel = Level.parse(logParts[0].toUpperCase()); + } catch (IllegalArgumentException e) { + logLevel = Level.INFO; + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Log level " + logParts[0] + " is not a valid log level! Using INFO instead."); + } + } - case LOG: - final String[] logParts = executable.split(" ", 2); + plugin.getLogger().log(logLevel, String.format("[%s]: %s", holder.map(MenuHolder::getMenuName).orElse("Unknown Menu"), message)); + break; - if (logParts.length == 0 || logParts[0].isBlank()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "LOG command requires at least a message"); + case BROADCAST: + Bukkit.broadcastMessage(StringUtils.color(executable)); break; - } - Level logLevel; - String message; + case CLOSE: + Menu.closeMenu(plugin, player, true, true, false); + break; - if (logParts.length == 1) { - logLevel = Level.INFO; - message = logParts[0]; - } else { - message = logParts[1]; + case OPEN_GUI_MENU: + case OPEN_MENU: + final String temporaryExecutable = executable.replaceAll("\\s+", " ").replace(" ", " "); + final String[] executableParts = temporaryExecutable.split(" ", 2); - try { - logLevel = Level.parse(logParts[0].toUpperCase()); - } catch (IllegalArgumentException e) { - logLevel = Level.INFO; - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Log level " + logParts[0] + " is not a valid log level! Using INFO instead."); + if (executableParts.length == 0) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); + break; } - } - plugin.getLogger().log(logLevel, String.format("[%s]: %s", holder.map(MenuHolder::getMenuName).orElse("Unknown Menu"), message)); - break; + final String menuName = executableParts[0]; - case BROADCAST: - Bukkit.broadcastMessage(StringUtils.color(executable)); - break; + final Optional optionalMenuToOpen = Menu.getMenuByName(menuName); - case CLOSE: - Menu.closeMenu(plugin, player, true, true, false); - break; + if (optionalMenuToOpen.isEmpty()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); + break; + } - case OPEN_GUI_MENU: - case OPEN_MENU: - final String temporaryExecutable = executable.replaceAll("\\s+", " ").replace(" ", " "); - final String[] executableParts = temporaryExecutable.split(" ", 2); + final Menu menuToOpen = optionalMenuToOpen.get(); - if (executableParts.length == 0) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); - break; - } + final List menuArgumentNames = menuToOpen.options().arguments(); - final String menuName = executableParts[0]; + String[] passedArgumentValues = null; + if (executableParts.length > 1) { + passedArgumentValues = executableParts[1].split(" "); + } - final Optional optionalMenuToOpen = Menu.getMenuByName(menuName); + if (menuArgumentNames.isEmpty()) { + if (passedArgumentValues != null && passedArgumentValues.length > 0) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Arguments were given for menu " + menuName + " in action [openguimenu] or [openmenu], but the menu does not support arguments!" + ); + } - if (optionalMenuToOpen.isEmpty()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); - break; - } + if (holder.isEmpty()) { + menuToOpen.openMenu(player); + break; + } - final Menu menuToOpen = optionalMenuToOpen.get(); + menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); + break; + } - final List menuArgumentNames = menuToOpen.options().arguments(); + if (passedArgumentValues == null || passedArgumentValues.length == 0) { + // Replicate old behavior: If no arguments are given, open the menu with the arguments from the current menu + if (holder.isEmpty()) { + menuToOpen.openMenu(player); + break; + } - String[] passedArgumentValues = null; - if (executableParts.length > 1) { - passedArgumentValues = executableParts[1].split(" "); - } + menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); + break; + } - if (menuArgumentNames.isEmpty()) { - if (passedArgumentValues != null && passedArgumentValues.length > 0) { + if (passedArgumentValues.length < menuArgumentNames.size()) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Arguments were given for menu " + menuName + " in action [openguimenu] or [openmenu], but the menu does not support arguments!" + "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" ); + break; } - if (holder.isEmpty()) { - menuToOpen.openMenu(player); - break; + final Map argumentsMap = new HashMap<>(); + if (holder.isPresent() && holder.get().getTypedArgs() != null) { + // Pass the arguments from the current menu to the new menu. If the new menu has arguments with the + // same name, they will be overwritten + argumentsMap.putAll(holder.get().getTypedArgs()); } - menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); - break; - } + for (int index = 0; index < menuArgumentNames.size(); index++) { + final String argumentName = menuArgumentNames.get(index); + + if (passedArgumentValues.length <= index) { + // This should never be the case! + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" + ); + break; + } + + if (menuArgumentNames.size() == index + 1) { + // If this is the last argument, get all remaining values and join them + final String lastArgumentValue = String.join(" ", Arrays.asList(passedArgumentValues).subList(index, passedArgumentValues.length)); + argumentsMap.put(argumentName, lastArgumentValue); + break; + } + + argumentsMap.put(argumentName, passedArgumentValues[index]); + } - if (passedArgumentValues == null || passedArgumentValues.length == 0) { - // Replicate old behavior: If no arguments are given, open the menu with the arguments from the current menu if (holder.isEmpty()) { - menuToOpen.openMenu(player); + menuToOpen.openMenu(player, argumentsMap, null); break; } - menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); + menuToOpen.openMenu(player, argumentsMap, holder.get().getPlaceholderPlayer()); break; - } - - if (passedArgumentValues.length < menuArgumentNames.size()) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" - ); + + case CONNECT: + plugin.connect(player, executable); break; - } - final Map argumentsMap = new HashMap<>(); - if (holder.isPresent() && holder.get().getTypedArgs() != null) { - // Pass the arguments from the current menu to the new menu. If the new menu has arguments with the - // same name, they will be overwritten - argumentsMap.putAll(holder.get().getTypedArgs()); - } + case JSON_MESSAGE: + AdventureUtils.sendJson(plugin, player, executable); + break; - for (int index = 0; index < menuArgumentNames.size(); index++) { - final String argumentName = menuArgumentNames.get(index); + case JSON_BROADCAST: + case BROADCAST_JSON: + plugin.audiences().all().sendMessage(AdventureUtils.fromJson(executable)); + break; - if (passedArgumentValues.length <= index) { - // This should never be the case! + case REFRESH: + if (holder.isEmpty()) { plugin.debug( - DebugLevel.HIGHEST, + DebugLevel.MEDIUM, Level.WARNING, - "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" + player.getName() + " does not have menu open! Nothing to refresh!" ); break; } - if (menuArgumentNames.size() == index + 1) { - // If this is the last argument, get all remaining values and join them - final String lastArgumentValue = String.join(" ", Arrays.asList(passedArgumentValues).subList(index, passedArgumentValues.length)); - argumentsMap.put(argumentName, lastArgumentValue); + holder.get().refreshMenu(); + break; + + case TAKE_MONEY: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot take money!"); break; } - argumentsMap.put(argumentName, passedArgumentValues[index]); - } - - if (holder.isEmpty()) { - menuToOpen.openMenu(player, argumentsMap, null); - break; - } - - menuToOpen.openMenu(player, argumentsMap, holder.get().getPlaceholderPlayer()); - break; - - case CONNECT: - plugin.connect(player, executable); - break; - - case JSON_MESSAGE: - AdventureUtils.sendJson(plugin, player, executable); - break; - - case JSON_BROADCAST: - case BROADCAST_JSON: - plugin.audiences().all().sendMessage(AdventureUtils.fromJson(executable)); - break; - - case REFRESH: - if (holder.isEmpty()) { - plugin.debug( - DebugLevel.MEDIUM, - Level.WARNING, - player.getName() + " does not have menu open! Nothing to refresh!" - ); + try { + plugin.getVault().takeMoney(player, Double.parseDouble(executable)); + } catch (final NumberFormatException exception) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for take money action: " + executable + ", is not a valid number!" + ); + } break; - } - - holder.get().refreshMenu(); - break; - case TAKE_MONEY: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot take money!"); - break; - } - - try { - plugin.getVault().takeMoney(player, Double.parseDouble(executable)); - } catch (final NumberFormatException exception) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for take money action: " + executable + ", is not a valid number!" - ); - } - break; - - case GIVE_MONEY: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot give money!"); - break; - } - - try { - plugin.getVault().giveMoney(player, Double.parseDouble(executable)); - } catch (final NumberFormatException exception) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for give money action: " + executable + ", is not a valid number!" - ); - } - break; - - case TAKE_EXP: - case GIVE_EXP: - final String lowerCaseExecutable = executable.toLowerCase(); - - try { - if (Integer.parseInt(lowerCaseExecutable.replaceAll("l", "")) <= 0) break; - - if (actionType == ActionType.TAKE_EXP) { - ExpUtils.setExp(player, "-" + lowerCaseExecutable); + case GIVE_MONEY: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot give money!"); break; } - ExpUtils.setExp(player, lowerCaseExecutable); - break; - - } catch (final NumberFormatException exception) { - if (actionType == ActionType.TAKE_EXP) { + try { + plugin.getVault().giveMoney(player, Double.parseDouble(executable)); + } catch (final NumberFormatException exception) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Amount for take exp action: " + executable + ", is not a valid number!" + "Amount for give money action: " + executable + ", is not a valid number!" ); - break; } - - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for give exp action: " + executable + ", is not a valid number!" - ); break; - } - - case GIVE_PERM: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Vault not hooked! Cannot give permission: " + executable + "!"); - break; - } - plugin.getVault().givePermission(player, executable); - break; + case TAKE_EXP: + case GIVE_EXP: + final String lowerCaseExecutable = executable.toLowerCase(); - case TAKE_PERM: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Vault not hooked! Cannot take permission: " + executable + "!"); - break; - } + try { + if (Integer.parseInt(lowerCaseExecutable.replaceAll("l", "")) <= 0) break; + + if (actionType == ActionType.TAKE_EXP) { + ExpUtils.setExp(player, "-" + lowerCaseExecutable); + break; + } - plugin.getVault().takePermission(player, executable); - break; + ExpUtils.setExp(player, lowerCaseExecutable); + break; - case BROADCAST_SOUND: - case BROADCAST_WORLD_SOUND: - case PLAY_SOUND: - final Sound sound; - float volume = 1; - float pitch = 1; + } catch (final NumberFormatException exception) { + if (actionType == ActionType.TAKE_EXP) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for take exp action: " + executable + ", is not a valid number!" + ); + break; + } - if (!executable.contains(" ")) { - try { - sound = SoundUtils.getSound(executable.toUpperCase()); - } catch (final IllegalArgumentException exception) { - plugin.printStacktrace( - "Sound name given for sound action: " + executable + ", is not a valid sound!", - exception + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for give exp action: " + executable + ", is not a valid number!" ); break; } - } else { - String[] parts = executable.split(" ", 3); - try { - sound = SoundUtils.getSound(parts[0].toUpperCase()); - } catch (final IllegalArgumentException exception) { - plugin.printStacktrace( - "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", - exception - ); + case GIVE_PERM: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Vault not hooked! Cannot give permission: " + executable + "!"); break; } - if (parts.length == 3) { + plugin.getVault().givePermission(player, executable); + break; + + case TAKE_PERM: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Vault not hooked! Cannot take permission: " + executable + "!"); + break; + } + + plugin.getVault().takePermission(player, executable); + break; + + case BROADCAST_SOUND: + case BROADCAST_WORLD_SOUND: + case PLAY_SOUND: + final Sound sound; + float volume = 1; + float pitch = 1; + + if (!executable.contains(" ")) { + try { + sound = SoundUtils.getSound(executable.toUpperCase()); + } catch (final IllegalArgumentException exception) { + plugin.printStacktrace( + "Sound name given for sound action: " + executable + ", is not a valid sound!", + exception + ); + break; + } + } else { + String[] parts = executable.split(" ", 3); + try { - pitch = Float.parseFloat(parts[2]); + sound = SoundUtils.getSound(parts[0].toUpperCase()); + } catch (final IllegalArgumentException exception) { + plugin.printStacktrace( + "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", + exception + ); + break; + } + + if (parts.length == 3) { + try { + pitch = Float.parseFloat(parts[2]); + } catch (final NumberFormatException exception) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Pitch given for sound action: " + parts[2] + ", is not a valid number!" + ); + + plugin.printStacktrace( + "Pitch given for sound action: " + parts[2] + ", is not a valid number!", + exception + ); + } + } + + try { + volume = Float.parseFloat(parts[1]); } catch (final NumberFormatException exception) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Pitch given for sound action: " + parts[2] + ", is not a valid number!" + "Volume given for sound action: " + parts[1] + ", is not a valid number!" ); plugin.printStacktrace( - "Pitch given for sound action: " + parts[2] + ", is not a valid number!", + "Volume given for sound action: " + parts[1] + ", is not a valid number!", exception ); } } - try { - volume = Float.parseFloat(parts[1]); - } catch (final NumberFormatException exception) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Volume given for sound action: " + parts[1] + ", is not a valid number!" - ); - - plugin.printStacktrace( - "Volume given for sound action: " + parts[1] + ", is not a valid number!", - exception - ); + switch (actionType) { + case BROADCAST_SOUND: + for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { + broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); + } + break; + + case BROADCAST_WORLD_SOUND: + for (final Player broadcastTarget : player.getWorld().getPlayers()) { + broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); + } + break; + + case PLAY_SOUND: + player.playSound(player.getLocation(), sound, volume, pitch); + break; } - } - - switch (actionType) { - case BROADCAST_SOUND: - for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { - broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); - } - break; - - case BROADCAST_WORLD_SOUND: - for (final Player broadcastTarget : player.getWorld().getPlayers()) { - broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); - } - break; - - case PLAY_SOUND: - player.playSound(player.getLocation(), sound, volume, pitch); - break; - } - break; + break; - default: - break; + default: + break; + } + } finally { + IN_ACTION.remove(inActionKey); } }); } From 26ec6b67c21b4f28216815150b2363d0687a7002 Mon Sep 17 00:00:00 2001 From: r00tb33rman Date: Sat, 13 Sep 2025 23:47:30 -0400 Subject: [PATCH 09/24] Reference viewer directly --- .../com/extendedclip/deluxemenus/menu/MenuHolder.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java index 5dc3222a..b28dc145 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java @@ -103,7 +103,7 @@ public Optional getMenu() { } public @NotNull String setPlaceholders(final @NotNull String string) { - final Player player = this.placeholderPlayer != null ? this.placeholderPlayer : this.getViewer(); + final Player player = this.placeholderPlayer != null ? this.placeholderPlayer : this.viewer; if (player == null) { return string; } @@ -112,7 +112,7 @@ public Optional getMenu() { } public @NotNull String setArguments(final @NotNull String string) { - final Player player = this.placeholderPlayer != null ? this.placeholderPlayer : this.getViewer(); + final Player player = this.placeholderPlayer != null ? this.placeholderPlayer : this.viewer; return StringUtils.replaceArguments( string, @@ -173,11 +173,11 @@ public void refreshMenu() { } if (active.isEmpty()) { - scheduler.runTask(getViewer(), () -> Menu.closeMenu(plugin, getViewer(), true)); + scheduler.runTask(viewer, () -> Menu.closeMenu(plugin, viewer, true)); return; } - scheduler.runTask(getViewer(), () -> { + scheduler.runTask(viewer, () -> { for (int slot : slotsToClear) { getInventory().setItem(slot, null); @@ -266,7 +266,7 @@ public void startUpdatePlaceholdersTask() { .orElse(10); updateTask = scheduler.runTaskTimer( - getViewer(), + viewer, () -> { if (updating) { From 02aa131a44f0f8e001bb6985d53b1546cd236e91 Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Tue, 7 Oct 2025 07:29:05 -0400 Subject: [PATCH 10/24] This weird implementation is not a DeluxeMenus "responsibility" --- .../deluxemenus/action/ClickActionTask.java | 652 +++++++++--------- 1 file changed, 317 insertions(+), 335 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index 86555602..a493cd1a 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -24,13 +24,10 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; public class ClickActionTask extends UniversalRunnable { - private static final ConcurrentHashMap IN_ACTION = new ConcurrentHashMap<>(); - private final DeluxeMenus plugin; private final TaskScheduler scheduler; private final UUID uuid; @@ -40,7 +37,6 @@ public class ClickActionTask extends UniversalRunnable { private final Map arguments; private final boolean parsePlaceholdersInArguments; private final boolean parsePlaceholdersAfterArguments; - private boolean actionHandled = false; public ClickActionTask( @NotNull final DeluxeMenus plugin, @@ -68,431 +64,417 @@ public void run() { return; } - if (this.actionHandled) { - return; - } - - final String inActionKey = this.uuid + ":" + this.actionType.name() + ":" + this.exec; - if (IN_ACTION.putIfAbsent(inActionKey, Boolean.TRUE) != null) { - return; - } - - this.actionHandled = true; - scheduler.runTask(player, () -> { - try { - final Optional holder = Menu.getMenuHolder(player); - final Player target = holder.isPresent() && holder.get().getPlaceholderPlayer() != null - ? holder.get().getPlaceholderPlayer() - : player; - - final String executable = StringUtils.replacePlaceholdersAndArguments( - this.exec, - this.arguments, - target, - this.parsePlaceholdersInArguments, - this.parsePlaceholdersAfterArguments); - - switch (actionType) { - case META: - if (!VersionHelper.IS_PDC_VERSION || plugin.getPersistentMetaHandler() == null) { - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Meta action not supported on this server version."); - break; - } - final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().parseAndExecuteMetaActionFromString(player, executable); - switch (result) { - case INVALID_SYNTAX: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Make sure you have the right syntax."); - break; - case NEW_VALUE_IS_DIFFERENT_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! New value is a different type than the old value!"); - break; - case INVALID_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! The specified type is not supported for the specified action!"); - break; - case EXISTENT_VALUE_IS_DIFFERENT_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Existent value is a different type than the new value!"); - break; - case VALUE_NOT_FOUND: - case SUCCESS: - default: - break; - } - break; - - case PLAYER: - case PLAYER_COMMAND_EVENT: - player.chat("/" + executable); - break; - - case PLACEHOLDER: - holder.ifPresent(it -> it.setPlaceholders(executable)); - break; - - case CHAT: - player.chat(executable); - break; - - case CONSOLE: - scheduler.runTask(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable)); - break; - case MINI_MESSAGE: - plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); + final Optional holder = Menu.getMenuHolder(player); + final Player target = holder.isPresent() && holder.get().getPlaceholderPlayer() != null + ? holder.get().getPlaceholderPlayer() + : player; + + final String executable = StringUtils.replacePlaceholdersAndArguments( + this.exec, + this.arguments, + target, + this.parsePlaceholdersInArguments, + this.parsePlaceholdersAfterArguments); + + switch (actionType) { + case META: + if (!VersionHelper.IS_PDC_VERSION || plugin.getPersistentMetaHandler() == null) { + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Meta action not supported on this server version."); break; + } + final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().parseAndExecuteMetaActionFromString(player, executable); + switch (result) { + case INVALID_SYNTAX: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Make sure you have the right syntax."); + break; + case NEW_VALUE_IS_DIFFERENT_TYPE: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! New value is a different type than the old value!"); + break; + case INVALID_TYPE: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! The specified type is not supported for the specified action!"); + break; + case EXISTENT_VALUE_IS_DIFFERENT_TYPE: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Existent value is a different type than the new value!"); + break; + case VALUE_NOT_FOUND: + case SUCCESS: + default: + break; + } + break; - case MINI_BROADCAST: - plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); - break; + case PLAYER: + case PLAYER_COMMAND_EVENT: + player.chat("/" + executable); + break; - case MESSAGE: - player.sendMessage(StringUtils.color(executable)); - break; + case PLACEHOLDER: + holder.ifPresent(it -> it.setPlaceholders(executable)); + break; - case LOG: - final String[] logParts = executable.split(" ", 2); + case CHAT: + player.chat(executable); + break; - if (logParts.length == 0 || logParts[0].isBlank()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "LOG command requires at least a message"); - break; - } + case CONSOLE: + scheduler.runTask(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable)); + break; - Level logLevel; - String message; + case MINI_MESSAGE: + plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); + break; - if (logParts.length == 1) { - logLevel = Level.INFO; - message = logParts[0]; - } else { - message = logParts[1]; + case MINI_BROADCAST: + plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); + break; - try { - logLevel = Level.parse(logParts[0].toUpperCase()); - } catch (IllegalArgumentException e) { - logLevel = Level.INFO; - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Log level " + logParts[0] + " is not a valid log level! Using INFO instead."); - } - } + case MESSAGE: + player.sendMessage(StringUtils.color(executable)); + break; - plugin.getLogger().log(logLevel, String.format("[%s]: %s", holder.map(MenuHolder::getMenuName).orElse("Unknown Menu"), message)); - break; + case LOG: + final String[] logParts = executable.split(" ", 2); - case BROADCAST: - Bukkit.broadcastMessage(StringUtils.color(executable)); + if (logParts.length == 0 || logParts[0].isBlank()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "LOG command requires at least a message"); break; + } - case CLOSE: - Menu.closeMenu(plugin, player, true, true); - break; + Level logLevel; + String message; - case OPEN_GUI_MENU: - case OPEN_MENU: - final String temporaryExecutable = executable.replaceAll("\\s+", " ").replace(" ", " "); - final String[] executableParts = temporaryExecutable.split(" ", 2); + if (logParts.length == 1) { + logLevel = Level.INFO; + message = logParts[0]; + } else { + message = logParts[1]; - if (executableParts.length == 0) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); - break; + try { + logLevel = Level.parse(logParts[0].toUpperCase()); + } catch (IllegalArgumentException e) { + logLevel = Level.INFO; + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Log level " + logParts[0] + " is not a valid log level! Using INFO instead."); } + } - final String menuName = executableParts[0]; + plugin.getLogger().log(logLevel, String.format("[%s]: %s", holder.map(MenuHolder::getMenuName).orElse("Unknown Menu"), message)); + break; - final Optional optionalMenuToOpen = Menu.getMenuByName(menuName); + case BROADCAST: + Bukkit.broadcastMessage(StringUtils.color(executable)); + break; - if (optionalMenuToOpen.isEmpty()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); - break; - } + case CLOSE: + Menu.closeMenu(plugin, player, true, true); + break; - final Menu menuToOpen = optionalMenuToOpen.get(); + case OPEN_GUI_MENU: + case OPEN_MENU: + final String temporaryExecutable = executable.replaceAll("\\s+", " ").replace(" ", " "); + final String[] executableParts = temporaryExecutable.split(" ", 2); - final List menuArgumentNames = menuToOpen.options().arguments(); + if (executableParts.length == 0) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); + break; + } - String[] passedArgumentValues = null; - if (executableParts.length > 1) { - passedArgumentValues = executableParts[1].split(" "); - } + final String menuName = executableParts[0]; - if (menuArgumentNames.isEmpty()) { - if (passedArgumentValues != null && passedArgumentValues.length > 0) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Arguments were given for menu " + menuName + " in action [openguimenu] or [openmenu], but the menu does not support arguments!" - ); - } + final Optional optionalMenuToOpen = Menu.getMenuByName(menuName); - if (holder.isEmpty()) { - menuToOpen.openMenu(player); - break; - } + if (optionalMenuToOpen.isEmpty()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); + break; + } - menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); - break; - } + final Menu menuToOpen = optionalMenuToOpen.get(); - if (passedArgumentValues == null || passedArgumentValues.length == 0) { - // Replicate old behavior: If no arguments are given, open the menu with the arguments from the current menu - if (holder.isEmpty()) { - menuToOpen.openMenu(player); - break; - } + final List menuArgumentNames = menuToOpen.options().arguments(); - menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); - break; - } + String[] passedArgumentValues = null; + if (executableParts.length > 1) { + passedArgumentValues = executableParts[1].split(" "); + } - if (passedArgumentValues.length < menuArgumentNames.size()) { + if (menuArgumentNames.isEmpty()) { + if (passedArgumentValues != null && passedArgumentValues.length > 0) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" + "Arguments were given for menu " + menuName + " in action [openguimenu] or [openmenu], but the menu does not support arguments!" ); - break; - } - - final Map argumentsMap = new HashMap<>(); - if (holder.isPresent() && holder.get().getTypedArgs() != null) { - // Pass the arguments from the current menu to the new menu. If the new menu has arguments with the - // same name, they will be overwritten - argumentsMap.putAll(holder.get().getTypedArgs()); - } - - for (int index = 0; index < menuArgumentNames.size(); index++) { - final String argumentName = menuArgumentNames.get(index); - - if (passedArgumentValues.length <= index) { - // This should never be the case! - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" - ); - break; - } - - if (menuArgumentNames.size() == index + 1) { - // If this is the last argument, get all remaining values and join them - final String lastArgumentValue = String.join(" ", Arrays.asList(passedArgumentValues).subList(index, passedArgumentValues.length)); - argumentsMap.put(argumentName, lastArgumentValue); - break; - } - - argumentsMap.put(argumentName, passedArgumentValues[index]); } if (holder.isEmpty()) { - menuToOpen.openMenu(player, argumentsMap, null); + menuToOpen.openMenu(player); break; } - menuToOpen.openMenu(player, argumentsMap, holder.get().getPlaceholderPlayer()); - break; - - case CONNECT: - plugin.connect(player, executable); - break; - - case JSON_MESSAGE: - AdventureUtils.sendJson(plugin, player, executable); - break; - - case JSON_BROADCAST: - case BROADCAST_JSON: - plugin.audiences().all().sendMessage(AdventureUtils.fromJson(executable)); + menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); break; + } - case REFRESH: + if (passedArgumentValues == null || passedArgumentValues.length == 0) { + // Replicate old behavior: If no arguments are given, open the menu with the arguments from the current menu if (holder.isEmpty()) { - plugin.debug( - DebugLevel.MEDIUM, - Level.WARNING, - player.getName() + " does not have menu open! Nothing to refresh!" - ); + menuToOpen.openMenu(player); break; } - holder.get().refreshMenu(); + menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); break; + } + + if (passedArgumentValues.length < menuArgumentNames.size()) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" + ); + break; + } - case TAKE_MONEY: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot take money!"); - break; - } + final Map argumentsMap = new HashMap<>(); + if (holder.isPresent() && holder.get().getTypedArgs() != null) { + // Pass the arguments from the current menu to the new menu. If the new menu has arguments with the + // same name, they will be overwritten + argumentsMap.putAll(holder.get().getTypedArgs()); + } - try { - plugin.getVault().takeMoney(player, Double.parseDouble(executable)); - } catch (final NumberFormatException exception) { + for (int index = 0; index < menuArgumentNames.size(); index++) { + final String argumentName = menuArgumentNames.get(index); + + if (passedArgumentValues.length <= index) { + // This should never be the case! plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Amount for take money action: " + executable + ", is not a valid number!" + "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" ); - } - break; - - case GIVE_MONEY: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot give money!"); break; } - try { - plugin.getVault().giveMoney(player, Double.parseDouble(executable)); - } catch (final NumberFormatException exception) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for give money action: " + executable + ", is not a valid number!" - ); + if (menuArgumentNames.size() == index + 1) { + // If this is the last argument, get all remaining values and join them + final String lastArgumentValue = String.join(" ", Arrays.asList(passedArgumentValues).subList(index, passedArgumentValues.length)); + argumentsMap.put(argumentName, lastArgumentValue); + break; } - break; - case TAKE_EXP: - case GIVE_EXP: - final String lowerCaseExecutable = executable.toLowerCase(); + argumentsMap.put(argumentName, passedArgumentValues[index]); + } - try { - if (Integer.parseInt(lowerCaseExecutable.replaceAll("l", "")) <= 0) break; + if (holder.isEmpty()) { + menuToOpen.openMenu(player, argumentsMap, null); + break; + } + + menuToOpen.openMenu(player, argumentsMap, holder.get().getPlaceholderPlayer()); + break; + + case CONNECT: + plugin.connect(player, executable); + break; + + case JSON_MESSAGE: + AdventureUtils.sendJson(plugin, player, executable); + break; + + case JSON_BROADCAST: + case BROADCAST_JSON: + plugin.audiences().all().sendMessage(AdventureUtils.fromJson(executable)); + break; + + case REFRESH: + if (holder.isEmpty()) { + plugin.debug( + DebugLevel.MEDIUM, + Level.WARNING, + player.getName() + " does not have menu open! Nothing to refresh!" + ); + break; + } - if (actionType == ActionType.TAKE_EXP) { - ExpUtils.setExp(player, "-" + lowerCaseExecutable); - break; - } + holder.get().refreshMenu(); + break; - ExpUtils.setExp(player, lowerCaseExecutable); + case TAKE_MONEY: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot take money!"); + break; + } + + try { + plugin.getVault().takeMoney(player, Double.parseDouble(executable)); + } catch (final NumberFormatException exception) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for take money action: " + executable + ", is not a valid number!" + ); + } + break; + + case GIVE_MONEY: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot give money!"); + break; + } + + try { + plugin.getVault().giveMoney(player, Double.parseDouble(executable)); + } catch (final NumberFormatException exception) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for give money action: " + executable + ", is not a valid number!" + ); + } + break; + + case TAKE_EXP: + case GIVE_EXP: + final String lowerCaseExecutable = executable.toLowerCase(); + + try { + if (Integer.parseInt(lowerCaseExecutable.replaceAll("l", "")) <= 0) break; + + if (actionType == ActionType.TAKE_EXP) { + ExpUtils.setExp(player, "-" + lowerCaseExecutable); break; + } - } catch (final NumberFormatException exception) { - if (actionType == ActionType.TAKE_EXP) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for take exp action: " + executable + ", is not a valid number!" - ); - break; - } + ExpUtils.setExp(player, lowerCaseExecutable); + break; + } catch (final NumberFormatException exception) { + if (actionType == ActionType.TAKE_EXP) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Amount for give exp action: " + executable + ", is not a valid number!" + "Amount for take exp action: " + executable + ", is not a valid number!" ); break; } - case GIVE_PERM: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Vault not hooked! Cannot give permission: " + executable + "!"); - break; - } - - plugin.getVault().givePermission(player, executable); + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for give exp action: " + executable + ", is not a valid number!" + ); + break; + } + + case GIVE_PERM: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Vault not hooked! Cannot give permission: " + executable + "!"); break; + } - case TAKE_PERM: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Vault not hooked! Cannot take permission: " + executable + "!"); - break; - } + plugin.getVault().givePermission(player, executable); + break; - plugin.getVault().takePermission(player, executable); + case TAKE_PERM: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Vault not hooked! Cannot take permission: " + executable + "!"); break; + } - case BROADCAST_SOUND: - case BROADCAST_WORLD_SOUND: - case PLAY_SOUND: - final Sound sound; - float volume = 1; - float pitch = 1; + plugin.getVault().takePermission(player, executable); + break; - if (!executable.contains(" ")) { - try { - sound = SoundUtils.getSound(executable.toUpperCase()); - } catch (final IllegalArgumentException exception) { - plugin.printStacktrace( - "Sound name given for sound action: " + executable + ", is not a valid sound!", - exception - ); - break; - } - } else { - String[] parts = executable.split(" ", 3); + case BROADCAST_SOUND: + case BROADCAST_WORLD_SOUND: + case PLAY_SOUND: + final Sound sound; + float volume = 1; + float pitch = 1; - try { - sound = SoundUtils.getSound(parts[0].toUpperCase()); - } catch (final IllegalArgumentException exception) { - plugin.printStacktrace( - "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", - exception - ); - break; - } + if (!executable.contains(" ")) { + try { + sound = SoundUtils.getSound(executable.toUpperCase()); + } catch (final IllegalArgumentException exception) { + plugin.printStacktrace( + "Sound name given for sound action: " + executable + ", is not a valid sound!", + exception + ); + break; + } + } else { + String[] parts = executable.split(" ", 3); - if (parts.length == 3) { - try { - pitch = Float.parseFloat(parts[2]); - } catch (final NumberFormatException exception) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Pitch given for sound action: " + parts[2] + ", is not a valid number!" - ); - - plugin.printStacktrace( - "Pitch given for sound action: " + parts[2] + ", is not a valid number!", - exception - ); - } - } + try { + sound = SoundUtils.getSound(parts[0].toUpperCase()); + } catch (final IllegalArgumentException exception) { + plugin.printStacktrace( + "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", + exception + ); + break; + } + if (parts.length == 3) { try { - volume = Float.parseFloat(parts[1]); + pitch = Float.parseFloat(parts[2]); } catch (final NumberFormatException exception) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Volume given for sound action: " + parts[1] + ", is not a valid number!" + "Pitch given for sound action: " + parts[2] + ", is not a valid number!" ); plugin.printStacktrace( - "Volume given for sound action: " + parts[1] + ", is not a valid number!", + "Pitch given for sound action: " + parts[2] + ", is not a valid number!", exception ); } } - switch (actionType) { - case BROADCAST_SOUND: - for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { - broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); - } - break; - - case BROADCAST_WORLD_SOUND: - for (final Player broadcastTarget : player.getWorld().getPlayers()) { - broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); - } - break; - - case PLAY_SOUND: - player.playSound(player.getLocation(), sound, volume, pitch); - break; + try { + volume = Float.parseFloat(parts[1]); + } catch (final NumberFormatException exception) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Volume given for sound action: " + parts[1] + ", is not a valid number!" + ); + + plugin.printStacktrace( + "Volume given for sound action: " + parts[1] + ", is not a valid number!", + exception + ); } - break; + } - default: - break; - } - } finally { - IN_ACTION.remove(inActionKey); + switch (actionType) { + case BROADCAST_SOUND: + for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { + broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); + } + break; + + case BROADCAST_WORLD_SOUND: + for (final Player broadcastTarget : player.getWorld().getPlayers()) { + broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); + } + break; + + case PLAY_SOUND: + player.playSound(player.getLocation(), sound, volume, pitch); + break; + } + break; + + default: + break; } }); } From b30911c70ff715070c003ff4fae4cda8a56ec9e3 Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Tue, 7 Oct 2025 07:31:05 -0400 Subject: [PATCH 11/24] Rebase --- .../com/extendedclip/deluxemenus/action/ClickActionTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index a493cd1a..53f2db64 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -167,7 +167,7 @@ public void run() { break; case CLOSE: - Menu.closeMenu(plugin, player, true, true); + Menu.closeMenu(plugin, player, true, true, false); break; case OPEN_GUI_MENU: From 873ca2042d04cc92959665e6f1d5da4b2816cb75 Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Sat, 25 Oct 2025 16:07:24 -0400 Subject: [PATCH 12/24] Schedule command registration globally --- .../menu/command/RegistrableMenuCommand.java | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java b/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java index 3edf522b..85797e79 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java @@ -2,6 +2,7 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.menu.Menu; +import com.extendedclip.deluxemenus.scheduler.scheduling.schedulers.TaskScheduler; import com.extendedclip.deluxemenus.utils.DebugLevel; import me.clip.placeholderapi.util.Msg; import org.bukkit.Bukkit; @@ -25,6 +26,7 @@ public class RegistrableMenuCommand extends Command { private static CommandMap commandMap = null; private final DeluxeMenus plugin; + private final TaskScheduler scheduler; private Menu menu; private boolean registered = false; @@ -34,6 +36,7 @@ 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.scheduler = plugin.getScheduler(); this.menu = menu; if (menu.options().commands().size() > 1) { @@ -84,34 +87,36 @@ public boolean execute(final @NotNull CommandSender sender, final @NotNull Strin } public void register() { - if (registered) { - throw new IllegalStateException("This command was already registered!"); - } + scheduler.runTask(() -> { + if (registered) { + throw new IllegalStateException("This command was already registered!"); + } - registered = true; + 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; + } + } - 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 + boolean registered = commandMap.register(FALLBACK_PREFIX, this); + if (registered) { + plugin.debug( + DebugLevel.LOW, + Level.INFO, + "Registered command: " + this.getName() + " for menu: " + menu.options().name() ); - 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() { From 66837b1702116565291c513b722d3048138d3a3b Mon Sep 17 00:00:00 2001 From: RootBeer Date: Sun, 15 Mar 2026 19:24:46 -0400 Subject: [PATCH 13/24] Rebase --- .../deluxemenus/action/ClickActionTask.java | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index a493cd1a..53574116 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -392,9 +392,15 @@ public void run() { break; case BROADCAST_SOUND: + case BROADCAST_RAW_SOUND: case BROADCAST_WORLD_SOUND: + case BROADCAST_WORLD_RAW_SOUND: + case PLAY_RAW_SOUND: case PLAY_SOUND: - final Sound sound; + boolean isRaw = isRaw(actionType); + + Sound sound = null; + String soundName = executable; float volume = 1; float pitch = 1; @@ -455,6 +461,22 @@ public void run() { } switch (actionType) { + case BROADCAST_WORLD_RAW_SOUND: + for (final Player broadcastTarget : player.getWorld().getPlayers()) { + broadcastTarget.playSound(broadcastTarget.getLocation(), soundName, volume, pitch); + } + break; + + case BROADCAST_RAW_SOUND: + for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { + broadcastTarget.playSound(broadcastTarget.getLocation(), soundName, volume, pitch); + } + break; + + case PLAY_RAW_SOUND: + player.playSound(player.getLocation(), soundName, volume, pitch); + break; + case BROADCAST_SOUND: for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); @@ -478,4 +500,8 @@ public void run() { } }); } + + private boolean isRaw(ActionType actionType) { + return actionType == ActionType.PLAY_RAW_SOUND || actionType == ActionType.BROADCAST_RAW_SOUND || actionType == ActionType.BROADCAST_WORLD_RAW_SOUND; + } } From ee277cbc5d4e911877c6fd8cf6e124d0e00da27e Mon Sep 17 00:00:00 2001 From: RootBeer Date: Mon, 20 Apr 2026 13:25:01 -0400 Subject: [PATCH 14/24] Fix compilation (this will probably be needed for upstream for newer imports) --- build.gradle.kts | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 46175 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 13 ++++++------- gradlew.bat | 5 +++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ac9dd579..fffd5a9a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { java - id("com.gradleup.shadow") version("8.3.5") + id("com.gradleup.shadow") version("9.4.1") id("com.github.ben-manes.versions") version("0.51.0") } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..61285a659d17295f1de7c53e24fdf13ad755c379 100644 GIT binary patch delta 38026 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cea7a793..c61a118f 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.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index b740cf13..adff685a 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -170,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -203,15 +203,14 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_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" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 7101f8e4..e509b2dd 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -68,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From 44a7cb5cc37fa536349bc4fa42cf8e0f2253d839 Mon Sep 17 00:00:00 2001 From: RootBeer Date: Mon, 20 Apr 2026 13:28:47 -0400 Subject: [PATCH 15/24] Finish rebase --- .../deluxemenus/action/ClickActionTask.java | 61 ++++++++++++++----- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index 53574116..f26fe560 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -405,26 +405,31 @@ public void run() { float pitch = 1; if (!executable.contains(" ")) { - try { - sound = SoundUtils.getSound(executable.toUpperCase()); - } catch (final IllegalArgumentException exception) { - plugin.printStacktrace( - "Sound name given for sound action: " + executable + ", is not a valid sound!", - exception - ); - break; + if (!isRaw) { + try { + sound = SoundUtils.getSound(executable.toUpperCase()); + } catch (final IllegalArgumentException exception) { + plugin.printStacktrace( + "Sound name given for sound action: " + executable + ", is not a valid sound!", + exception + ); + break; + } } } else { String[] parts = executable.split(" ", 3); + soundName = parts[0]; - try { - sound = SoundUtils.getSound(parts[0].toUpperCase()); - } catch (final IllegalArgumentException exception) { - plugin.printStacktrace( - "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", - exception - ); - break; + if (!isRaw) { + try { + sound = SoundUtils.getSound(parts[0].toUpperCase()); + } catch (final IllegalArgumentException exception) { + plugin.printStacktrace( + "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", + exception + ); + break; + } } if (parts.length == 3) { @@ -478,18 +483,42 @@ public void run() { break; case BROADCAST_SOUND: + if (sound == null) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Sound name given for sound action: " + executable + ", is not a valid sound!" + ); + break; + } for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); } break; case BROADCAST_WORLD_SOUND: + if (sound == null) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Sound name given for sound action: " + executable + ", is not a valid sound!" + ); + break; + } for (final Player broadcastTarget : player.getWorld().getPlayers()) { broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); } break; case PLAY_SOUND: + if (sound == null) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Sound name given for sound action: " + executable + ", is not a valid sound!" + ); + break; + } player.playSound(player.getLocation(), sound, volume, pitch); break; } From e3d0ef39863e4815ab817fc6ed4d6fc526a038cb Mon Sep 17 00:00:00 2001 From: RootBeer Date: Mon, 20 Apr 2026 13:34:31 -0400 Subject: [PATCH 16/24] Simplify scheduling for need-only actions --- .../deluxemenus/action/ClickActionTask.java | 715 +++++++++--------- .../extendedclip/deluxemenus/menu/Menu.java | 2 +- 2 files changed, 357 insertions(+), 360 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index f26fe560..dfb3592d 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -64,470 +64,467 @@ public void run() { return; } - scheduler.runTask(player, () -> { - - final Optional holder = Menu.getMenuHolder(player); - final Player target = holder.isPresent() && holder.get().getPlaceholderPlayer() != null - ? holder.get().getPlaceholderPlayer() - : player; - - final String executable = StringUtils.replacePlaceholdersAndArguments( - this.exec, - this.arguments, - target, - this.parsePlaceholdersInArguments, - this.parsePlaceholdersAfterArguments); - - switch (actionType) { - case META: - if (!VersionHelper.IS_PDC_VERSION || plugin.getPersistentMetaHandler() == null) { - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Meta action not supported on this server version."); - break; - } - final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().parseAndExecuteMetaActionFromString(player, executable); - switch (result) { - case INVALID_SYNTAX: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Make sure you have the right syntax."); - break; - case NEW_VALUE_IS_DIFFERENT_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! New value is a different type than the old value!"); - break; - case INVALID_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! The specified type is not supported for the specified action!"); - break; - case EXISTENT_VALUE_IS_DIFFERENT_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Existent value is a different type than the new value!"); - break; - case VALUE_NOT_FOUND: - case SUCCESS: - default: - break; - } - break; - - case PLAYER: - case PLAYER_COMMAND_EVENT: - player.chat("/" + executable); - break; - - case PLACEHOLDER: - holder.ifPresent(it -> it.setPlaceholders(executable)); - break; - - case CHAT: - player.chat(executable); - break; - - case CONSOLE: - scheduler.runTask(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable)); - break; - - case MINI_MESSAGE: - plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); + final Optional holder = Menu.getMenuHolder(player); + final Player target = holder.isPresent() && holder.get().getPlaceholderPlayer() != null + ? holder.get().getPlaceholderPlayer() + : player; + + final String executable = StringUtils.replacePlaceholdersAndArguments( + this.exec, + this.arguments, + target, + this.parsePlaceholdersInArguments, + this.parsePlaceholdersAfterArguments); + + switch (actionType) { + case META: + if (!VersionHelper.IS_PDC_VERSION || plugin.getPersistentMetaHandler() == null) { + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Meta action not supported on this server version."); break; + } + final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().parseAndExecuteMetaActionFromString(player, executable); + switch (result) { + case INVALID_SYNTAX: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Make sure you have the right syntax."); + break; + case NEW_VALUE_IS_DIFFERENT_TYPE: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! New value is a different type than the old value!"); + break; + case INVALID_TYPE: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! The specified type is not supported for the specified action!"); + break; + case EXISTENT_VALUE_IS_DIFFERENT_TYPE: + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Existent value is a different type than the new value!"); + break; + case VALUE_NOT_FOUND: + case SUCCESS: + default: + break; + } + break; - case MINI_BROADCAST: - plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); - break; + case PLAYER: + case PLAYER_COMMAND_EVENT: + player.chat("/" + executable); + break; - case MESSAGE: - player.sendMessage(StringUtils.color(executable)); - break; + case PLACEHOLDER: + holder.ifPresent(it -> it.setPlaceholders(executable)); + break; - case LOG: - final String[] logParts = executable.split(" ", 2); + case CHAT: + player.chat(executable); + break; - if (logParts.length == 0 || logParts[0].isBlank()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "LOG command requires at least a message"); - break; - } + case CONSOLE: + scheduler.runTask(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable)); + break; - Level logLevel; - String message; + case MINI_MESSAGE: + plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); + break; - if (logParts.length == 1) { - logLevel = Level.INFO; - message = logParts[0]; - } else { - message = logParts[1]; + case MINI_BROADCAST: + plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); + break; - try { - logLevel = Level.parse(logParts[0].toUpperCase()); - } catch (IllegalArgumentException e) { - logLevel = Level.INFO; - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Log level " + logParts[0] + " is not a valid log level! Using INFO instead."); - } - } + case MESSAGE: + player.sendMessage(StringUtils.color(executable)); + break; - plugin.getLogger().log(logLevel, String.format("[%s]: %s", holder.map(MenuHolder::getMenuName).orElse("Unknown Menu"), message)); - break; + case LOG: + final String[] logParts = executable.split(" ", 2); - case BROADCAST: - Bukkit.broadcastMessage(StringUtils.color(executable)); + if (logParts.length == 0 || logParts[0].isBlank()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "LOG command requires at least a message"); break; + } - case CLOSE: - Menu.closeMenu(plugin, player, true, true); - break; + Level logLevel; + String message; - case OPEN_GUI_MENU: - case OPEN_MENU: - final String temporaryExecutable = executable.replaceAll("\\s+", " ").replace(" ", " "); - final String[] executableParts = temporaryExecutable.split(" ", 2); + if (logParts.length == 1) { + logLevel = Level.INFO; + message = logParts[0]; + } else { + message = logParts[1]; - if (executableParts.length == 0) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); - break; + try { + logLevel = Level.parse(logParts[0].toUpperCase()); + } catch (IllegalArgumentException e) { + logLevel = Level.INFO; + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Log level " + logParts[0] + " is not a valid log level! Using INFO instead."); } + } - final String menuName = executableParts[0]; + plugin.getLogger().log(logLevel, String.format("[%s]: %s", holder.map(MenuHolder::getMenuName).orElse("Unknown Menu"), message)); + break; - final Optional optionalMenuToOpen = Menu.getMenuByName(menuName); + case BROADCAST: + Bukkit.broadcastMessage(StringUtils.color(executable)); + break; - if (optionalMenuToOpen.isEmpty()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); - break; - } + case CLOSE: + Menu.closeMenu(plugin, player, true, true); + break; - final Menu menuToOpen = optionalMenuToOpen.get(); + case OPEN_GUI_MENU: + case OPEN_MENU: + final String temporaryExecutable = executable.replaceAll("\\s+", " ").replace(" ", " "); + final String[] executableParts = temporaryExecutable.split(" ", 2); - final List menuArgumentNames = menuToOpen.options().arguments(); + if (executableParts.length == 0) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); + break; + } - String[] passedArgumentValues = null; - if (executableParts.length > 1) { - passedArgumentValues = executableParts[1].split(" "); - } + final String menuName = executableParts[0]; - if (menuArgumentNames.isEmpty()) { - if (passedArgumentValues != null && passedArgumentValues.length > 0) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Arguments were given for menu " + menuName + " in action [openguimenu] or [openmenu], but the menu does not support arguments!" - ); - } + final Optional optionalMenuToOpen = Menu.getMenuByName(menuName); - if (holder.isEmpty()) { - menuToOpen.openMenu(player); - break; - } + if (optionalMenuToOpen.isEmpty()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Could not find and open menu " + executable); + break; + } - menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); - break; - } + final Menu menuToOpen = optionalMenuToOpen.get(); - if (passedArgumentValues == null || passedArgumentValues.length == 0) { - // Replicate old behavior: If no arguments are given, open the menu with the arguments from the current menu - if (holder.isEmpty()) { - menuToOpen.openMenu(player); - break; - } + final List menuArgumentNames = menuToOpen.options().arguments(); - menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); - break; - } + String[] passedArgumentValues = null; + if (executableParts.length > 1) { + passedArgumentValues = executableParts[1].split(" "); + } - if (passedArgumentValues.length < menuArgumentNames.size()) { + if (menuArgumentNames.isEmpty()) { + if (passedArgumentValues != null && passedArgumentValues.length > 0) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" + "Arguments were given for menu " + menuName + " in action [openguimenu] or [openmenu], but the menu does not support arguments!" ); - break; } - final Map argumentsMap = new HashMap<>(); - if (holder.isPresent() && holder.get().getTypedArgs() != null) { - // Pass the arguments from the current menu to the new menu. If the new menu has arguments with the - // same name, they will be overwritten - argumentsMap.putAll(holder.get().getTypedArgs()); + if (holder.isEmpty()) { + menuToOpen.openMenu(player); + break; } - for (int index = 0; index < menuArgumentNames.size(); index++) { - final String argumentName = menuArgumentNames.get(index); - - if (passedArgumentValues.length <= index) { - // This should never be the case! - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" - ); - break; - } - - if (menuArgumentNames.size() == index + 1) { - // If this is the last argument, get all remaining values and join them - final String lastArgumentValue = String.join(" ", Arrays.asList(passedArgumentValues).subList(index, passedArgumentValues.length)); - argumentsMap.put(argumentName, lastArgumentValue); - break; - } - - argumentsMap.put(argumentName, passedArgumentValues[index]); - } + menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); + break; + } + if (passedArgumentValues == null || passedArgumentValues.length == 0) { + // Replicate old behavior: If no arguments are given, open the menu with the arguments from the current menu if (holder.isEmpty()) { - menuToOpen.openMenu(player, argumentsMap, null); + menuToOpen.openMenu(player); break; } - menuToOpen.openMenu(player, argumentsMap, holder.get().getPlaceholderPlayer()); + menuToOpen.openMenu(player, holder.get().getTypedArgs(), holder.get().getPlaceholderPlayer()); break; - - case CONNECT: - plugin.connect(player, executable); + } + + if (passedArgumentValues.length < menuArgumentNames.size()) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" + ); break; + } - case JSON_MESSAGE: - AdventureUtils.sendJson(plugin, player, executable); - break; + final Map argumentsMap = new HashMap<>(); + if (holder.isPresent() && holder.get().getTypedArgs() != null) { + // Pass the arguments from the current menu to the new menu. If the new menu has arguments with the + // same name, they will be overwritten + argumentsMap.putAll(holder.get().getTypedArgs()); + } - case JSON_BROADCAST: - case BROADCAST_JSON: - plugin.audiences().all().sendMessage(AdventureUtils.fromJson(executable)); - break; + for (int index = 0; index < menuArgumentNames.size(); index++) { + final String argumentName = menuArgumentNames.get(index); - case REFRESH: - if (holder.isEmpty()) { + if (passedArgumentValues.length <= index) { + // This should never be the case! plugin.debug( - DebugLevel.MEDIUM, + DebugLevel.HIGHEST, Level.WARNING, - player.getName() + " does not have menu open! Nothing to refresh!" + "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" ); break; } - holder.get().refreshMenu(); - break; - - case TAKE_MONEY: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot take money!"); + if (menuArgumentNames.size() == index + 1) { + // If this is the last argument, get all remaining values and join them + final String lastArgumentValue = String.join(" ", Arrays.asList(passedArgumentValues).subList(index, passedArgumentValues.length)); + argumentsMap.put(argumentName, lastArgumentValue); break; } - try { - plugin.getVault().takeMoney(player, Double.parseDouble(executable)); - } catch (final NumberFormatException exception) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for take money action: " + executable + ", is not a valid number!" - ); - } + argumentsMap.put(argumentName, passedArgumentValues[index]); + } + + if (holder.isEmpty()) { + menuToOpen.openMenu(player, argumentsMap, null); + break; + } + + menuToOpen.openMenu(player, argumentsMap, holder.get().getPlaceholderPlayer()); + break; + + case CONNECT: + plugin.connect(player, executable); + break; + + case JSON_MESSAGE: + AdventureUtils.sendJson(plugin, player, executable); + break; + + case JSON_BROADCAST: + case BROADCAST_JSON: + plugin.audiences().all().sendMessage(AdventureUtils.fromJson(executable)); + break; + + case REFRESH: + if (holder.isEmpty()) { + plugin.debug( + DebugLevel.MEDIUM, + Level.WARNING, + player.getName() + " does not have menu open! Nothing to refresh!" + ); break; + } - case GIVE_MONEY: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot give money!"); + holder.get().refreshMenu(); + break; + + case TAKE_MONEY: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot take money!"); + break; + } + + try { + plugin.getVault().takeMoney(player, Double.parseDouble(executable)); + } catch (final NumberFormatException exception) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for take money action: " + executable + ", is not a valid number!" + ); + } + break; + + case GIVE_MONEY: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot give money!"); + break; + } + + try { + plugin.getVault().giveMoney(player, Double.parseDouble(executable)); + } catch (final NumberFormatException exception) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for give money action: " + executable + ", is not a valid number!" + ); + } + break; + + case TAKE_EXP: + case GIVE_EXP: + final String lowerCaseExecutable = executable.toLowerCase(); + + try { + if (Integer.parseInt(lowerCaseExecutable.replaceAll("l", "")) <= 0) break; + + if (actionType == ActionType.TAKE_EXP) { + ExpUtils.setExp(player, "-" + lowerCaseExecutable); break; } - try { - plugin.getVault().giveMoney(player, Double.parseDouble(executable)); - } catch (final NumberFormatException exception) { + ExpUtils.setExp(player, lowerCaseExecutable); + break; + + } catch (final NumberFormatException exception) { + if (actionType == ActionType.TAKE_EXP) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Amount for give money action: " + executable + ", is not a valid number!" + "Amount for take exp action: " + executable + ", is not a valid number!" ); + break; } - break; - case TAKE_EXP: - case GIVE_EXP: - final String lowerCaseExecutable = executable.toLowerCase(); + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for give exp action: " + executable + ", is not a valid number!" + ); + break; + } + + case GIVE_PERM: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Vault not hooked! Cannot give permission: " + executable + "!"); + break; + } - try { - if (Integer.parseInt(lowerCaseExecutable.replaceAll("l", "")) <= 0) break; + plugin.getVault().givePermission(player, executable); + break; - if (actionType == ActionType.TAKE_EXP) { - ExpUtils.setExp(player, "-" + lowerCaseExecutable); + case TAKE_PERM: + if (plugin.getVault() == null || !plugin.getVault().hooked()) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Vault not hooked! Cannot take permission: " + executable + "!"); + break; + } + + plugin.getVault().takePermission(player, executable); + break; + + case BROADCAST_SOUND: + case BROADCAST_RAW_SOUND: + case BROADCAST_WORLD_SOUND: + case BROADCAST_WORLD_RAW_SOUND: + case PLAY_RAW_SOUND: + case PLAY_SOUND: + boolean isRaw = isRaw(actionType); + + Sound sound = null; + String soundName = executable; + float volume = 1; + float pitch = 1; + + if (!executable.contains(" ")) { + if (!isRaw) { + try { + sound = SoundUtils.getSound(executable.toUpperCase()); + } catch (final IllegalArgumentException exception) { + plugin.printStacktrace( + "Sound name given for sound action: " + executable + ", is not a valid sound!", + exception + ); break; } + } + } else { + String[] parts = executable.split(" ", 3); + soundName = parts[0]; - ExpUtils.setExp(player, lowerCaseExecutable); - break; + if (!isRaw) { + try { + sound = SoundUtils.getSound(parts[0].toUpperCase()); + } catch (final IllegalArgumentException exception) { + plugin.printStacktrace( + "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", + exception + ); + break; + } + } - } catch (final NumberFormatException exception) { - if (actionType == ActionType.TAKE_EXP) { + if (parts.length == 3) { + try { + pitch = Float.parseFloat(parts[2]); + } catch (final NumberFormatException exception) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Amount for take exp action: " + executable + ", is not a valid number!" + "Pitch given for sound action: " + parts[2] + ", is not a valid number!" + ); + + plugin.printStacktrace( + "Pitch given for sound action: " + parts[2] + ", is not a valid number!", + exception ); - break; } + } + try { + volume = Float.parseFloat(parts[1]); + } catch (final NumberFormatException exception) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Amount for give exp action: " + executable + ", is not a valid number!" + "Volume given for sound action: " + parts[1] + ", is not a valid number!" ); - break; - } - case GIVE_PERM: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Vault not hooked! Cannot give permission: " + executable + "!"); - break; + plugin.printStacktrace( + "Volume given for sound action: " + parts[1] + ", is not a valid number!", + exception + ); } + } - plugin.getVault().givePermission(player, executable); - break; - - case TAKE_PERM: - if (plugin.getVault() == null || !plugin.getVault().hooked()) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Vault not hooked! Cannot take permission: " + executable + "!"); + switch (actionType) { + case BROADCAST_WORLD_RAW_SOUND: + for (final Player broadcastTarget : player.getWorld().getPlayers()) { + broadcastTarget.playSound(broadcastTarget.getLocation(), soundName, volume, pitch); + } break; - } - - plugin.getVault().takePermission(player, executable); - break; - case BROADCAST_SOUND: - case BROADCAST_RAW_SOUND: - case BROADCAST_WORLD_SOUND: - case BROADCAST_WORLD_RAW_SOUND: - case PLAY_RAW_SOUND: - case PLAY_SOUND: - boolean isRaw = isRaw(actionType); - - Sound sound = null; - String soundName = executable; - float volume = 1; - float pitch = 1; - - if (!executable.contains(" ")) { - if (!isRaw) { - try { - sound = SoundUtils.getSound(executable.toUpperCase()); - } catch (final IllegalArgumentException exception) { - plugin.printStacktrace( - "Sound name given for sound action: " + executable + ", is not a valid sound!", - exception - ); - break; - } + case BROADCAST_RAW_SOUND: + for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { + broadcastTarget.playSound(broadcastTarget.getLocation(), soundName, volume, pitch); } - } else { - String[] parts = executable.split(" ", 3); - soundName = parts[0]; - - if (!isRaw) { - try { - sound = SoundUtils.getSound(parts[0].toUpperCase()); - } catch (final IllegalArgumentException exception) { - plugin.printStacktrace( - "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", - exception - ); - break; - } - } - - if (parts.length == 3) { - try { - pitch = Float.parseFloat(parts[2]); - } catch (final NumberFormatException exception) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Pitch given for sound action: " + parts[2] + ", is not a valid number!" - ); + break; - plugin.printStacktrace( - "Pitch given for sound action: " + parts[2] + ", is not a valid number!", - exception - ); - } - } + case PLAY_RAW_SOUND: + player.playSound(player.getLocation(), soundName, volume, pitch); + break; - try { - volume = Float.parseFloat(parts[1]); - } catch (final NumberFormatException exception) { + case BROADCAST_SOUND: + if (sound == null) { plugin.debug( DebugLevel.HIGHEST, Level.WARNING, - "Volume given for sound action: " + parts[1] + ", is not a valid number!" - ); - - plugin.printStacktrace( - "Volume given for sound action: " + parts[1] + ", is not a valid number!", - exception + "Sound name given for sound action: " + executable + ", is not a valid sound!" ); - } - } - - switch (actionType) { - case BROADCAST_WORLD_RAW_SOUND: - for (final Player broadcastTarget : player.getWorld().getPlayers()) { - broadcastTarget.playSound(broadcastTarget.getLocation(), soundName, volume, pitch); - } - break; - - case BROADCAST_RAW_SOUND: - for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { - broadcastTarget.playSound(broadcastTarget.getLocation(), soundName, volume, pitch); - } - break; - - case PLAY_RAW_SOUND: - player.playSound(player.getLocation(), soundName, volume, pitch); - break; - - case BROADCAST_SOUND: - if (sound == null) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Sound name given for sound action: " + executable + ", is not a valid sound!" - ); - break; - } - for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { - broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); - } break; + } + for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { + broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); + } + break; - case BROADCAST_WORLD_SOUND: - if (sound == null) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Sound name given for sound action: " + executable + ", is not a valid sound!" - ); - break; - } - for (final Player broadcastTarget : player.getWorld().getPlayers()) { - broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); - } + case BROADCAST_WORLD_SOUND: + if (sound == null) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Sound name given for sound action: " + executable + ", is not a valid sound!" + ); break; + } + for (final Player broadcastTarget : player.getWorld().getPlayers()) { + broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); + } + break; - case PLAY_SOUND: + case PLAY_SOUND: if (sound == null) { plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Sound name given for sound action: " + executable + ", is not a valid sound!" + DebugLevel.HIGHEST, + Level.WARNING, + "Sound name given for sound action: " + executable + ", is not a valid sound!" ); break; } player.playSound(player.getLocation(), sound, volume, pitch); break; - } - break; + } + break; - default: - break; - } - }); + default: + break; + } } private boolean isRaw(ActionType actionType) { diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java index 2b23ba2c..5056aecc 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java @@ -58,7 +58,7 @@ public Menu( if (this.options.registerCommands()) { this.command = new RegistrableMenuCommand(plugin, this); - scheduler.runTask(() -> this.command.register()); + this.command.register(); } menus.put(this.options.name(), this); From 00907ba466a017528d0e4254c4db1e288920f4ce Mon Sep 17 00:00:00 2001 From: RootBeer Date: Mon, 20 Apr 2026 13:39:46 -0400 Subject: [PATCH 17/24] Rebase --- .../com/extendedclip/deluxemenus/action/ClickActionTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index dfb3592d..e5b2772c 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -165,7 +165,7 @@ public void run() { break; case CLOSE: - Menu.closeMenu(plugin, player, true, true); + Menu.closeMenu(plugin, player, true, true, false); break; case OPEN_GUI_MENU: From 507e2576981adb58c87782e602c359acd6b505bd Mon Sep 17 00:00:00 2001 From: RootBeer Date: Mon, 20 Apr 2026 23:00:49 -0400 Subject: [PATCH 18/24] Modernization --- build.gradle.kts | 11 +- gradle/libs.versions.toml | 26 +- .../extendedclip/deluxemenus/DeluxeMenus.java | 2 +- .../deluxemenus/action/ClickAction.java | 2 - .../deluxemenus/action/ClickActionTask.java | 301 +++++++----------- .../command/DeluxeMenusCommand.java | 7 +- .../command/subcommand/DumpCommand.java | 8 +- .../command/subcommand/ExecuteCommand.java | 8 +- .../command/subcommand/HelpCommand.java | 4 +- .../command/subcommand/ListCommand.java | 12 +- .../command/subcommand/MetaCommand.java | 180 +++++------ .../command/subcommand/OpenCommand.java | 10 +- .../command/subcommand/RefreshCommand.java | 10 +- .../command/subcommand/ReloadCommand.java | 18 +- .../deluxemenus/config/DeluxeMenusConfig.java | 150 ++++----- .../hooks/ExecutableBlocksHook.java | 1 - .../hooks/SimpleItemGeneratorHook.java | 7 +- .../deluxemenus/menu/MenuItem.java | 114 +++---- .../menu/command/RegistrableMenuCommand.java | 11 +- .../persistentmeta/PersistentMetaHandler.java | 39 +-- .../deluxemenus/placeholder/Expansion.java | 6 +- .../requirement/HasItemRequirement.java | 8 +- .../requirement/InputResultRequirement.java | 71 ++--- .../requirement/IsNearRequirement.java | 2 +- .../requirement/IsObjectRequirement.java | 15 +- .../requirement/JavascriptRequirement.java | 4 +- .../requirement/RequirementType.java | 8 +- .../deluxemenus/utils/DebugLevel.java | 2 +- .../deluxemenus/utils/ExpUtils.java | 2 +- .../extendedclip/deluxemenus/utils/Pair.java | 14 +- .../deluxemenus/utils/SkullUtils.java | 8 +- .../deluxemenus/utils/VersionHelper.java | 1 - src/main/resources/paper-plugin.yml | 5 + 33 files changed, 467 insertions(+), 600 deletions(-) create mode 100644 src/main/resources/paper-plugin.yml diff --git a/build.gradle.kts b/build.gradle.kts index b4508fe4..3da3e656 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ plugins { java id("com.gradleup.shadow") version("9.4.1") - id("com.github.ben-manes.versions") version("0.51.0") + id("com.github.ben-manes.versions") version("0.54.0") } // Change to true when releasing @@ -14,19 +14,19 @@ version = "$majorVersion-$minorVersion" repositories { mavenCentral() - maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") + maven("https://repo.papermc.io/repository/maven-public/") 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.momirealms.net/releases/") maven("https://repo.nexomc.com/releases/") maven("https://repo.oraxen.com/releases") + maven("https://maven.devs.beer/") maven("https://repo.papermc.io/repository/maven-public/") maven("https://jitpack.io") } dependencies { - compileOnly(libs.spigot) + compileOnly(libs.paper) compileOnly(libs.vault) compileOnly(libs.authlib) @@ -60,6 +60,7 @@ tasks { relocate("org.bstats", "com.extendedclip.deluxemenus.libs.bstats") archiveFileName.set("DeluxeMenus-${rootProject.version}.jar") } + java { sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 @@ -67,7 +68,7 @@ tasks { } processResources { - filesMatching("plugin.yml") { + filesMatching(listOf("plugin.yml", "paper-plugin.yml")) { expand("version" to rootProject.version) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 06e791dd..d4de3b7a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,34 +1,32 @@ [versions] # Compile only -spigot = "1.21.5-R0.1-SNAPSHOT" +paper = "1.21.11-R0.1-SNAPSHOT" vault = "1.7.1" authlib = "1.5.25" headdb = "1.3.2" -craftengine = "0.0.62" -itemsadder = "3.6.3-beta-14" -nexo = "1.1.0" -oraxen = "1.190.0" +craftengine = "0.0.67" +itemsadder = "4.0.10" +nexo = "1.10.0" +oraxen = "1.212.0" mythiclib = "1.7.1-SNAPSHOT" -mmoitems = "6.10-SNAPSHOT" -papi = "2.11.6" +mmoitems = "6.10.1-SNAPSHOT" +papi = "2.12.2" score = "5.25.3.9" sig = "1.5.0" -bstats = "3.1.0" - +bstats = "3.2.1" # Implementation -nashorn = "15.6" +nashorn = "15.7" adventure-platform = "4.4.1" -adventure-minimessage = "4.24.0" - +adventure-minimessage = "4.26.1" [libraries] # Compile only -spigot = { module = "io.papermc.paper:paper-api", version.ref = "spigot" } +paper = { module = "io.papermc.paper:paper-api", version.ref = "paper" } 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" } craftengine-core = { module = "net.momirealms:craft-engine-core", version.ref = "craftengine"} craftengine-bukkit = { module = "net.momirealms:craft-engine-bukkit", version.ref = "craftengine"} -itemsadder = { module = "com.github.LoneDev6:api-itemsadder", version.ref = "itemsadder" } +itemsadder = { module = "dev.lone:api-itemsadder", version.ref = "itemsadder" } nexo = { module = "com.nexomc:nexo", version.ref = "nexo" } oraxen = { module = "io.th0rgal:oraxen", version.ref = "oraxen" } mythiclib = { module = "io.lumine:MythicLib-dist", version.ref = "mythiclib"} diff --git a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java index dbc3659f..12d79301 100644 --- a/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java +++ b/src/main/java/com/extendedclip/deluxemenus/DeluxeMenus.java @@ -356,7 +356,7 @@ private void setUpMetrics() { .map(Menu::getMenuItems) .flatMap(c -> c.values().stream().map(TreeMap::values).flatMap(Collection::stream)) .map(MenuItem::options) - .collect(Collectors.toList()); + .toList(); results.put("Byte", options.stream().filter(option -> option.nbtByte().isPresent()).mapToInt(b -> 1).sum()); results.put("Bytes", options.stream().filter(option -> !option.nbtBytes().isEmpty()).mapToInt(b -> 1).sum()); results.put("Short", options.stream().filter(option -> option.nbtShort().isPresent()).mapToInt(s -> 1).sum()); diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickAction.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickAction.java index ace0bf84..aefda3e0 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickAction.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickAction.java @@ -101,7 +101,6 @@ public void setChance(@Nullable final String chance) { * @param holder the holder to parse placeholders in the delay for. * @return the parsed delay */ - @SuppressWarnings("UnstableApiUsage") public long getDelay(@NotNull final MenuHolder holder) { if (delay == null || delay.isEmpty()) { return 0; @@ -118,7 +117,6 @@ public long getDelay(@NotNull final MenuHolder holder) { * @param holder the holder to parse placeholders in the chance for. * @return true if the chance has passed, false otherwise */ - @SuppressWarnings("UnstableApiUsage") public boolean checkChance(@NotNull final MenuHolder holder) { if (chance == null) { return true; diff --git a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java index e5b2772c..fc405c3a 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -77,62 +77,35 @@ public void run() { this.parsePlaceholdersAfterArguments); switch (actionType) { - case META: + case META -> { if (!VersionHelper.IS_PDC_VERSION || plugin.getPersistentMetaHandler() == null) { plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Meta action not supported on this server version."); break; } final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().parseAndExecuteMetaActionFromString(player, executable); switch (result) { - case INVALID_SYNTAX: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Make sure you have the right syntax."); - break; - case NEW_VALUE_IS_DIFFERENT_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! New value is a different type than the old value!"); - break; - case INVALID_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! The specified type is not supported for the specified action!"); - break; - case EXISTENT_VALUE_IS_DIFFERENT_TYPE: - plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Existent value is a different type than the new value!"); - break; - case VALUE_NOT_FOUND: - case SUCCESS: - default: - break; + case INVALID_SYNTAX -> + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Make sure you have the right syntax."); + case NEW_VALUE_IS_DIFFERENT_TYPE -> + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! New value is a different type than the old value!"); + case INVALID_TYPE -> + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! The specified type is not supported for the specified action!"); + case EXISTENT_VALUE_IS_DIFFERENT_TYPE -> + plugin.debug(DebugLevel.HIGHEST, Level.INFO, "Invalid meta action! Existent value is a different type than the new value!"); + default -> { + } } - break; - - case PLAYER: - case PLAYER_COMMAND_EVENT: - player.chat("/" + executable); - break; - - case PLACEHOLDER: - holder.ifPresent(it -> it.setPlaceholders(executable)); - break; - - case CHAT: - player.chat(executable); - break; - - case CONSOLE: - scheduler.runTask(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable)); - break; - - case MINI_MESSAGE: - plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); - break; - - case MINI_BROADCAST: - plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); - break; - - case MESSAGE: - player.sendMessage(StringUtils.color(executable)); - break; - - case LOG: + } + case PLAYER, PLAYER_COMMAND_EVENT -> player.chat("/" + executable); + case PLACEHOLDER -> holder.ifPresent(it -> it.setPlaceholders(executable)); + case CHAT -> player.chat(executable); + case CONSOLE -> scheduler.runTask(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), executable)); + case MINI_MESSAGE -> + plugin.audiences().player(player).sendMessage(MiniMessage.miniMessage().deserialize(executable)); + case MINI_BROADCAST -> + plugin.audiences().all().sendMessage(MiniMessage.miniMessage().deserialize(executable)); + case MESSAGE -> player.sendMessage(StringUtils.color(executable)); + case LOG -> { final String[] logParts = executable.split(" ", 2); if (logParts.length == 0 || logParts[0].isBlank()) { @@ -158,18 +131,10 @@ public void run() { } plugin.getLogger().log(logLevel, String.format("[%s]: %s", holder.map(MenuHolder::getMenuName).orElse("Unknown Menu"), message)); - break; - - case BROADCAST: - Bukkit.broadcastMessage(StringUtils.color(executable)); - break; - - case CLOSE: - Menu.closeMenu(plugin, player, true, true, false); - break; - - case OPEN_GUI_MENU: - case OPEN_MENU: + } + case BROADCAST -> Bukkit.broadcastMessage(StringUtils.color(executable)); + case CLOSE -> Menu.closeMenu(plugin, player, true, true, false); + case OPEN_GUI_MENU, OPEN_MENU -> { final String temporaryExecutable = executable.replaceAll("\\s+", " ").replace(" ", " "); final String[] executableParts = temporaryExecutable.split(" ", 2); @@ -199,9 +164,9 @@ public void run() { if (menuArgumentNames.isEmpty()) { if (passedArgumentValues != null && passedArgumentValues.length > 0) { plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Arguments were given for menu " + menuName + " in action [openguimenu] or [openmenu], but the menu does not support arguments!" + DebugLevel.HIGHEST, + Level.WARNING, + "Arguments were given for menu " + menuName + " in action [openguimenu] or [openmenu], but the menu does not support arguments!" ); } @@ -227,9 +192,9 @@ public void run() { if (passedArgumentValues.length < menuArgumentNames.size()) { plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" + DebugLevel.HIGHEST, + Level.WARNING, + "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" ); break; } @@ -247,9 +212,9 @@ public void run() { if (passedArgumentValues.length <= index) { // This should never be the case! plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" + DebugLevel.HIGHEST, + Level.WARNING, + "Not enough arguments given for menu " + menuName + " when opening using the [openguimenu] or [openmenu] action!" ); break; } @@ -270,35 +235,24 @@ public void run() { } menuToOpen.openMenu(player, argumentsMap, holder.get().getPlaceholderPlayer()); - break; - - case CONNECT: - plugin.connect(player, executable); - break; - - case JSON_MESSAGE: - AdventureUtils.sendJson(plugin, player, executable); - break; - - case JSON_BROADCAST: - case BROADCAST_JSON: - plugin.audiences().all().sendMessage(AdventureUtils.fromJson(executable)); - break; - - case REFRESH: + } + case CONNECT -> plugin.connect(player, executable); + case JSON_MESSAGE -> AdventureUtils.sendJson(plugin, player, executable); + case JSON_BROADCAST, BROADCAST_JSON -> + plugin.audiences().all().sendMessage(AdventureUtils.fromJson(executable)); + case REFRESH -> { if (holder.isEmpty()) { plugin.debug( - DebugLevel.MEDIUM, - Level.WARNING, - player.getName() + " does not have menu open! Nothing to refresh!" + DebugLevel.MEDIUM, + Level.WARNING, + player.getName() + " does not have menu open! Nothing to refresh!" ); break; } holder.get().refreshMenu(); - break; - - case TAKE_MONEY: + } + case TAKE_MONEY -> { if (plugin.getVault() == null || !plugin.getVault().hooked()) { plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot take money!"); break; @@ -308,14 +262,13 @@ public void run() { plugin.getVault().takeMoney(player, Double.parseDouble(executable)); } catch (final NumberFormatException exception) { plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for take money action: " + executable + ", is not a valid number!" + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for take money action: " + executable + ", is not a valid number!" ); } - break; - - case GIVE_MONEY: + } + case GIVE_MONEY -> { if (plugin.getVault() == null || !plugin.getVault().hooked()) { plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Vault not hooked! Cannot give money!"); break; @@ -325,19 +278,17 @@ public void run() { plugin.getVault().giveMoney(player, Double.parseDouble(executable)); } catch (final NumberFormatException exception) { plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for give money action: " + executable + ", is not a valid number!" + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for give money action: " + executable + ", is not a valid number!" ); } - break; - - case TAKE_EXP: - case GIVE_EXP: + } + case TAKE_EXP, GIVE_EXP -> { final String lowerCaseExecutable = executable.toLowerCase(); try { - if (Integer.parseInt(lowerCaseExecutable.replaceAll("l", "")) <= 0) break; + if (Integer.parseInt(lowerCaseExecutable.replace("l", "")) <= 0) break; if (actionType == ActionType.TAKE_EXP) { ExpUtils.setExp(player, "-" + lowerCaseExecutable); @@ -345,56 +296,48 @@ public void run() { } ExpUtils.setExp(player, lowerCaseExecutable); - break; } catch (final NumberFormatException exception) { if (actionType == ActionType.TAKE_EXP) { plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for take exp action: " + executable + ", is not a valid number!" + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for take exp action: " + executable + ", is not a valid number!" ); break; } plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Amount for give exp action: " + executable + ", is not a valid number!" + DebugLevel.HIGHEST, + Level.WARNING, + "Amount for give exp action: " + executable + ", is not a valid number!" ); - break; } - - case GIVE_PERM: + } + case GIVE_PERM -> { if (plugin.getVault() == null || !plugin.getVault().hooked()) { plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Vault not hooked! Cannot give permission: " + executable + "!"); + DebugLevel.HIGHEST, + Level.WARNING, + "Vault not hooked! Cannot give permission: " + executable + "!"); break; } plugin.getVault().givePermission(player, executable); - break; - - case TAKE_PERM: + } + case TAKE_PERM -> { if (plugin.getVault() == null || !plugin.getVault().hooked()) { plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Vault not hooked! Cannot take permission: " + executable + "!"); + DebugLevel.HIGHEST, + Level.WARNING, + "Vault not hooked! Cannot take permission: " + executable + "!"); break; } plugin.getVault().takePermission(player, executable); - break; - - case BROADCAST_SOUND: - case BROADCAST_RAW_SOUND: - case BROADCAST_WORLD_SOUND: - case BROADCAST_WORLD_RAW_SOUND: - case PLAY_RAW_SOUND: - case PLAY_SOUND: + } + case BROADCAST_SOUND, BROADCAST_RAW_SOUND, BROADCAST_WORLD_SOUND, BROADCAST_WORLD_RAW_SOUND, PLAY_RAW_SOUND, + PLAY_SOUND -> { boolean isRaw = isRaw(actionType); Sound sound = null; @@ -408,8 +351,8 @@ public void run() { sound = SoundUtils.getSound(executable.toUpperCase()); } catch (final IllegalArgumentException exception) { plugin.printStacktrace( - "Sound name given for sound action: " + executable + ", is not a valid sound!", - exception + "Sound name given for sound action: " + executable + ", is not a valid sound!", + exception ); break; } @@ -423,8 +366,8 @@ public void run() { sound = SoundUtils.getSound(parts[0].toUpperCase()); } catch (final IllegalArgumentException exception) { plugin.printStacktrace( - "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", - exception + "Sound name given for sound action: " + parts[0] + ", is not a valid sound!", + exception ); break; } @@ -435,14 +378,14 @@ public void run() { pitch = Float.parseFloat(parts[2]); } catch (final NumberFormatException exception) { plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Pitch given for sound action: " + parts[2] + ", is not a valid number!" + DebugLevel.HIGHEST, + Level.WARNING, + "Pitch given for sound action: " + parts[2] + ", is not a valid number!" ); plugin.printStacktrace( - "Pitch given for sound action: " + parts[2] + ", is not a valid number!", - exception + "Pitch given for sound action: " + parts[2] + ", is not a valid number!", + exception ); } } @@ -451,79 +394,71 @@ public void run() { volume = Float.parseFloat(parts[1]); } catch (final NumberFormatException exception) { plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Volume given for sound action: " + parts[1] + ", is not a valid number!" + DebugLevel.HIGHEST, + Level.WARNING, + "Volume given for sound action: " + parts[1] + ", is not a valid number!" ); plugin.printStacktrace( - "Volume given for sound action: " + parts[1] + ", is not a valid number!", - exception + "Volume given for sound action: " + parts[1] + ", is not a valid number!", + exception ); } } switch (actionType) { - case BROADCAST_WORLD_RAW_SOUND: + case BROADCAST_WORLD_RAW_SOUND -> { for (final Player broadcastTarget : player.getWorld().getPlayers()) { broadcastTarget.playSound(broadcastTarget.getLocation(), soundName, volume, pitch); } - break; - - case BROADCAST_RAW_SOUND: + } + case BROADCAST_RAW_SOUND -> { for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { broadcastTarget.playSound(broadcastTarget.getLocation(), soundName, volume, pitch); } - break; - - case PLAY_RAW_SOUND: - player.playSound(player.getLocation(), soundName, volume, pitch); - break; - - case BROADCAST_SOUND: + } + case PLAY_RAW_SOUND -> player.playSound(player.getLocation(), soundName, volume, pitch); + case BROADCAST_SOUND -> { if (sound == null) { plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Sound name given for sound action: " + executable + ", is not a valid sound!" + DebugLevel.HIGHEST, + Level.WARNING, + "Sound name given for sound action: " + executable + ", is not a valid sound!" ); break; } for (final Player broadcastTarget : Bukkit.getOnlinePlayers()) { broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); } - break; - - case BROADCAST_WORLD_SOUND: + } + case BROADCAST_WORLD_SOUND -> { if (sound == null) { plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Sound name given for sound action: " + executable + ", is not a valid sound!" + DebugLevel.HIGHEST, + Level.WARNING, + "Sound name given for sound action: " + executable + ", is not a valid sound!" ); break; } for (final Player broadcastTarget : player.getWorld().getPlayers()) { broadcastTarget.playSound(broadcastTarget.getLocation(), sound, volume, pitch); } - break; - - case PLAY_SOUND: - if (sound == null) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Sound name given for sound action: " + executable + ", is not a valid sound!" - ); - break; - } - player.playSound(player.getLocation(), sound, volume, pitch); + } + case PLAY_SOUND -> { + if (sound == null) { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Sound name given for sound action: " + executable + ", is not a valid sound!" + ); break; + } + player.playSound(player.getLocation(), sound, volume, pitch); + } } - break; - - default: - break; + } + default -> { + } } } diff --git a/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java index e360bde8..382440e5 100644 --- a/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java @@ -11,6 +11,7 @@ import org.bukkit.command.PluginCommand; import org.bukkit.command.TabExecutor; import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NonNull; import java.util.Arrays; import java.util.HashMap; @@ -49,7 +50,7 @@ public boolean onCommand( final @NotNull CommandSender sender, final @NotNull Command command, final @NotNull String label, - final @NotNull String[] args + final @NotNull String @NonNull [] args ) { final List arguments = Arrays.asList(args); @@ -58,7 +59,7 @@ public boolean onCommand( return true; } - final SubCommand subCommand = subCommands.get(arguments.get(0).toLowerCase()); + final SubCommand subCommand = subCommands.get(arguments.getFirst().toLowerCase()); if (subCommand != null) { subCommand.execute(sender, arguments.subList(1, arguments.size())); @@ -74,7 +75,7 @@ public List onTabComplete( final @NotNull CommandSender sender, final @NotNull Command command, final @NotNull String label, - final @NotNull String[] args + final @NotNull String @NonNull [] args ) { final List arguments = Arrays.asList(args); diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/DumpCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/DumpCommand.java index e59e0d05..8d0e3075 100644 --- a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/DumpCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/DumpCommand.java @@ -42,7 +42,7 @@ public void execute(final @NotNull CommandSender sender, final @NotNull List menus = Menu.getAllMenus(); if (menus.isEmpty()) { plugin.sms(sender, Messages.MENUS_LOADED.message().replaceText(AMOUNT_REPLACER_BUILDER.replacement("There are no").build())); @@ -85,11 +85,11 @@ public void execute(@NotNull final CommandSender sender, @NotNull final List> paginatedMenus = getPaginatedMenus( @@ -373,6 +373,6 @@ private void addMenuToTreeRecursively(final Map tree, final List return null; } - return "/" + menu.options().commands().get(0); + return "/" + menu.options().commands().getFirst(); } } diff --git a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/MetaCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/MetaCommand.java index 631aa206..65cbafc0 100644 --- a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/MetaCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/MetaCommand.java @@ -61,10 +61,10 @@ public void execute(@NotNull final CommandSender sender, @NotNull final List type = DataType.getSupportedTypeByName(typeName); if (type == null) { plugin.sms(sender, Messages.META_TYPE_UNSUPPORTED.message().replaceText(TYPE_REPLACER_BUILDER.replacement(typeName).build())); @@ -318,7 +318,7 @@ private void handleShowMeta(@NotNull final CommandSender sender, @NotNull final return; } - final String keyName = arguments.get(0); + final String keyName = arguments.getFirst(); final NamespacedKey namespacedKey = plugin.getPersistentMetaHandler().getKey(keyName); if (namespacedKey == null) { plugin.sms(sender, Messages.META_KEY_INVALID.message().replaceText(KEY_REPLACER_BUILDER.replacement(keyName).build())); @@ -366,25 +366,18 @@ private void handleSetMeta(@NotNull final CommandSender sender, @NotNull final P final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().setMetaValue(target, namespacedKey, type, parsedValue); switch (result) { - case SUCCESS: - plugin.sms(sender, Messages.META_VALUE_SET.message() - .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build()) - .replaceText(TYPE_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.TYPE_NAME, type.getComplexType().getSimpleName())).build()) - .replaceText(VALUE_REPLACER_BUILDER.replacement(String.valueOf(parsedValue)).build()) - .replaceText(PLAYER_REPLACER_BUILDER.replacement(target.getName()).build()) - ); - return; - case NEW_VALUE_IS_DIFFERENT_TYPE: - plugin.sms(sender, Messages.META_VALUE_TYPE_MISMATCH.message() - .replaceText(VALUE_REPLACER_BUILDER.replacement(String.valueOf(parsedValue)).build()) - .replaceText(TYPE_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.TYPE_NAME, type.getComplexType().getSimpleName())).build())); - return; - case EXISTENT_VALUE_IS_DIFFERENT_TYPE: - plugin.sms(sender, Messages.META_EXISTENT_VALUE_WRONG_TYPE.message() - .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build())); - return; - default: - plugin.sms(sender, Messages.WRONG_USAGE_META_SET_COMMAND); + case SUCCESS -> plugin.sms(sender, Messages.META_VALUE_SET.message() + .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build()) + .replaceText(TYPE_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.TYPE_NAME, type.getComplexType().getSimpleName())).build()) + .replaceText(VALUE_REPLACER_BUILDER.replacement(String.valueOf(parsedValue)).build()) + .replaceText(PLAYER_REPLACER_BUILDER.replacement(target.getName()).build()) + ); + case NEW_VALUE_IS_DIFFERENT_TYPE -> plugin.sms(sender, Messages.META_VALUE_TYPE_MISMATCH.message() + .replaceText(VALUE_REPLACER_BUILDER.replacement(String.valueOf(parsedValue)).build()) + .replaceText(TYPE_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.TYPE_NAME, type.getComplexType().getSimpleName())).build())); + case EXISTENT_VALUE_IS_DIFFERENT_TYPE -> plugin.sms(sender, Messages.META_EXISTENT_VALUE_WRONG_TYPE.message() + .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build())); + default -> plugin.sms(sender, Messages.WRONG_USAGE_META_SET_COMMAND); } } @@ -394,26 +387,19 @@ private void handleRemoveMeta(@NotNull final CommandSender sender, @NotNull fina @NotNull final Map context) { final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().removeMetaValue(target, namespacedKey, type); switch (result) { - case SUCCESS: - plugin.sms(sender, Messages.META_VALUE_REMOVED.message() - .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build()) - .replaceText(TYPE_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.TYPE_NAME, type.getComplexType().getSimpleName())).build()) - .replaceText(PLAYER_REPLACER_BUILDER.replacement(target.getName()).build()) - ); - return; - case EXISTENT_VALUE_IS_DIFFERENT_TYPE: - plugin.sms(sender, Messages.META_EXISTENT_VALUE_WRONG_TYPE.message() - .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build())); - return; - case VALUE_NOT_FOUND: - plugin.sms(sender, Messages.NO_META_VALUE.message() - .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build()) - .replaceText(TYPE_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.TYPE_NAME, type.getComplexType().getSimpleName())).build()) - .replaceText(PLAYER_REPLACER_BUILDER.replacement(target.getName()).build()) - ); - return; - default: - plugin.sms(sender, Messages.WRONG_USAGE_META_REMOVE_COMMAND); + case SUCCESS -> plugin.sms(sender, Messages.META_VALUE_REMOVED.message() + .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build()) + .replaceText(TYPE_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.TYPE_NAME, type.getComplexType().getSimpleName())).build()) + .replaceText(PLAYER_REPLACER_BUILDER.replacement(target.getName()).build()) + ); + case EXISTENT_VALUE_IS_DIFFERENT_TYPE -> plugin.sms(sender, Messages.META_EXISTENT_VALUE_WRONG_TYPE.message() + .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build())); + case VALUE_NOT_FOUND -> plugin.sms(sender, Messages.NO_META_VALUE.message() + .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build()) + .replaceText(TYPE_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.TYPE_NAME, type.getComplexType().getSimpleName())).build()) + .replaceText(PLAYER_REPLACER_BUILDER.replacement(target.getName()).build()) + ); + default -> plugin.sms(sender, Messages.WRONG_USAGE_META_REMOVE_COMMAND); } } @@ -422,21 +408,18 @@ private void handleSwitchMeta(@NotNull final CommandSender sender, @NotNull fina final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().switchMetaValue(target, namespacedKey); switch (result) { - case SUCCESS: + case SUCCESS -> { final Object newValue = plugin.getPersistentMetaHandler().getMetaValue(target, namespacedKey, DataType.BOOLEAN); plugin.sms(sender, Messages.META_VALUE_SWITCHED.message() - .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build()) - .replaceText(NEW_VALUE_REPLACER_BUILDER.replacement(String.valueOf(newValue)).build()) - .replaceText(PLAYER_REPLACER_BUILDER.replacement(target.getName()).build()) + .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build()) + .replaceText(NEW_VALUE_REPLACER_BUILDER.replacement(String.valueOf(newValue)).build()) + .replaceText(PLAYER_REPLACER_BUILDER.replacement(target.getName()).build()) ); - return; - case EXISTENT_VALUE_IS_DIFFERENT_TYPE: - plugin.sms(sender, Messages.META_EXISTENT_VALUE_WRONG_TYPE.message() - .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build())); - return; - default: - plugin.sms(sender, Messages.WRONG_USAGE_META_SWITCH_COMMAND); + } + case EXISTENT_VALUE_IS_DIFFERENT_TYPE -> plugin.sms(sender, Messages.META_EXISTENT_VALUE_WRONG_TYPE.message() + .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build())); + default -> plugin.sms(sender, Messages.WRONG_USAGE_META_SWITCH_COMMAND); } } @@ -449,34 +432,29 @@ private void handleAddMeta(@NotNull final CommandSender sender, @NotNull final P if (!(parsedValue instanceof Number)) { plugin.sms(sender, Messages.META_ADD_TYPE_MISMATCH.message() - .replaceText(VALUE_REPLACER_BUILDER.replacement(String.valueOf(value)).build())); + .replaceText(VALUE_REPLACER_BUILDER.replacement(value).build())); return; } final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().addMetaValue(target, namespacedKey, type, (Number) parsedValue); switch (result) { - case SUCCESS: + case SUCCESS -> { final Object newValue = plugin.getPersistentMetaHandler().getMetaValue(target, namespacedKey, type); plugin.sms(sender, Messages.META_VALUE_ADDED.message() - .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build()) - .replaceText(TYPE_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.TYPE_NAME, type.getComplexType().getSimpleName())).build()) - .replaceText(VALUE_REPLACER_BUILDER.replacement(String.valueOf(parsedValue)).build()) - .replaceText(NEW_VALUE_REPLACER_BUILDER.replacement(String.valueOf(newValue)).build()) - .replaceText(PLAYER_REPLACER_BUILDER.replacement(target.getName()).build()) + .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build()) + .replaceText(TYPE_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.TYPE_NAME, type.getComplexType().getSimpleName())).build()) + .replaceText(VALUE_REPLACER_BUILDER.replacement(String.valueOf(parsedValue)).build()) + .replaceText(NEW_VALUE_REPLACER_BUILDER.replacement(String.valueOf(newValue)).build()) + .replaceText(PLAYER_REPLACER_BUILDER.replacement(target.getName()).build()) ); - return; - case INVALID_TYPE: - plugin.sms(sender, Messages.META_ADD_TYPE_MISMATCH.message() - .replaceText(VALUE_REPLACER_BUILDER.replacement(String.valueOf(parsedValue)).build())); - return; - case EXISTENT_VALUE_IS_DIFFERENT_TYPE: - plugin.sms(sender, Messages.META_EXISTENT_VALUE_WRONG_TYPE.message() - .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build())); - return; - default: - plugin.sms(sender, Messages.WRONG_USAGE_META_ADD_COMMAND); + } + case INVALID_TYPE -> plugin.sms(sender, Messages.META_ADD_TYPE_MISMATCH.message() + .replaceText(VALUE_REPLACER_BUILDER.replacement(String.valueOf(parsedValue)).build())); + case EXISTENT_VALUE_IS_DIFFERENT_TYPE -> plugin.sms(sender, Messages.META_EXISTENT_VALUE_WRONG_TYPE.message() + .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build())); + default -> plugin.sms(sender, Messages.WRONG_USAGE_META_ADD_COMMAND); } } @@ -489,34 +467,29 @@ private void handleSubtractMeta(@NotNull final CommandSender sender, @NotNull fi if (!(parsedValue instanceof Number)) { plugin.sms(sender, Messages.META_SUBTRACT_TYPE_MISMATCH.message() - .replaceText(VALUE_REPLACER_BUILDER.replacement(String.valueOf(value)).build())); + .replaceText(VALUE_REPLACER_BUILDER.replacement(value).build())); return; } final PersistentMetaHandler.OperationResult result = plugin.getPersistentMetaHandler().subtractMetaValue(target, namespacedKey, type, (Number) parsedValue); switch (result) { - case SUCCESS: + case SUCCESS -> { final Object newValue = plugin.getPersistentMetaHandler().getMetaValue(target, namespacedKey, type); plugin.sms(sender, Messages.META_VALUE_SUBTRACTED.message() - .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build()) - .replaceText(TYPE_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.TYPE_NAME, type.getComplexType().getSimpleName())).build()) - .replaceText(VALUE_REPLACER_BUILDER.replacement(String.valueOf(parsedValue)).build()) - .replaceText(NEW_VALUE_REPLACER_BUILDER.replacement(String.valueOf(newValue)).build()) - .replaceText(PLAYER_REPLACER_BUILDER.replacement(target.getName()).build()) + .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build()) + .replaceText(TYPE_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.TYPE_NAME, type.getComplexType().getSimpleName())).build()) + .replaceText(VALUE_REPLACER_BUILDER.replacement(String.valueOf(parsedValue)).build()) + .replaceText(NEW_VALUE_REPLACER_BUILDER.replacement(String.valueOf(newValue)).build()) + .replaceText(PLAYER_REPLACER_BUILDER.replacement(target.getName()).build()) ); - return; - case INVALID_TYPE: - plugin.sms(sender, Messages.META_SUBTRACT_TYPE_MISMATCH.message() - .replaceText(VALUE_REPLACER_BUILDER.replacement(String.valueOf(parsedValue)).build())); - return; - case EXISTENT_VALUE_IS_DIFFERENT_TYPE: - plugin.sms(sender, Messages.META_EXISTENT_VALUE_WRONG_TYPE.message() - .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build())); - return; - default: - plugin.sms(sender, Messages.WRONG_USAGE_META_SUBTRACT_COMMAND); + } + case INVALID_TYPE -> plugin.sms(sender, Messages.META_SUBTRACT_TYPE_MISMATCH.message() + .replaceText(VALUE_REPLACER_BUILDER.replacement(String.valueOf(parsedValue)).build())); + case EXISTENT_VALUE_IS_DIFFERENT_TYPE -> plugin.sms(sender, Messages.META_EXISTENT_VALUE_WRONG_TYPE.message() + .replaceText(KEY_REPLACER_BUILDER.replacement(context.getOrDefault(ContextKeys.KEY_NAME, namespacedKey.toString())).build())); + default -> plugin.sms(sender, Messages.WRONG_USAGE_META_SUBTRACT_COMMAND); } } @@ -527,23 +500,12 @@ private void sendWrongUsageMessage(@NotNull final CommandSender sender, @Nullabl } switch (action) { - case SET: - plugin.sms(sender, Messages.WRONG_USAGE_META_SET_COMMAND); - return; - case REMOVE: - plugin.sms(sender, Messages.WRONG_USAGE_META_REMOVE_COMMAND); - return; - case ADD: - plugin.sms(sender, Messages.WRONG_USAGE_META_ADD_COMMAND); - return; - case SUBTRACT: - plugin.sms(sender, Messages.WRONG_USAGE_META_SUBTRACT_COMMAND); - return; - case SWITCH: - plugin.sms(sender, Messages.WRONG_USAGE_META_SWITCH_COMMAND); - return; - default: - plugin.sms(sender, Messages.WRONG_USAGE_META_COMMAND); + case SET -> plugin.sms(sender, Messages.WRONG_USAGE_META_SET_COMMAND); + case REMOVE -> plugin.sms(sender, Messages.WRONG_USAGE_META_REMOVE_COMMAND); + case ADD -> plugin.sms(sender, Messages.WRONG_USAGE_META_ADD_COMMAND); + case SUBTRACT -> plugin.sms(sender, Messages.WRONG_USAGE_META_SUBTRACT_COMMAND); + case SWITCH -> plugin.sms(sender, Messages.WRONG_USAGE_META_SWITCH_COMMAND); + default -> plugin.sms(sender, Messages.WRONG_USAGE_META_COMMAND); } } 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 33cb874c..1e58ed91 100644 --- a/src/main/java/com/extendedclip/deluxemenus/command/subcommand/OpenCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/command/subcommand/OpenCommand.java @@ -127,10 +127,10 @@ public void execute(final @NotNull CommandSender sender, final @NotNull List menu = Menu.getMenuByName(arguments.get(0)); + Optional menu = Menu.getMenuByName(arguments.getFirst()); if (menu.isEmpty()) { - plugin.sms(sender, Messages.INVALID_MENU.message().replaceText(MENU_REPLACER_BUILDER.replacement(arguments.get(0)).build())); + plugin.sms(sender, Messages.INVALID_MENU.message().replaceText(MENU_REPLACER_BUILDER.replacement(arguments.getFirst()).build())); return; } @@ -152,11 +152,11 @@ public void execute(final @NotNull CommandSender sender, final @NotNull List menu = Menu.getMenuByName(arguments.get(0)); + Optional menu = Menu.getMenuByName(arguments.getFirst()); if (menu.isEmpty()) { - plugin.sms(sender, Messages.INVALID_MENU.message().replaceText(MENU_REPLACER_BUILDER.replacement(arguments.get(0)).build())); + plugin.sms(sender, Messages.INVALID_MENU.message().replaceText(MENU_REPLACER_BUILDER.replacement(arguments.getFirst()).build())); return; } @@ -74,11 +74,11 @@ public void execute(final @NotNull CommandSender sender, final @NotNull List { ItemWrapper wrapper = new ItemWrapper(); if (c.contains(rPath + ".material")) { String materialName = c.getString(rPath + ".material"); try { if (!containsPlaceholders(materialName) && plugin.getItemHooks().values() - .stream() - .filter(x -> materialName.toLowerCase().startsWith(x.getPrefix())) - .findFirst() - .orElse(null) == null) + .stream() + .filter(x -> materialName.toLowerCase().startsWith(x.getPrefix())) + .findFirst() + .orElse(null) == null) Material.valueOf(materialName.toUpperCase()); } catch (Exception ex) { plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "has item requirement at path: " + rPath + " does not specify a valid Material name!"); @@ -948,11 +945,11 @@ private RequirementList getRequirements(FileConfiguration c, String path) { final ConfigurationSection modelDataComponent = c.getConfigurationSection(rPath + ".model_data_component"); if (modelDataComponent != null) { wrapper.setCustomModelDataComponent( - CustomModelDataComponent.builder() - .colors(modelDataComponent.getStringList("colors")) - .flags(modelDataComponent.getStringList("flags")) - .floats(modelDataComponent.getStringList("floats")) - .strings(modelDataComponent.getStringList("strings"))); + CustomModelDataComponent.builder() + .colors(modelDataComponent.getStringList("colors")) + .flags(modelDataComponent.getStringList("flags")) + .floats(modelDataComponent.getStringList("floats")) + .strings(modelDataComponent.getStringList("strings"))); } } @@ -982,92 +979,78 @@ private RequirementList getRequirements(FileConfiguration c, String path) { invert = type == RequirementType.DOES_NOT_HAVE_ITEM; req = new HasItemRequirement(plugin, wrapper, invert); - break; - case HAS_PERMISSION: - case DOES_NOT_HAVE_PERMISSION: + } + case HAS_PERMISSION, DOES_NOT_HAVE_PERMISSION -> { if (c.contains(rPath + ".permission")) { invert = type == RequirementType.DOES_NOT_HAVE_PERMISSION; req = new HasPermissionRequirement(c.getString(rPath + ".permission"), invert); } else { plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Has Permission requirement at path: " + rPath + " does not contain a permission: entry"); } - break; - case HAS_PERMISSIONS: - case DOES_NOT_HAVE_PERMISSIONS: + } + case HAS_PERMISSIONS, DOES_NOT_HAVE_PERMISSIONS -> { if (c.contains(rPath + ".permissions")) { invert = type == RequirementType.DOES_NOT_HAVE_PERMISSIONS; int minimum = -1; if (c.contains(rPath + ".minimum") && (minimum = c.getInt(rPath + ".minimum")) < 1) { plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Has Permissions requirement at path: " + rPath + " has a minimum lower than 1. All permissions will be checked" + DebugLevel.HIGHEST, + Level.WARNING, + "Has Permissions requirement at path: " + rPath + " has a minimum lower than 1. All permissions will be checked" ); minimum = -1; } List permissions = c.getStringList(rPath + ".permissions"); if (permissions.isEmpty()) { plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Has Permissions requirement at path: " + rPath + " has no permissions to check. Ignoring..." + DebugLevel.HIGHEST, + Level.WARNING, + "Has Permissions requirement at path: " + rPath + " has no permissions to check. Ignoring..." ); break; } else if (minimum > permissions.size()) { 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" + 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 { plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Has Permissions requirement at path: " + rPath + " does not contain permissions: entry" + DebugLevel.HIGHEST, + Level.WARNING, + "Has Permissions requirement at path: " + rPath + " does not contain permissions: entry" ); } - break; - case JAVASCRIPT: + } + case JAVASCRIPT -> { if (c.contains(rPath + ".expression")) { req = new JavascriptRequirement(plugin, c.getString(rPath + ".expression")); } else { plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Javascript requirement at path: " + rPath + " does not contain an expression: entry"); } - break; - case EQUAL_TO: - case NOT_EQUAL_TO: - case GREATER_THAN: - case GREATER_THAN_EQUAL_TO: - case LESS_THAN: - case LESS_THAN_EQUAL_TO: - case STRING_CONTAINS: - case STRING_EQUALS: - case STRING_EQUALS_IGNORECASE: - case STRING_DOES_NOT_CONTAIN: - case STRING_DOES_NOT_EQUAL: - case STRING_DOES_NOT_EQUAL_IGNORECASE: - case STRING_CONTAINS_IGNORECASE: - case STRING_DOES_NOT_CONTAIN_IGNORECASE: + } + case EQUAL_TO, NOT_EQUAL_TO, GREATER_THAN, GREATER_THAN_EQUAL_TO, LESS_THAN, LESS_THAN_EQUAL_TO, + STRING_CONTAINS, STRING_EQUALS, STRING_EQUALS_IGNORECASE, STRING_DOES_NOT_CONTAIN, + STRING_DOES_NOT_EQUAL, STRING_DOES_NOT_EQUAL_IGNORECASE, STRING_CONTAINS_IGNORECASE, + STRING_DOES_NOT_CONTAIN_IGNORECASE -> { if (c.contains(rPath + ".input") && c.contains(rPath + ".output")) { req = new InputResultRequirement(type, c.getString(rPath + ".input"), c.getString(rPath + ".output")); } else { 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: + } + case HAS_MONEY, DOES_NOT_HAVE_MONEY -> { if (c.contains(rPath + ".amount") || c.contains(rPath + ".placeholder")) { invert = type == RequirementType.DOES_NOT_HAVE_MONEY; req = new HasMoneyRequirement(plugin, c.getDouble(rPath + ".amount"), invert, c.getString(rPath + ".placeholder", null)); } else { 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: + } + case HAS_EXP, DOES_NOT_HAVE_EXP -> { if (c.contains(rPath + ".amount")) { if (!containsPlaceholders(c.getString(rPath + ".amount")) && !c.isInt(rPath + ".amount")) { plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Value at path: " + rPath + ".amount is not a placeholder or a number"); @@ -1078,9 +1061,8 @@ private RequirementList getRequirements(FileConfiguration c, String path) { } else { plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Has Exp requirement at path: " + rPath + " does not contain an amount: entry"); } - break; - case REGEX_MATCHES: - case REGEX_DOES_NOT_MATCH: + } + case REGEX_MATCHES, REGEX_DOES_NOT_MATCH -> { if (c.contains(rPath + ".input") && c.contains(rPath + ".regex")) { Pattern p = Pattern.compile(c.getString(rPath + ".regex")); invert = type == RequirementType.REGEX_DOES_NOT_MATCH; @@ -1088,9 +1070,8 @@ private RequirementList getRequirements(FileConfiguration c, String path) { } else { plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Regex requirement at path: " + rPath + " does not contain a input: or regex: entry"); } - break; - case IS_NEAR: - case IS_NOT_NEAR: + } + case IS_NEAR, IS_NOT_NEAR -> { if (c.contains(rPath + ".location") && c.contains(rPath + ".distance")) { invert = type == RequirementType.IS_NOT_NEAR; Location loc = LocationUtils.deserializeLocation(c.getString(rPath + ".location")); @@ -1101,9 +1082,8 @@ private RequirementList getRequirements(FileConfiguration c, String path) { } else { 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: + } + case HAS_META, DOES_NOT_HAVE_META -> { if (!VersionHelper.IS_PDC_VERSION) { plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "Has Meta requirement is not available for your server version!"); break; @@ -1115,8 +1095,8 @@ private RequirementList getRequirements(FileConfiguration c, String path) { } else { 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: + } + case STRING_LENGTH -> { if (c.contains(rPath + ".input") && (c.contains(rPath + ".min") || c.contains(rPath + ".max"))) { int min = c.getInt(rPath + ".min", 0); Integer max = null; @@ -1127,16 +1107,16 @@ private RequirementList getRequirements(FileConfiguration c, String path) { } else { 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: + } + case IS_OBJECT -> { if (c.contains(rPath + ".input") && c.contains(rPath + ".object")) { req = new IsObjectRequirement(c.getString(rPath + ".input"), c.getString(rPath + ".object")); } else { plugin.debug(DebugLevel.HIGHEST, Level.WARNING, "String length requirement at path: " + rPath + " does not contain an input: or object:"); } - break; - default: - break; + } + default -> { + } } if (req != null) { @@ -1240,26 +1220,22 @@ private ClickHandler getClickHandler(FileConfiguration c, String configPath) { if (!actions.isEmpty()) { - handler = new ClickHandler() { - - @Override - public void onClick(@NotNull final MenuHolder holder) { - - for (ClickAction action : actions) { + handler = holder -> { - if (!action.checkChance(holder)) { - continue; - } + for (ClickAction action : actions) { - final ClickActionTask actionTask = new ClickActionTask(plugin, holder.getViewer().getUniqueId(), action.getType(), action.getExecutable(), holder.getTypedArgs(), holder.parsePlaceholdersInArguments(), holder.parsePlaceholdersAfterArguments()); + if (!action.checkChance(holder)) { + continue; + } - if (action.hasDelay()) { - actionTask.runTaskLater(plugin, action.getDelay(holder)); - continue; - } + final ClickActionTask actionTask = new ClickActionTask(plugin, holder.getViewer().getUniqueId(), action.getType(), action.getExecutable(), holder.getTypedArgs(), holder.parsePlaceholdersInArguments(), holder.parsePlaceholdersAfterArguments()); - actionTask.runTask(plugin); + if (action.hasDelay()) { + actionTask.runTaskLater(plugin, action.getDelay(holder)); + continue; } + + actionTask.runTask(plugin); } }; } diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableBlocksHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableBlocksHook.java index 58ec1bf2..d92173b2 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableBlocksHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/ExecutableBlocksHook.java @@ -15,7 +15,6 @@ public class ExecutableBlocksHook implements ItemHook, SimpleCache { private final Map cache = new ConcurrentHashMap<>(); - @SuppressWarnings("deprecation") @Override public ItemStack getItem(@NotNull String... arguments) { if (arguments.length == 0) { diff --git a/src/main/java/com/extendedclip/deluxemenus/hooks/SimpleItemGeneratorHook.java b/src/main/java/com/extendedclip/deluxemenus/hooks/SimpleItemGeneratorHook.java index 58e7eccb..8ec99f25 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/SimpleItemGeneratorHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/SimpleItemGeneratorHook.java @@ -15,7 +15,6 @@ public class SimpleItemGeneratorHook implements ItemHook, SimpleCache { - private final Map cache = new ConcurrentHashMap<>(); private final DeluxeMenus plugin; @@ -34,9 +33,9 @@ public ItemStack getItem(@NotNull Player holder, @NotNull String... arguments) { return errorItem("Item arguments are absent."); } - final ItemStack item = cache.computeIfAbsent(arguments[0], id -> { - return SimpleItemGenerator.get().bakeItem(id, holder).orElse(null); - }); + final ItemStack item = cache.computeIfAbsent(arguments[0], id -> + SimpleItemGenerator.get().bakeItem(id, holder).orElse(null) + ); return item == null? errorItem("Item %s wasn't found by SimpleItemGenerator.", arguments[0]) : item.clone(); } diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java index 9025a29b..a896cdb8 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuItem.java @@ -81,11 +81,7 @@ public static ItemStack base64ToItemStack(String data) { return (ItemStack) object; } return null; - } catch (IllegalArgumentException e) { - return null; - } catch (IOException e) { - return null; - } catch (ClassNotFoundException e) { + } catch (IllegalArgumentException | ClassNotFoundException | IOException e) { return null; } } @@ -282,20 +278,18 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { LoreAppendMode mode = this.options.loreAppendMode().orElse(LoreAppendMode.OVERRIDE); if (!this.options.hasLore() && this.options.loreAppendMode().isEmpty()) mode = LoreAppendMode.IGNORE; switch (mode) { - case IGNORE: // DM lore is not added at all - lore.addAll(itemLore); - break; - case TOP: // DM lore is added at the top + case IGNORE -> // DM lore is not added at all + lore.addAll(itemLore); + case TOP -> { lore.addAll(getMenuItemLore(holder, this.options.lore())); lore.addAll(itemLore); - break; - case BOTTOM: // DM lore is bottom at the bottom + } + case BOTTOM -> { lore.addAll(itemLore); lore.addAll(getMenuItemLore(holder, this.options.lore())); - break; - case OVERRIDE: // Lore from DM overrides the lore from the item - lore.addAll(getMenuItemLore(holder, this.options.lore())); - break; + } + case OVERRIDE -> // Lore from DM overrides the lore from the item + lore.addAll(getMenuItemLore(holder, this.options.lore())); } itemMeta.setLore(lore); @@ -373,69 +367,65 @@ public ItemStack getItemStack(@NotNull final MenuHolder holder) { Level.WARNING, "Trim pattern is not set for item with trim material " + trimMaterialName.get() ); - } else if (trimPatternName.isPresent()) { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Trim material is not set for item with trim pattern " + trimPatternName.get() - ); - } + } else trimPatternName.ifPresent(s -> plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Trim material is not set for item with trim pattern " + s + )); } - if (itemMeta instanceof LeatherArmorMeta && this.options.rgb().isPresent()) { - final LeatherArmorMeta leatherArmorMeta = (LeatherArmorMeta) itemMeta; + switch (itemMeta) { + case LeatherArmorMeta leatherArmorMeta when this.options.rgb().isPresent() -> { - final Color color = parseRGBColor(holder.setPlaceholdersAndArguments(this.options.rgb().get())); - if (color != null) { - leatherArmorMeta.setColor(color); - } else { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Invalid rgb colors found for leather armor: " + this.options.rgb().get() - ); - } + final Color color = parseRGBColor(holder.setPlaceholdersAndArguments(this.options.rgb().get())); + if (color != null) { + leatherArmorMeta.setColor(color); + } else { + plugin.debug( + DebugLevel.HIGHEST, + Level.WARNING, + "Invalid rgb colors found for leather armor: " + this.options.rgb().get() + ); + } - itemStack.setItemMeta(leatherArmorMeta); - } else if (itemMeta instanceof FireworkEffectMeta && this.options.rgb().isPresent()) { - final FireworkEffectMeta fireworkEffectMeta = (FireworkEffectMeta) itemMeta; - final Color color = parseRGBColor(holder.setPlaceholdersAndArguments(this.options.rgb().get())); - if (color != null) { - fireworkEffectMeta.setEffect(FireworkEffect.builder().withColor(color).build()); - } else { - plugin.debug( - DebugLevel.HIGHEST, - Level.WARNING, - "Invalid RGB color found for firework or firework star: " + this.options.rgb().get() - ); + itemStack.setItemMeta(leatherArmorMeta); } - itemStack.setItemMeta(fireworkEffectMeta); - } else if (itemMeta instanceof EnchantmentStorageMeta && !this.options.enchantments().isEmpty()) { - final EnchantmentStorageMeta enchantmentStorageMeta = (EnchantmentStorageMeta) itemMeta; - for (final Map.Entry entry : this.options.enchantments().entrySet()) { - final boolean result = enchantmentStorageMeta.addStoredEnchant(entry.getKey(), entry.getValue(), true); - if (!result) { + case FireworkEffectMeta fireworkEffectMeta when this.options.rgb().isPresent() -> { + final Color color = parseRGBColor(holder.setPlaceholdersAndArguments(this.options.rgb().get())); + if (color != null) { + fireworkEffectMeta.setEffect(FireworkEffect.builder().withColor(color).build()); + } else { plugin.debug( - DebugLevel.HIGHEST, - Level.INFO, - "Failed to add enchantment " + entry.getKey().getName() + " to item " + itemStack.getType() + DebugLevel.HIGHEST, + Level.WARNING, + "Invalid RGB color found for firework or firework star: " + this.options.rgb().get() ); } + itemStack.setItemMeta(fireworkEffectMeta); + } + case EnchantmentStorageMeta enchantmentStorageMeta when !this.options.enchantments().isEmpty() -> { + for (final Map.Entry entry : this.options.enchantments().entrySet()) { + final boolean result = enchantmentStorageMeta.addStoredEnchant(entry.getKey(), entry.getValue(), true); + if (!result) { + plugin.debug( + DebugLevel.HIGHEST, + Level.INFO, + "Failed to add enchantment " + entry.getKey().getName() + " to item " + itemStack.getType() + ); + } + } + itemStack.setItemMeta(enchantmentStorageMeta); } - itemStack.setItemMeta(enchantmentStorageMeta); - } else { - itemStack.setItemMeta(itemMeta); + default -> itemStack.setItemMeta(itemMeta); } if (!(itemMeta instanceof EnchantmentStorageMeta) && !this.options.enchantments().isEmpty()) { this.options.enchantments().forEach((enchantment, level) -> itemMeta.addEnchant(enchantment, level, true)); } - if (this.options.lightLevel().isPresent() && itemMeta instanceof BlockDataMeta) { - final BlockDataMeta blockDataMeta = (BlockDataMeta) itemMeta; + if (this.options.lightLevel().isPresent() && itemMeta instanceof BlockDataMeta blockDataMeta) { final BlockData blockData = blockDataMeta.getBlockData(itemStack.getType()); - if (blockData instanceof Light) { - final Light light = (Light) blockData; + if (blockData instanceof Light light) { final String parsedLightLevel = holder.setPlaceholdersAndArguments(this.options.lightLevel().get()); try { final int lightLevel = Math.min(Integer.parseInt(parsedLightLevel), light.getMaximumLevel()); diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java b/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java index 8a98c02a..3b5c7c67 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/command/RegistrableMenuCommand.java @@ -13,6 +13,7 @@ import org.bukkit.command.SimpleCommandMap; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NonNull; import java.lang.reflect.Field; import java.util.Arrays; @@ -35,7 +36,7 @@ public class RegistrableMenuCommand extends Command { public RegistrableMenuCommand(final @NotNull DeluxeMenus plugin, final @NotNull Menu menu) { - super(menu.options().commands().isEmpty() ? menu.options().name() : menu.options().commands().get(0)); + super(menu.options().commands().isEmpty() ? menu.options().name() : menu.options().commands().getFirst()); this.plugin = plugin; this.scheduler = plugin.getScheduler(); this.menu = menu; @@ -46,12 +47,12 @@ public RegistrableMenuCommand(final @NotNull DeluxeMenus plugin, } @Override - public boolean execute(final @NotNull CommandSender sender, final @NotNull String commandLabel, final @NotNull String[] typedArgs) { + public boolean execute(final @NotNull CommandSender sender, final @NotNull String commandLabel, final @NotNull String @NonNull [] typedArgs) { if (this.unregistered) { throw new IllegalStateException("This command was unregistered!"); } - if (!(sender instanceof Player)) { + if (!(sender instanceof Player player)) { Msg.msg(sender, "Menus can only be opened by players!"); return true; } @@ -63,7 +64,7 @@ public boolean execute(final @NotNull CommandSender sender, final @NotNull Strin if (typedArgs.length < menu.options().arguments().size()) { if (menu.options().argumentsUsageMessage().isPresent()) { String usageMessage = menu.options().argumentsUsageMessage().get(); - Msg.msg(sender, StringUtils.replacePlaceholders(usageMessage, (Player) sender)); + Msg.msg(sender, StringUtils.replacePlaceholders(usageMessage, player)); } return true; } @@ -82,7 +83,6 @@ public boolean execute(final @NotNull CommandSender sender, final @NotNull Strin } } - Player player = (Player) sender; plugin.debug(DebugLevel.LOWEST, Level.INFO, "opening menu: " + menu.options().name()); menu.openMenu(player, argMap, null); return true; @@ -145,6 +145,7 @@ public void unregister() { knownCommands = SimpleCommandMap.class.getDeclaredField("knownCommands"); knownCommands.setAccessible(true); + //noinspection unchecked 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. diff --git a/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java b/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java index bab7d857..f6c077ee 100644 --- a/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java +++ b/src/main/java/com/extendedclip/deluxemenus/persistentmeta/PersistentMetaHandler.java @@ -120,9 +120,9 @@ public Map getMetaValues( return player.getPersistentDataContainer().getKeys().stream() .filter(key -> player.getPersistentDataContainer().has(key, type.getPDType())) .map(key -> Pair.of(key.toString(), player.getPersistentDataContainer().get(key, type.getPDType()))) - .filter(entry -> entry.getValue() != null) - .filter(entry -> type.isSupported(entry.getValue())) - .collect(Collectors.toMap(Pair::getKey, Pair::getValue)); + .filter(entry -> entry.value() != null) + .filter(entry -> type.isSupported(entry.value())) + .collect(Collectors.toMap(Pair::key, Pair::value)); } /** @@ -223,7 +223,7 @@ public Map getMetaValues( return OperationResult.EXISTENT_VALUE_IS_DIFFERENT_TYPE; } - player.getPersistentDataContainer().set(key, DataType.BOOLEAN.getPDType(), currentValue.equalsIgnoreCase("true") ? "false" : "true"); + player.getPersistentDataContainer().set(key, DataType.BOOLEAN.getPDType(), Boolean.toString(!currentValue.equalsIgnoreCase("true"))); return OperationResult.SUCCESS; } @@ -352,30 +352,32 @@ public Map getMetaValues( final Object parsedValue = parseValueByType(type, args.length >= 4 ? args[3] : null); - switch (action) { - case SET: + return switch (action) { + case SET -> { if (parsedValue == null) { - return OperationResult.NEW_VALUE_IS_DIFFERENT_TYPE; + yield OperationResult.NEW_VALUE_IS_DIFFERENT_TYPE; } - return setMetaValue(player, key, type, parsedValue); - case REMOVE: - return removeMetaValue(player, key, type); - case ADD: + yield setMetaValue(player, key, type, parsedValue); + } + case REMOVE -> removeMetaValue(player, key, type); + case ADD -> { if (!(parsedValue instanceof Number)) { - return OperationResult.NEW_VALUE_IS_DIFFERENT_TYPE; + yield OperationResult.NEW_VALUE_IS_DIFFERENT_TYPE; } - return addMetaValue(player, key, type, (Number) parsedValue); - case SUBTRACT: + yield addMetaValue(player, key, type, (Number) parsedValue); + } + case SUBTRACT -> { if (!(parsedValue instanceof Number)) { - return OperationResult.NEW_VALUE_IS_DIFFERENT_TYPE; + yield OperationResult.NEW_VALUE_IS_DIFFERENT_TYPE; } - return subtractMetaValue(player, key, type, (Number) parsedValue); - } + yield subtractMetaValue(player, key, type, (Number) parsedValue); + } + default -> OperationResult.INVALID_SYNTAX; + }; - return OperationResult.INVALID_SYNTAX; } /** @@ -424,7 +426,6 @@ public Map getMetaValues( * @param key The string to parse. * @return The {@link NamespacedKey} or null if the key could not be parsed. */ - @SuppressWarnings("UnstableApiUsage") public @Nullable NamespacedKey getKey(@NotNull final String key) { final NamespacedKey namespacedKey; diff --git a/src/main/java/com/extendedclip/deluxemenus/placeholder/Expansion.java b/src/main/java/com/extendedclip/deluxemenus/placeholder/Expansion.java index 6a1a6ea0..ac467470 100644 --- a/src/main/java/com/extendedclip/deluxemenus/placeholder/Expansion.java +++ b/src/main/java/com/extendedclip/deluxemenus/placeholder/Expansion.java @@ -70,13 +70,13 @@ public boolean persist() { final String parsedInputLower = parsedInput.toLowerCase(); switch (parsedInputLower) { - case "is_in_menu": { + case "is_in_menu" -> { return getBooleanAsString(Menu.getMenuHolder(onlinePlayer).isPresent()); } - case "opened_menu": { + case "opened_menu" -> { return Menu.getOpenMenu(onlinePlayer).map(Menu::options).map(MenuOptions::name).orElse(""); } - case "last_menu": { + case "last_menu" -> { return Menu.getLastMenu(onlinePlayer).map(Menu::options).map(MenuOptions::name).orElse(""); } } diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java index 9c1a1442..8a33e741 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/HasItemRequirement.java @@ -187,7 +187,7 @@ private boolean itemModelComponentContains(MenuHolder holder, @NotNull final Cus .map(holder::setPlaceholdersAndArguments) .map(StringUtils::parseRGBColor) .filter(Objects::nonNull) - .collect(Collectors.toList()); + .toList(); for (Color color : colors) { if (!modelData.getColors().contains(color)) { @@ -201,7 +201,7 @@ private boolean itemModelComponentContains(MenuHolder holder, @NotNull final Cus .stream() .map(holder::setPlaceholdersAndArguments) .map(Boolean::parseBoolean) - .collect(Collectors.toList()); + .toList(); for (Boolean flag : flags) { if (!modelData.getFlags().contains(flag)) { @@ -215,7 +215,7 @@ private boolean itemModelComponentContains(MenuHolder holder, @NotNull final Cus .stream() .map(holder::setPlaceholdersAndArguments) .map(Float::parseFloat) - .collect(Collectors.toList()); + .toList(); for (Float floatValue : floats) { if (!modelData.getFloats().contains(floatValue)) { @@ -228,7 +228,7 @@ private boolean itemModelComponentContains(MenuHolder holder, @NotNull final Cus final List strings = wrapper.strings() .stream() .map(holder::setPlaceholdersAndArguments) - .collect(Collectors.toList()); + .toList(); for (String string : strings) { if (!modelData.getStrings().contains(string)) { diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/InputResultRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/InputResultRequirement.java index 93932422..8b11ce83 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/InputResultRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/InputResultRequirement.java @@ -21,24 +21,32 @@ public boolean evaluate(MenuHolder holder) { String parsedResult = holder.setPlaceholdersAndArguments(this.result); switch (type) { - case STRING_CONTAINS: - return parsedInput.contains(parsedResult); - case STRING_EQUALS: - return parsedInput.equals(parsedResult); - case STRING_EQUALS_IGNORECASE: - return parsedInput.equalsIgnoreCase(parsedResult); - case STRING_DOES_NOT_CONTAIN: - return !parsedInput.contains(parsedResult); - case STRING_DOES_NOT_EQUAL: - return !parsedInput.equals(parsedResult); - case STRING_DOES_NOT_EQUAL_IGNORECASE: - return !parsedInput.equalsIgnoreCase(parsedResult); - case STRING_CONTAINS_IGNORECASE: - return parsedInput.toLowerCase().contains(parsedResult.toLowerCase()); - case STRING_DOES_NOT_CONTAIN_IGNORECASE: - return !parsedInput.toLowerCase().contains(parsedResult.toLowerCase()); - default: - break; + case STRING_CONTAINS -> { + return parsedInput.contains(parsedResult); + } + case STRING_EQUALS -> { + return parsedInput.equals(parsedResult); + } + case STRING_EQUALS_IGNORECASE -> { + return parsedInput.equalsIgnoreCase(parsedResult); + } + case STRING_DOES_NOT_CONTAIN -> { + return !parsedInput.contains(parsedResult); + } + case STRING_DOES_NOT_EQUAL -> { + return !parsedInput.equals(parsedResult); + } + case STRING_DOES_NOT_EQUAL_IGNORECASE -> { + return !parsedInput.equalsIgnoreCase(parsedResult); + } + case STRING_CONTAINS_IGNORECASE -> { + return parsedInput.toLowerCase().contains(parsedResult.toLowerCase()); + } + case STRING_DOES_NOT_CONTAIN_IGNORECASE -> { + return !parsedInput.toLowerCase().contains(parsedResult.toLowerCase()); + } + default -> { + } } double in; @@ -64,23 +72,14 @@ public boolean evaluate(MenuHolder holder) { return false; } - switch (type) { - case GREATER_THAN: - return in > res; - case GREATER_THAN_EQUAL_TO: - return in >= res; - case EQUAL_TO: - return in == res; - case NOT_EQUAL_TO: - return in != res; - case LESS_THAN_EQUAL_TO: - return in <= res; - case LESS_THAN: - return in < res; - default: - break; - } - return false; + return switch (type) { + case GREATER_THAN -> in > res; + case GREATER_THAN_EQUAL_TO -> in >= res; + case EQUAL_TO -> in == res; + case NOT_EQUAL_TO -> in != res; + case LESS_THAN_EQUAL_TO -> in <= res; + case LESS_THAN -> in < res; + default -> false; + }; } } - diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/IsNearRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/IsNearRequirement.java index d207aba7..e5b575a5 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/IsNearRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/IsNearRequirement.java @@ -24,6 +24,6 @@ public boolean evaluate(MenuHolder holder) { if (holder.getViewer().getWorld().getName().equals(location.getWorld().getName())) { withinRange = holder.getViewer().getLocation().distance(location) < distance; } - return invert ? !withinRange : withinRange; + return invert != withinRange; } } diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/IsObjectRequirement.java b/src/main/java/com/extendedclip/deluxemenus/requirement/IsObjectRequirement.java index 467908fa..f9a1b79a 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/IsObjectRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/IsObjectRequirement.java @@ -24,27 +24,32 @@ public boolean evaluate(MenuHolder holder) { String toCheck = holder.setPlaceholdersAndArguments(input); switch (object) { - case "int": + case "int" -> { return Ints.tryParse(toCheck) != null; - case "double": + } + case "double" -> { return Doubles.tryParse(toCheck) != null; - case "player": + } + case "player" -> { try { UUID id = UUID.fromString(toCheck); return Bukkit.getPlayer(id) != null; } catch (IllegalArgumentException e) { return Bukkit.getPlayerExact(toCheck) != null; } - case "uuid": + } + case "uuid" -> { try { UUID.fromString(toCheck); return true; } catch (IllegalArgumentException e) { return false; } - default: + } + default -> { 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 9fcc1ff2..0958d0cd 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/JavascriptRequirement.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/JavascriptRequirement.java @@ -27,8 +27,8 @@ public JavascriptRequirement(final @NotNull DeluxeMenus plugin, String expressio this.expression = expression; if (engine == null) { if (manager.isProvidedFor(ScriptEngineManager.class)) { - final RegisteredServiceProvider provider = manager.getRegistration(ScriptEngineManager.class); - engine = (ScriptEngineManager) provider.getProvider(); + final RegisteredServiceProvider provider = manager.getRegistration(ScriptEngineManager.class); + engine = provider.getProvider(); } else { engine = new ScriptEngineManager(); manager.register(ScriptEngineManager.class, engine, plugin, ServicePriority.Highest); diff --git a/src/main/java/com/extendedclip/deluxemenus/requirement/RequirementType.java b/src/main/java/com/extendedclip/deluxemenus/requirement/RequirementType.java index d36213f4..61ce60b1 100644 --- a/src/main/java/com/extendedclip/deluxemenus/requirement/RequirementType.java +++ b/src/main/java/com/extendedclip/deluxemenus/requirement/RequirementType.java @@ -31,10 +31,10 @@ public enum RequirementType { Arrays.asList("amount", "placeholder")), HAS_EXP(Arrays.asList("has exp", "hasexp", "exp"), "Checks if a player has enough exp", - Arrays.asList("amount")), + List.of("amount")), DOES_NOT_HAVE_EXP(Arrays.asList("!has exp", "!hasexp", "!exp"), "Checks if a player has enough exp", - Arrays.asList("amount")), + List.of("amount")), HAS_PERMISSION(Arrays.asList("has permission", "has perm", "haspermission", "hasperm", "perm"), "Checks if a player has a specific permission", Collections.singletonList("permission")), DOES_NOT_HAVE_PERMISSION( @@ -86,10 +86,10 @@ public enum RequirementType { REGEX_DOES_NOT_MATCH(Arrays.asList("!regex matches", "!regex"), "Checks if a placeholder parsed string does not match a regex pattern", Arrays.asList("input", "regex")), - STRING_LENGTH(Arrays.asList("string length"), + STRING_LENGTH(List.of("string length"), "Checks if the given string's length is between the provided minimum and (optionally) maximum.", Arrays.asList("input", "min", "max")), - IS_OBJECT(Arrays.asList("is object"), + IS_OBJECT(List.of("is object"), "Checks if the given string can be parsed as a given Java object.", Arrays.asList("input", "object")); diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/DebugLevel.java b/src/main/java/com/extendedclip/deluxemenus/utils/DebugLevel.java index d21e06a7..d89e701f 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/DebugLevel.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/DebugLevel.java @@ -17,7 +17,7 @@ public enum DebugLevel { private final String[] names; private final int priority; - private DebugLevel(final int priority, @NotNull final String... names) { + DebugLevel(final int priority, @NotNull final String... names) { this.priority = priority; this.names = names; } diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/ExpUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/ExpUtils.java index e1d0f4f1..023a5cc6 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/ExpUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/ExpUtils.java @@ -26,7 +26,7 @@ public static void setExp( final String lowerCase = stringAmount.toLowerCase(Locale.ENGLISH); if (stringAmount.contains("l")) { - final int neededLevel = Integer.parseInt(lowerCase.replaceAll("l", "")) + target.getLevel(); + final int neededLevel = Integer.parseInt(lowerCase.replace("l", "")) + target.getLevel(); amount = getExpToLevel(neededLevel) + (getTotalExperience(target) - getExpToLevel(target.getLevel())); setTotalExperience(target, 0); } diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/Pair.java b/src/main/java/com/extendedclip/deluxemenus/utils/Pair.java index 3ba367cc..78bd7a43 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/Pair.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/Pair.java @@ -2,11 +2,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NonNull; -public class Pair { - - private final K key; - private final V value; +public record Pair(K key, V value) { public static Pair of(@NotNull final K key, @Nullable final V value) { return new Pair<>(key, value); @@ -21,16 +19,18 @@ public Pair(@NotNull final K key, @Nullable final V value) { this.value = value; } - public @NotNull K getKey() { + @Override + public @NotNull K key() { return key; } - public @Nullable V getValue() { + @Override + public @Nullable V value() { return value; } @Override - public String toString() { + public @NonNull String toString() { return "Pair{" + "key=" + key + ", value=" + key + '}'; } } diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java index 38ad9457..c25c64f7 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/SkullUtils.java @@ -81,8 +81,7 @@ public static ItemStack getSkullByBase64EncodedTextureUrl(@NotNull final DeluxeM } public static String getTextureFromSkull(final DeluxeMenus plugin, ItemStack item) { - if (!(item.getItemMeta() instanceof SkullMeta)) return null; - SkullMeta meta = (SkullMeta) item.getItemMeta(); + if (!(item.getItemMeta() instanceof SkullMeta meta)) return null; if (VersionHelper.HAS_PLAYER_PROFILES) { PlayerProfile profile = meta.getOwnerProfile(); @@ -150,8 +149,7 @@ public static ItemStack getSkullByName(@NotNull final DeluxeMenus plugin, @NotNu } public static String getSkullOwner(ItemStack skull) { - if (skull == null || !(skull.getItemMeta() instanceof SkullMeta)) return null; - SkullMeta meta = (SkullMeta) skull.getItemMeta(); + if (skull == null || !(skull.getItemMeta() instanceof SkullMeta meta)) return null; if (!VersionHelper.IS_SKULL_OWNER_LEGACY) { if (meta.getOwningPlayer() == null) return null; @@ -183,7 +181,7 @@ private static GameProfile getGameProfile(@NotNull final String base64Url) { */ @NotNull private static PlayerProfile getPlayerProfile(@NotNull final DeluxeMenus plugin, @NotNull final String base64Url) { - final PlayerProfile profile = Bukkit.createPlayerProfile(UUID.randomUUID()); + final PlayerProfile profile = Bukkit.createProfile(UUID.randomUUID()); final String decodedBase64 = decodeSkinUrl(base64Url); if (decodedBase64 == null) { diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java b/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java index 87e9a943..a676f056 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/VersionHelper.java @@ -185,7 +185,6 @@ private static int getCurrentVersion() { else stringBuilder.append(patch.replace(".", "")); } - //noinspection UnstableApiUsage final Integer version = Ints.tryParse(stringBuilder.toString()); // Should never fail diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml new file mode 100644 index 00000000..98eca40f --- /dev/null +++ b/src/main/resources/paper-plugin.yml @@ -0,0 +1,5 @@ +name: DeluxeMenus +main: com.extendedclip.deluxemenus.DeluxeMenus +version: ${version} +api-version: '1.13' +folia-supported: true From 933e0dc3ce49499c6e068a81290489d6e6973751 Mon Sep 17 00:00:00 2001 From: RootBeer Date: Wed, 22 Apr 2026 17:35:49 -0400 Subject: [PATCH 19/24] Fix Paper support fully --- .../command/DeluxeMenusCommand.java | 44 +++++++++---------- src/main/resources/paper-plugin.yml | 10 ++++- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java b/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java index 382440e5..f0870718 100644 --- a/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java +++ b/src/main/java/com/extendedclip/deluxemenus/command/DeluxeMenusCommand.java @@ -3,17 +3,18 @@ import com.extendedclip.deluxemenus.DeluxeMenus; import com.extendedclip.deluxemenus.command.subcommand.*; import com.extendedclip.deluxemenus.utils.Messages; +import io.papermc.paper.command.brigadier.BasicCommand; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; 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.CommandSender; -import org.bukkit.command.PluginCommand; -import org.bukkit.command.TabExecutor; import org.jetbrains.annotations.NotNull; import org.jspecify.annotations.NonNull; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -22,7 +23,7 @@ import static net.kyori.adventure.text.Component.text; -public class DeluxeMenusCommand implements TabExecutor { +public class DeluxeMenusCommand implements BasicCommand { private static final TextReplacementConfig.Builder VERSION_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); private static final TextReplacementConfig.Builder AUTHORS_REPLACER_BUILDER = TextReplacementConfig.builder().matchLiteral(""); @@ -35,48 +36,47 @@ public DeluxeMenusCommand(final @NotNull DeluxeMenus plugin) { } public boolean register() { - final PluginCommand command = this.plugin.getCommand("deluxemenus"); - if (command == null) { - return false; - } - - command.setExecutor(this); registerSubCommands(); + plugin.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> + event.registrar().register( + "deluxemenus", + "DeluxeMenus main command", + List.of("dm"), + this + ) + ); return true; } @Override - public boolean onCommand( - final @NotNull CommandSender sender, - final @NotNull Command command, - final @NotNull String label, + public void execute( + final @NotNull CommandSourceStack stack, final @NotNull String @NonNull [] args ) { + final CommandSender sender = stack.getSender(); final List arguments = Arrays.asList(args); - if (arguments.isEmpty()) { + if (arguments.isEmpty() || (arguments.size() == 1 && arguments.getFirst().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; + return; } final SubCommand subCommand = subCommands.get(arguments.getFirst().toLowerCase()); if (subCommand != null) { subCommand.execute(sender, arguments.subList(1, arguments.size())); - return true; + return; } plugin.sms(sender, Messages.WRONG_USAGE); - return true; } @Override - public List onTabComplete( - final @NotNull CommandSender sender, - final @NotNull Command command, - final @NotNull String label, + public @NotNull Collection suggest( + final @NotNull CommandSourceStack stack, final @NotNull String @NonNull [] args ) { + final CommandSender sender = stack.getSender(); final List arguments = Arrays.asList(args); return subCommands.values() diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 98eca40f..6524f4fc 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -1,5 +1,11 @@ name: DeluxeMenus main: com.extendedclip.deluxemenus.DeluxeMenus -version: ${version} -api-version: '1.13' +version: '${version}' +api-version: '1.21' folia-supported: true +dependencies: + server: + Vault: + load: BEFORE + required: false + join-classpath: true From 52d82a7cc24c4b7c78cca2e5d7a11c5ebe96d9c6 Mon Sep 17 00:00:00 2001 From: MrVaylis Date: Sun, 3 May 2026 18:36:21 +0300 Subject: [PATCH 20/24] Fix Paper plugin dependency classpath --- src/main/resources/paper-plugin.yml | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 6524f4fc..6a38f0e2 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -5,7 +5,51 @@ api-version: '1.21' folia-supported: true dependencies: server: + PlaceholderAPI: + load: BEFORE + required: true + join-classpath: true Vault: load: BEFORE required: false join-classpath: true + HeadDatabase: + load: BEFORE + required: false + join-classpath: true + CraftEngine: + load: BEFORE + required: false + join-classpath: true + ItemsAdder: + load: BEFORE + required: false + join-classpath: true + Nexo: + load: BEFORE + required: false + join-classpath: true + Oraxen: + load: BEFORE + required: false + join-classpath: true + MMOItems: + load: BEFORE + required: false + join-classpath: true + ExecutableItems: + load: BEFORE + required: false + join-classpath: true + ExecutableBlocks: + load: BEFORE + required: false + join-classpath: true + Score: + load: BEFORE + required: false + join-classpath: true + SimpleItemGenerator: + load: BEFORE + required: false + join-classpath: true From b391bebb103f5c5a00b7f418453a724ecb2b9323 Mon Sep 17 00:00:00 2001 From: RootBeer Date: Wed, 27 May 2026 16:04:18 -0400 Subject: [PATCH 21/24] Rebase --- 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 d4de3b7a..38d3ef73 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ paper = "1.21.11-R0.1-SNAPSHOT" vault = "1.7.1" authlib = "1.5.25" headdb = "1.3.2" -craftengine = "0.0.67" +craftengine = "26.5" itemsadder = "4.0.10" nexo = "1.10.0" oraxen = "1.212.0" From 2b317c9f8b73c52b17f522dc69ca04f445583522 Mon Sep 17 00:00:00 2001 From: RootBeer Date: Sun, 26 Jul 2026 11:00:00 -0400 Subject: [PATCH 22/24] Bump relevant dependencies --- README.md | 1 - build.gradle.kts | 4 +-- gradle/libs.versions.toml | 4 +-- gradle/wrapper/gradle-wrapper.jar | Bin 46175 -> 48462 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++- gradlew | 6 ++-- gradlew.bat | 35 ++++++++--------------- settings.gradle.kts | 2 +- 8 files changed, 23 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index ac63f750..501b014f 100644 --- a/README.md +++ b/README.md @@ -39,4 +39,3 @@ If you would like to contribute towards DeluxeMenus should you take a look at ou - [CI Server][ci] - [Spigot Page][spigot] - [Plugin Statistics][bstats] - diff --git a/build.gradle.kts b/build.gradle.kts index 3da3e656..5cf53320 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { java - id("com.gradleup.shadow") version("9.4.1") + id("com.gradleup.shadow") version("9.6.1") id("com.github.ben-manes.versions") version("0.54.0") } @@ -49,7 +49,7 @@ dependencies { implementation(libs.adventure.minimessage) implementation(libs.bstats) - compileOnly("org.jetbrains:annotations:23.0.0") + compileOnly("org.jetbrains:annotations:26.1.0") } tasks { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 38d3ef73..5d782e6b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,13 +4,13 @@ paper = "1.21.11-R0.1-SNAPSHOT" vault = "1.7.1" authlib = "1.5.25" headdb = "1.3.2" -craftengine = "26.5" +craftengine = "26.7.4" itemsadder = "4.0.10" nexo = "1.10.0" oraxen = "1.212.0" mythiclib = "1.7.1-SNAPSHOT" mmoitems = "6.10.1-SNAPSHOT" -papi = "2.12.2" +papi = "2.12.3" score = "5.25.3.9" sig = "1.5.0" bstats = "3.2.1" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 61285a659d17295f1de7c53e24fdf13ad755c379..b1b8ef56b44f16b14dc800fa8103a6d89abb526f 100644 GIT binary patch delta 39773 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%0D6K65^m-0x6bZ9rDjF>In@OH( z-}n(gbEW`7N;Uw_nvT;^Dka<4HW`~@d4Ar1vP6&Q8=(0P)eG+4q=iBy>>s6#LLaiI z8Ius$PjnUlOR@A0RT&$X@hAoJ70tH@R`t4bsi-gdvtDkF(|IT~X#VhfF}CW+LQ7FW zSCNg@0d5;A>uzYV4~NGgNQxY^mrkR5QK&vnGw3CnNw3furU7!AZlW^NZ8XbtJC&P% zUZvTlH)xLOZJKMIO)m3%nrB`_^Ubxiz~?+yp|iN&T=EwTkfQDE%!hMSq#BO znPfZowh7rr_-LTI6JC5QuwEpv41d*sTb<#r!bL{Oe)x&GLyY# zLIBZRGLO=gqKSY}SBMZm*Ua_TQ9^uh;QMA2wwOAK9o3H6%iT8%>4Q0Pe|TBUf%$0VOHR=*E~+%SzZrDd+t#Ea7=v2I9{w8WcjX}z#b;jQh&*4=4IZK>gAkr&I%Q-uf7#`b@v z445^+&wQ(+H4C;az4Zb~B9#ysl|-y|$yh#%^l+I5GKSgjYy2pU*>B>c zTd{C8PsNu@i6PRUvsF;PGHb-8HBi`z0ayr&YC?+eT?{WbUxFJB6jmX3I0_AI-2=7(f^3?W&}|!N zOYhPXc4q+M`adSf=X)-NHXLhyh%EbDTX3O48 zY-q^Lv~;AhxmYr3kdvwU>e!xGOEQ+))v~$wYBQcQ$j#(Vrg!Z!GfXFUR+!(Z2UjzB z`qFz-$#krbskRd1I(rzbGlpWhfwkGJIoO*N!HX*KUtG?ENej$<@nk-m*rPXpvo;<# zv)Qg#DyCICuUtYRl`}J`ShA%bj4jD@d^fDrvVPj>5bi!VkxJy&FkMl1f=!-qTW`FF z`b(J{b@i9}shZ~a$e*4GQ>Z9@Y41Ce8Aai{|^uLtgo|z)wD^l5eN@$xk!n z+o??R;3$dy;dO~@E|ciZi+^r^NvCs6o9a6C*(cI1vdrqv2~X(BiIc6aue3kgd6w9; zUA!9B^q0#rXc6pd?!%f{z5mPnw1k%WXfd5W$>pqt460)a=w9fTY-vv?lkh}nnl-3_ z$!iV{<%T;UjcHC@edh$H^sV+&n3{alNX=kPqDNG~y0h!*FQ2?cKb=W`%S4OiOtYqp z8FV&N&4lQ+nM_QtX;7<=R?5LuC9>-h8EBTy4EyOEYeNxiFxH0~5UA^%dY4wq#?Phm zWIh~i=48bUx`4?@WY^2?{M1HEt7t8?GhKC|HhHM8?94)EEX|$~>Pws1P%51Z#nY)= zERhPuQo~@gbV``n=Fs|oeM4xNeW6@B)SpQ8h0J}icrG~{T2|s4ZuZc6oKJr`GZ=$O zɵ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 Hv&Q@%MAWx6 delta 37584 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 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 +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,29 +65,18 @@ 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 +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/settings.gradle.kts b/settings.gradle.kts index a91e4ac4..b9a1575b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,3 @@ rootProject.name = "DeluxeMenus" -enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") \ No newline at end of file +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") From 66e87620c25364b7a292ec7ea41004c52edcd6e8 Mon Sep 17 00:00:00 2001 From: RootBeer Date: Sun, 26 Jul 2026 11:20:22 -0400 Subject: [PATCH 23/24] Resolve reported issues by upstream --- .../deluxemenus/config/DeluxeMenusConfig.java | 4 +- .../deluxemenus/listener/PlayerListener.java | 2 +- .../extendedclip/deluxemenus/menu/Menu.java | 20 ++++-- .../deluxemenus/menu/MenuHolder.java | 66 +++++++++++-------- .../scheduler/folia/FoliaScheduledTask.java | 14 +++- 5 files changed, 65 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java index d6f1894b..4ad8eb23 100644 --- a/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java +++ b/src/main/java/com/extendedclip/deluxemenus/config/DeluxeMenusConfig.java @@ -1231,11 +1231,11 @@ private ClickHandler getClickHandler(FileConfiguration c, String configPath) { 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)); + plugin.getScheduler().runTaskLater(holder.getViewer(), actionTask, action.getDelay(holder)); continue; } - actionTask.runTask(plugin); + plugin.getScheduler().runTask(holder.getViewer(), actionTask); } }; } diff --git a/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java b/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java index ac8ed2a7..830b1471 100644 --- a/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java +++ b/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java @@ -81,7 +81,7 @@ public void onOpen(InventoryOpenEvent event) { } if (Menu.isInMenu(player)) { - Menu.closeMenu(plugin, player, true); + Menu.closeMenu(plugin, player, false); } } diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java index 3fab1180..cbe3d37d 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java @@ -213,14 +213,22 @@ public static void closeMenu(final @NotNull DeluxeMenus plugin, final @NotNull P holder.getMenu().map(Menu::options).map(MenuOptions::closeHandler).flatMap(h -> h).ifPresent(h -> h.onClick(holder)); } + menuHolders.remove(holder); + lastOpenedMenus.put(player.getUniqueId(), holder.getMenu().orElse(null)); + if (close) { - plugin.getScheduler().runTask(player, () -> { + final TaskScheduler scheduler = plugin.getScheduler(); + final Runnable closeInventory = () -> { player.closeInventory(); cleanInventory(plugin, player); - }); + }; + + if (scheduler.isEntityThread(player)) { + closeInventory.run(); + } else { + scheduler.runTask(player, closeInventory); + } } - menuHolders.remove(holder); - lastOpenedMenus.put(player.getUniqueId(), holder.getMenu().orElse(null)); if (runCloseCommmands) { holder.getMenu().map(Menu::options).map(MenuOptions::guiCloseCommands).ifPresent(commands -> executeCommands(plugin, player, commands, holder)); @@ -467,9 +475,9 @@ private static void executeCommands(final @NotNull DeluxeMenus plugin, final @No ); if (action.hasDelay()) { - actionTask.runTaskLater(plugin, action.getDelay(holder)); + plugin.getScheduler().runTaskLater(viewer, actionTask, action.getDelay(holder)); } else { - actionTask.runTask(plugin); + plugin.getScheduler().runTask(viewer, actionTask); } } } diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java index 82c9be24..5cf80368 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java @@ -29,7 +29,9 @@ public class MenuHolder implements InventoryHolder { private MyScheduledTask updateTask = null; private MyScheduledTask refreshTask = null; private Inventory inventory; - private boolean updating; + + private volatile boolean updating; + private boolean parsePlaceholdersInArguments; private boolean parsePlaceholdersAfterArguments; private Map typedArgs; @@ -135,8 +137,6 @@ public void refreshMenu() { return; } - setUpdating(true); - scheduler.runTaskAsynchronously(() -> { final Set active = new HashSet<>(); @@ -178,45 +178,53 @@ public void refreshMenu() { } scheduler.runTask(viewer, () -> { + // Only held across the inventory mutation below. Holding it across the rebuild above + // instead means every click landing during a refresh cycle is swallowed by the + // isUpdating gate in PlayerListener, which is what made refreshes need a second click. + setUpdating(true); + + try { + for (int slot : slotsToClear) { + getInventory().setItem(slot, null); + } - for (int slot : slotsToClear) { - getInventory().setItem(slot, null); - } + boolean update = false; - boolean update = false; + for (MenuItem item : active) { - for (MenuItem item : active) { + ItemStack iStack = item.getItemStack(this); - ItemStack iStack = item.getItemStack(this); + if (iStack == null) { + continue; + } - if (iStack == null) { - continue; - } + iStack = plugin.getMenuItemMarker().mark(iStack); - iStack = plugin.getMenuItemMarker().mark(iStack); + int slot = item.options().slot(); - int slot = item.options().slot(); + if (slot >= menu.options().size()) { + continue; + } - if (slot >= menu.options().size()) { - continue; - } + if (item.options().updatePlaceholders()) { + update = true; + } - if (item.options().updatePlaceholders()) { - update = true; + getInventory().setItem(item.options().slot(), iStack); } - getInventory().setItem(item.options().slot(), iStack); - } + setActiveItems(active); - setActiveItems(active); - - if (update && updateTask == null) { - startUpdatePlaceholdersTask(); - } else if (!update && updateTask != null) { - stopPlaceholderUpdate(); + if (update && updateTask == null) { + startUpdatePlaceholdersTask(); + } else if (!update && updateTask != null) { + stopPlaceholderUpdate(); + } + } finally { + // Without this a throw mid-rebuild would leave the gate closed for good, + // making the menu permanently unclickable. + setUpdating(false); } - - setUpdating(false); }); }); } diff --git a/src/main/java/com/extendedclip/deluxemenus/scheduler/folia/FoliaScheduledTask.java b/src/main/java/com/extendedclip/deluxemenus/scheduler/folia/FoliaScheduledTask.java index aaa517af..eb9f199d 100644 --- a/src/main/java/com/extendedclip/deluxemenus/scheduler/folia/FoliaScheduledTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/scheduler/folia/FoliaScheduledTask.java @@ -35,23 +35,31 @@ public FoliaScheduledTask(final ScheduledTask task) { } public void cancel() { + if (this.task == null) { + return; + } + this.task.cancel(); } public boolean isCancelled() { - return this.task.isCancelled(); + return this.task == null || this.task.isCancelled(); } public Plugin getOwningPlugin() { - return this.task.getOwningPlugin(); + return this.task == null ? null : this.task.getOwningPlugin(); } public boolean isCurrentlyRunning() { + if (this.task == null) { + return false; + } + final ScheduledTask.ExecutionState state = this.task.getExecutionState(); return state == ScheduledTask.ExecutionState.RUNNING || state == ScheduledTask.ExecutionState.CANCELLED_RUNNING; } public boolean isRepeatingTask() { - return this.task.isRepeatingTask(); + return this.task != null && this.task.isRepeatingTask(); } } From b4313c72367e4de3bc39ea784272136207f8c4f1 Mon Sep 17 00:00:00 2001 From: RootBeer Date: Sun, 26 Jul 2026 11:21:42 -0400 Subject: [PATCH 24/24] Drop redundant comments --- .../java/com/extendedclip/deluxemenus/menu/MenuHolder.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java index 5cf80368..34afac38 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java @@ -178,9 +178,6 @@ public void refreshMenu() { } scheduler.runTask(viewer, () -> { - // Only held across the inventory mutation below. Holding it across the rebuild above - // instead means every click landing during a refresh cycle is swallowed by the - // isUpdating gate in PlayerListener, which is what made refreshes need a second click. setUpdating(true); try { @@ -221,8 +218,6 @@ public void refreshMenu() { stopPlaceholderUpdate(); } } finally { - // Without this a throw mid-rebuild would leave the gate closed for good, - // making the menu permanently unclickable. setUpdating(false); } });