diff --git a/build.gradle.kts b/build.gradle.kts index 6558bf29..ac9dd579 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,6 +21,7 @@ repositories { maven("https://repo.momirealms.net/releases/") 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 508eeb31..06e791dd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,7 +22,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 a5b4b354..319272da 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/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 d0feb75c..a493cd1a 100644 --- a/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java +++ b/src/main/java/com/extendedclip/deluxemenus/action/ClickActionTask.java @@ -4,6 +4,8 @@ import com.extendedclip.deluxemenus.menu.Menu; 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; @@ -14,8 +16,6 @@ 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; @@ -26,9 +26,10 @@ import java.util.UUID; import java.util.logging.Level; -public class ClickActionTask extends BukkitRunnable { +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; @@ -47,6 +48,7 @@ public ClickActionTask( final boolean parsePlaceholdersAfterArguments ) { this.plugin = plugin; + this.scheduler = plugin.getScheduler(); this.uuid = uuid; this.actionType = actionType; this.exec = exec; @@ -62,417 +64,418 @@ public void run() { return; } - final Optional holder = Menu.getMenuHolder(player); - final Player target = holder.isPresent() && holder.get().getPlaceholderPlayer() != null - ? holder.get().getPlaceholderPlayer() - : player; + 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); + 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: + scheduler.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; + scheduler.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..ba570781 100644 --- a/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java +++ b/src/main/java/com/extendedclip/deluxemenus/hooks/MMOItemsHook.java @@ -2,15 +2,14 @@ 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; 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; @@ -19,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 @@ -47,7 +48,7 @@ public ItemStack getItem(@NotNull final String... arguments) { ItemStack mmoItem = null; try { - mmoItem = Bukkit.getScheduler().callSyncMethod(plugin, () -> { + mmoItem = scheduler.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..f2d7d5f3 100644 --- a/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java +++ b/src/main/java/com/extendedclip/deluxemenus/listener/PlayerListener.java @@ -6,9 +6,9 @@ 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.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -26,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. @@ -33,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) @@ -92,8 +94,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, () -> { + scheduler.runTaskLater(player, () -> { + Menu.closeMenu(plugin, player, false); Menu.cleanInventory(plugin, player); player.updateInventory(); }, 3L); @@ -181,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()); } } diff --git a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java index 615bedf7..2b23ba2c 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/Menu.java @@ -1,20 +1,26 @@ 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; 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.*; +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,11 +31,12 @@ 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 TaskScheduler scheduler; private final MenuOptions options; private final Map> items; // menu path starting from the plugin directory @@ -44,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); - this.command.register(); + scheduler.runTask(() -> this.command.register()); } menus.put(this.options.name(), this); @@ -199,7 +207,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 +282,13 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map { + scheduler.runTaskAsynchronously(() -> { Set activeItems = new HashSet<>(); @@ -383,8 +393,8 @@ public void openMenu(final @NotNull Player viewer, final @Nullable Map { - if(options.refresh()) { + scheduler.runTask(viewer, () -> { + if (options.refresh()) { holder.startRefreshTask(); } @@ -395,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 adc23192..b28dc145 100644 --- a/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java +++ b/src/main/java/com/extendedclip/deluxemenus/menu/MenuHolder.java @@ -2,20 +2,16 @@ 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.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; @@ -24,13 +20,14 @@ public class MenuHolder implements InventoryHolder { private final DeluxeMenus plugin; + private final TaskScheduler scheduler; private final Player viewer; 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; @@ -39,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; @@ -55,7 +54,7 @@ public String getViewerName() { return viewer.getName(); } - public BukkitTask getUpdateTask() { + public MyScheduledTask getUpdateTask() { return updateTask; } @@ -104,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; } @@ -113,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, @@ -138,45 +137,51 @@ public void refreshMenu() { setUpdating(true); - Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> { + scheduler.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); + scheduler.runTask(viewer, () -> Menu.closeMenu(plugin, viewer, true)); + return; } - Bukkit.getScheduler().runTask(plugin, () -> { + scheduler.runTask(viewer, () -> { + + for (int slot : slotsToClear) { + getInventory().setItem(slot, null); + } boolean update = false; @@ -201,7 +206,7 @@ public void refreshMenu() { if (update && updateTask == null) { startUpdatePlaceholdersTask(); - } else if(!update && updateTask != null) { + } else if (!update && updateTask != null) { stopPlaceholderUpdate(); } @@ -221,7 +226,7 @@ public void stopPlaceholderUpdate() { } public void stopRefreshTask() { - if(refreshTask != null) { + if (refreshTask != null) { try { refreshTask.cancel(); } catch (Exception ignored) { @@ -231,20 +236,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 = scheduler.runTaskTimerAsynchronously( + this::refreshMenu, + initialDelay, + period + ); } public void startUpdatePlaceholdersTask() { @@ -253,69 +259,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 = scheduler.runTaskTimer( + viewer, + () -> { - 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/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() { 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..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; @@ -15,7 +16,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 { @@ -25,28 +25,20 @@ 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(); - new BukkitRunnable() { - @Override - public void run() { - if (check()) { - new BukkitRunnable() { - - @Override - public void run() { - register(); - } - }.runTask(plugin); - } + scheduler.runTaskAsynchronously(() -> { + if (check()) { + scheduler.runTask(this::register); } - - }.runTaskAsynchronously(plugin); + }); } @EventHandler(priority = EventPriority.MONITOR) @@ -74,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(); diff --git a/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java b/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java index 870e8312..08de3186 100644 --- a/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java +++ b/src/main/java/com/extendedclip/deluxemenus/utils/ItemUtils.java @@ -33,7 +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 itemstack The string to check + * @param material 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 c57c869c..ec50f892 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, CraftEngine, ItemsAdder, Nexo, Oraxen, ExecutableItems, ExecutableBlocks, Score, SimpleItemGenerator, MMOItems ] description: All in one inventory menu system