diff --git a/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java b/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java index b895f278a..949e90bae 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java +++ b/API/src/main/java/fr/maxlego08/menu/api/MenuPlugin.java @@ -13,6 +13,7 @@ import fr.maxlego08.menu.api.storage.StorageManager; import fr.maxlego08.menu.api.utils.MetaUpdater; import fr.maxlego08.menu.api.utils.toast.ToastHelper; +import fr.maxlego08.menu.api.utils.version.ClientVersionManager; import org.bukkit.OfflinePlayer; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; @@ -261,6 +262,17 @@ public interface MenuPlugin extends Plugin { DialogManager getDialogManager(); + /** + * Returns the client version manager, used to know which Minecraft version a player's + * client is running. Never null, but may have no provider registered, in which case it + * reports the server version for every player. + * + * @return the client version manager + */ + default ClientVersionManager getClientVersionManager() { + return ClientVersionManager.SERVER_VERSION_ONLY; + } + ItemManager getItemManager(); AttributApplier getAttributApplier(); diff --git a/API/src/main/java/fr/maxlego08/menu/api/button/PerformButton.java b/API/src/main/java/fr/maxlego08/menu/api/button/PerformButton.java index 2fa79a170..51754b50d 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/button/PerformButton.java +++ b/API/src/main/java/fr/maxlego08/menu/api/button/PerformButton.java @@ -180,7 +180,7 @@ private void execute(@NotNull MenuPlugin plugin, @NotNull Player player, @NotNul } catch (Exception exception) { if (Configuration.enableDebug){ Logger.info("An error occurred while executing command: " + command); - exception.printStackTrace(); + Logger.error(exception); } } }); diff --git a/API/src/main/java/fr/maxlego08/menu/api/inventory/dialog/DialogInventory.java b/API/src/main/java/fr/maxlego08/menu/api/inventory/dialog/DialogInventory.java index a941eb92b..6b3ad116d 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/inventory/dialog/DialogInventory.java +++ b/API/src/main/java/fr/maxlego08/menu/api/inventory/dialog/DialogInventory.java @@ -9,6 +9,7 @@ import fr.maxlego08.menu.api.engine.InventoryResult; import fr.maxlego08.menu.api.enums.dialog.DialogType; import fr.maxlego08.menu.api.pattern.Pattern; +import fr.maxlego08.menu.api.utils.DialogFallback; import fr.maxlego08.menu.api.utils.OpenWithItem; import fr.maxlego08.menu.api.utils.PaperMetaUpdater; import fr.maxlego08.menu.api.utils.Placeholders; @@ -18,6 +19,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryType; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Collections; @@ -68,6 +70,24 @@ public interface DialogInventory extends Inventory { ZDialogInventoryBuild getBuild(Player player); + /** + * The inventory opened instead of this dialog when the player's client is below + * Minecraft 1.21.6 and therefore cannot render dialogs. + * + * @return the configured fallback, or null if none was configured + */ + @Nullable + default DialogFallback getFallbackInventory() { + return null; + } + + /** + * @param fallbackInventory the fallback inventory, may be null + * @see #getFallbackInventory() + */ + default void setFallbackInventory(@Nullable DialogFallback fallbackInventory) { + } + /** * @deprecated Use {@link ServerLinksDialogInventory#setExitActionButton(ActionButtonRecord)} */ diff --git a/API/src/main/java/fr/maxlego08/menu/api/loader/NoneLoader.java b/API/src/main/java/fr/maxlego08/menu/api/loader/NoneLoader.java index 2f28b9f23..298e83370 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/loader/NoneLoader.java +++ b/API/src/main/java/fr/maxlego08/menu/api/loader/NoneLoader.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.api.loader; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.api.button.Button; import fr.maxlego08.menu.api.button.DefaultButtonValue; import org.bukkit.configuration.file.YamlConfiguration; @@ -29,7 +31,7 @@ public Button load(@NonNull YamlConfiguration configuration, @NonNull String pat } return this.clazz.getDeclaredConstructor().newInstance(); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } return null; } diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/DialogFallback.java b/API/src/main/java/fr/maxlego08/menu/api/utils/DialogFallback.java new file mode 100644 index 000000000..5f5c92d4a --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/DialogFallback.java @@ -0,0 +1,25 @@ +package fr.maxlego08.menu.api.utils; + +/** + * The inventory opened in place of a dialog when the player's client is too old to + * render dialogs (below Minecraft 1.21.6). + * + * @param inventoryName the name of the inventory file, without its extension + * @param plugin the plugin owning the inventory, defaults to {@code zMenu} + * @param page the page to open, defaults to {@code 1} + */ +public record DialogFallback(String inventoryName, String plugin, int page) { + + public DialogFallback(String inventoryName, String plugin, int page) { + this.inventoryName = inventoryName; + this.plugin = plugin == null || plugin.isEmpty() ? "zMenu" : plugin; + this.page = Math.max(1, page); + } + + /** + * @return true if an inventory name was configured. + */ + public boolean isValid() { + return this.inventoryName != null && !this.inventoryName.isEmpty(); + } +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java b/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java index 66808cdf1..e6c3c7af6 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/Message.java @@ -69,6 +69,7 @@ public enum Message implements IMessage { DIALOG_OPEN_SUCCESS_OTHER("&aYou have just opened the dialog &f%name%&a to the &3%player%&a."), DIALOG_OPEN_ERROR_NOT_FOUND("&cImpossible to find the dialog &f%name%&c."), DIALOG_OPEN_ERROR_CONSOLE("&cOnly one player can open a dialog."), + DIALOG_NOT_SUPPORTED("&cYour Minecraft version does not support this menu, you need at least &f%version%&c."), BEDROCK_OPEN_SUCCESS("&aYou have just opened the bedrock inventory &f%name%&a."), BEDROCK_OPEN_SUCCESS_OTHER("&aYou have just opened the bedrock inventory &f%name%&a to the &3%player%&a."), diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/Placeholders.java b/API/src/main/java/fr/maxlego08/menu/api/utils/Placeholders.java index eb16fabfd..5e933d253 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/utils/Placeholders.java +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/Placeholders.java @@ -116,7 +116,7 @@ public String parse(@NotNull String string, @NotNull String key, @NotNull String } } } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); Logger.info("Error with placeholder key " + key + " !", Logger.LogType.ERROR); } return string; diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/resolvable/Resolvable.java b/API/src/main/java/fr/maxlego08/menu/api/utils/resolvable/Resolvable.java index bdb2dc183..10b09cd06 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/utils/resolvable/Resolvable.java +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/resolvable/Resolvable.java @@ -1,5 +1,6 @@ package fr.maxlego08.menu.api.utils.resolvable; +import com.google.common.base.Preconditions; import fr.maxlego08.menu.api.context.BuildContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -78,6 +79,20 @@ static void applyResolvable( return resolvable.resolve(context); } + static @NotNull X resolveOrDefault( + @NotNull BuildContext context, + @Nullable Resolvable resolvable, + @NotNull X defaultValue + ) { + Preconditions.checkNotNull(defaultValue, "Default value cannot be null"); + if (resolvable == null) { + return defaultValue; + } + + X value = resolvable.resolve(context); + return value != null ? value : defaultValue; + } + static @NotNull List resolveList( @NotNull BuildContext context, @Nullable List> resolvables diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/version/ClientVersionManager.java b/API/src/main/java/fr/maxlego08/menu/api/utils/version/ClientVersionManager.java new file mode 100644 index 000000000..31c220862 --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/version/ClientVersionManager.java @@ -0,0 +1,79 @@ +package fr.maxlego08.menu.api.utils.version; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.List; + +/** + * Resolves the Minecraft version of a player's client through the registered + * {@link ClientVersionProvider}s, which are queried in registration order. + *

+ * Providers are ordered from most to least trustworthy: protocol translation plugins first, + * then Paper's own handshake protocol number. When no provider is able to answer, the + * manager falls back to the server version, so a server with no usable source of client + * versions behaves as if every client matched it. + */ +public interface ClientVersionManager { + + /** + * The first Minecraft version able to render the Paper Dialog API. + */ + MinecraftVersion DIALOG_MINIMUM_VERSION = MinecraftVersion.parse("1.21.6"); + + /** + * Registers a new provider. Providers are queried in registration order. + * + * @param provider the provider to register + */ + void registerProvider(@NotNull ClientVersionProvider provider); + + /** + * @return the registered providers, in registration order. + */ + @NotNull + Collection getProviders(); + + /** + * Returns the version of the player's client, or the server version when no provider + * could determine it. + * + * @param player the player to inspect + * @return the client version, never null + */ + @NotNull + MinecraftVersion getClientVersion(@NotNull Player player); + + /** + * @param player the player to inspect + * @return true if the player's client can display dialogs (Minecraft 1.21.6 or above). + */ + default boolean supportsDialogs(@NotNull Player player) { + return this.getClientVersion(player).isAtLeast(DIALOG_MINIMUM_VERSION); + } + + /** + * A manager with no provider, reporting the server version for every player. Used as the + * default for {@link fr.maxlego08.menu.api.MenuPlugin#getClientVersionManager()} so an + * implementation predating this interface keeps working. + */ + ClientVersionManager SERVER_VERSION_ONLY = new ClientVersionManager() { + + @Override + public void registerProvider(@NotNull ClientVersionProvider provider) { + } + + @Override + @NotNull + public Collection getProviders() { + return List.of(); + } + + @Override + @NotNull + public MinecraftVersion getClientVersion(@NotNull Player player) { + return MinecraftVersion.getCurrentVersion(); + } + }; +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/version/ClientVersionProvider.java b/API/src/main/java/fr/maxlego08/menu/api/utils/version/ClientVersionProvider.java new file mode 100644 index 000000000..6e6bb90db --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/version/ClientVersionProvider.java @@ -0,0 +1,36 @@ +package fr.maxlego08.menu.api.utils.version; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +/** + * Provides the Minecraft version of a player's client, which can differ from the + * server version when a protocol translation plugin such as ViaVersion is installed. + *

+ * Implementations live in the {@code Hooks/ClientVersion} module and are registered on + * {@link ClientVersionManager} when their backing plugin is present. + */ +public interface ClientVersionProvider { + + /** + * @return the name of the plugin backing this provider, used for logging. + */ + @NotNull + String getName(); + + /** + * @return true if the backing plugin is present and its API can be used. + */ + boolean isAvailable(); + + /** + * Returns the version of the player's client. + * + * @param player the player to inspect + * @return the client version, or {@link Optional#empty()} if it could not be determined + */ + @NotNull + Optional getClientVersion(@NotNull Player player); +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/version/MinecraftVersion.java b/API/src/main/java/fr/maxlego08/menu/api/utils/version/MinecraftVersion.java index fb3c199c6..d22382ca6 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/utils/version/MinecraftVersion.java +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/version/MinecraftVersion.java @@ -47,12 +47,14 @@ public static MinecraftVersion parse(@Nullable String rawVersion) { for (String part : parts) { if (index >= 3) break; if (!NUMERIC.matcher(part).matches()) { - continue; + break; } try { - numbers[index++] = Integer.parseInt(part); + numbers[index] = Integer.parseInt(part); + index++; } catch (NumberFormatException e) { Logger.info("Could not parse numeric segment '" + part + "' in Minecraft value '" + raw + "'. (" + e.getMessage() + ")", Logger.LogType.WARNING); + break; } } diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/version/ProtocolVersions.java b/API/src/main/java/fr/maxlego08/menu/api/utils/version/ProtocolVersions.java new file mode 100644 index 000000000..b58219d71 --- /dev/null +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/version/ProtocolVersions.java @@ -0,0 +1,89 @@ +package fr.maxlego08.menu.api.utils.version; + +import java.util.Map; +import java.util.NavigableMap; +import java.util.Optional; +import java.util.TreeMap; + +/** + * Maps a Minecraft protocol number to the Minecraft version that introduced it. + *

+ * Protocol numbers are the values sent by the client during the handshake, as exposed by + * {@code Player#getProtocolVersion()} on Paper. Versions sharing a protocol are collapsed + * onto the lowest one of the range ({@code 772} is {@code 1.21.7-1.21.8}, so it maps to + * {@code 1.21.7}), which is the conservative choice for a minimum-version check. + */ +public final class ProtocolVersions { + private static final NavigableMap VERSIONS = new TreeMap<>(); + + static { + register(4, "1.7.2"); + register(5, "1.7.6"); + register(47, "1.8"); + register(107, "1.9"); + register(108, "1.9.1"); + register(109, "1.9.2"); + register(110, "1.9.3"); + register(210, "1.10"); + register(315, "1.11"); + register(316, "1.11.1"); + register(335, "1.12"); + register(338, "1.12.1"); + register(340, "1.12.2"); + register(393, "1.13"); + register(401, "1.13.1"); + register(404, "1.13.2"); + register(477, "1.14"); + register(480, "1.14.1"); + register(485, "1.14.2"); + register(490, "1.14.3"); + register(498, "1.14.4"); + register(573, "1.15"); + register(575, "1.15.1"); + register(578, "1.15.2"); + register(735, "1.16"); + register(736, "1.16.1"); + register(751, "1.16.2"); + register(753, "1.16.3"); + register(754, "1.16.4"); + register(755, "1.17"); + register(756, "1.17.1"); + register(757, "1.18"); + register(758, "1.18.2"); + register(759, "1.19"); + register(760, "1.19.1"); + register(761, "1.19.3"); + register(762, "1.19.4"); + register(763, "1.20"); + register(764, "1.20.2"); + register(765, "1.20.3"); + register(766, "1.20.5"); + register(767, "1.21"); + register(768, "1.21.2"); + register(769, "1.21.4"); + register(770, "1.21.5"); + register(771, "1.21.6"); + register(772, "1.21.7"); + register(773, "1.21.9"); + register(774, "1.21.11"); + register(775, "26.1"); + register(776, "26.2"); + } + + private ProtocolVersions() { + } + + private static void register(int protocol, String version) { + VERSIONS.put(protocol, MinecraftVersion.parse(version)); + } + + public static Optional fromProtocol(int protocol) { + if (protocol < 0) return Optional.empty(); + + Map.Entry entry = VERSIONS.floorEntry(protocol); + if (entry == null) { + return Optional.of(VERSIONS.firstEntry().getValue()); + } + return Optional.of(entry.getValue()); + } +} diff --git a/API/src/main/java/fr/maxlego08/menu/api/utils/version/VersionFilter.java b/API/src/main/java/fr/maxlego08/menu/api/utils/version/VersionFilter.java index 816450e48..d328bb3ff 100644 --- a/API/src/main/java/fr/maxlego08/menu/api/utils/version/VersionFilter.java +++ b/API/src/main/java/fr/maxlego08/menu/api/utils/version/VersionFilter.java @@ -86,7 +86,7 @@ public static int scanAndRegister( } catch (Exception e) { if (Configuration.enableDebug) { Logger.error("Failed to load class " + clazz.getName() + " for plugin " + plugin.getName() + " with annotation " + annotation.getSimpleName() + " due to: " + e.getMessage() + ". Please check reporte this error to the plugin developer (" + plugin.getDescription().getAuthors() + ")"); - e.printStackTrace(); + Logger.error(e); } } } diff --git a/API/src/main/java/fr/maxlego08/menu/zcore/logger/Logger.java b/API/src/main/java/fr/maxlego08/menu/zcore/logger/Logger.java index 8173e17b7..2dc9cb46b 100644 --- a/API/src/main/java/fr/maxlego08/menu/zcore/logger/Logger.java +++ b/API/src/main/java/fr/maxlego08/menu/zcore/logger/Logger.java @@ -37,6 +37,48 @@ public static void error(String message) { getLogger().log(message, LogType.ERROR); } + /** + * Logs a throwable and its stack trace through the plugin logger, so the failure carries + * the plugin prefix and honours the configured formatting instead of going straight to + * {@code System.err} the way {@link Throwable#printStackTrace()} does. + * + * @param message context describing what was being attempted + * @param throwable the failure, may be null + */ + public static void error(String message, Throwable throwable) { + Logger current = getLogger(); + if (current == null) { + System.err.println(message); + if (throwable != null) throwable.printStackTrace(); + return; + } + + current.log(message, LogType.ERROR); + if (throwable == null) return; + + current.log(throwable + "", LogType.ERROR); + for (StackTraceElement element : throwable.getStackTrace()) { + current.log(" at " + element, LogType.ERROR); + } + + Throwable cause = throwable.getCause(); + while (cause != null) { + current.log("Caused by: " + cause, LogType.ERROR); + for (StackTraceElement element : cause.getStackTrace()) { + current.log(" at " + element, LogType.ERROR); + } + cause = cause.getCause(); + } + } + + /** + * @param throwable the failure to log, may be null + * @see #error(String, Throwable) + */ + public static void error(Throwable throwable) { + error("An error occurred: " + (throwable == null ? "unknown" : throwable.getMessage()), throwable); + } + public void log(String message) { this.log(message, LogType.INFO); } diff --git a/Common/src/main/java/fr/maxlego08/menu/ZMenuItemStack.java b/Common/src/main/java/fr/maxlego08/menu/ZMenuItemStack.java index 41dab82d2..16a552913 100644 --- a/Common/src/main/java/fr/maxlego08/menu/ZMenuItemStack.java +++ b/Common/src/main/java/fr/maxlego08/menu/ZMenuItemStack.java @@ -169,7 +169,7 @@ public ItemStack build(BuildContext context) { } catch (Exception e) { if (Configuration.enableDebug) { Logger.info("Error while applying ItemComponent '" + metadata.getParentLoader().getComponentName() + "' for item " + this.path + " in file " + this.filePath + " (" + player + ")", Logger.LogType.ERROR); - e.printStackTrace(); + Logger.error(e); } } } @@ -403,7 +403,7 @@ private void applyDisplayNameLore(Player player, Placeholders placeholders, Item itemName = fontImage.replace(this.papi(placeholders.parse(displayName), offlinePlayer == null ? player : offlinePlayer, useCache)); } catch (Exception exception) { Logger.info("Error with update display name for item " + this.path + " in file " + this.filePath + " (" + player + ", " + this.displayName + ")", Logger.LogType.ERROR); - exception.printStackTrace(); + Logger.error(exception); } } @@ -1015,7 +1015,7 @@ public void setTypeMapAccessor(MapConfiguration configuration) { enchantments.put(enchantment, level); } catch (ItemEnchantException e) { - e.printStackTrace(); + Logger.error(e); } } diff --git a/Common/src/main/java/fr/maxlego08/menu/common/utils/MessageUtils.java b/Common/src/main/java/fr/maxlego08/menu/common/utils/MessageUtils.java index aa76b79e8..f69da01b5 100644 --- a/Common/src/main/java/fr/maxlego08/menu/common/utils/MessageUtils.java +++ b/Common/src/main/java/fr/maxlego08/menu/common/utils/MessageUtils.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.common.utils; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.api.MenuPlugin; import fr.maxlego08.menu.api.utils.IMessage; import fr.maxlego08.menu.api.utils.Message; @@ -160,7 +162,7 @@ protected final Class getNMSClass(String name) { try { return Class.forName("net.minecraft.server." + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + "." + name); } catch (ClassNotFoundException e) { - e.printStackTrace(); + Logger.error(e); } return null; } @@ -184,7 +186,7 @@ protected void title(Player player, String title, String subtitle, int fadeInTim this.sendPacket(player, packet); this.sendPacket(player, timingPacket); } catch (Exception e) { - e.printStackTrace(); + Logger.error(e); } } @@ -194,7 +196,7 @@ protected final void sendPacket(Player player, Object packet) { Object playerConnection = handle.getClass().getField("playerConnection").get(handle); playerConnection.getClass().getMethod("sendPacket", this.getNMSClass("Packet")).invoke(playerConnection, packet); } catch (Exception e) { - e.printStackTrace(); + Logger.error(e); } } diff --git a/Common/src/main/java/fr/maxlego08/menu/common/utils/PlayerUtil.java b/Common/src/main/java/fr/maxlego08/menu/common/utils/PlayerUtil.java index 44c141df6..e6f406553 100644 --- a/Common/src/main/java/fr/maxlego08/menu/common/utils/PlayerUtil.java +++ b/Common/src/main/java/fr/maxlego08/menu/common/utils/PlayerUtil.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.common.utils; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.api.utils.SimpleCache; import org.bukkit.Bukkit; import org.bukkit.profile.PlayerProfile; @@ -33,7 +35,7 @@ public static PlayerProfile getProfileFromUrl(@NotNull String url) { try { urlObject = SkinUrlDecoder.extractSkinUrl(url).toURL(); // The URL to the skin, for example: https://textures.minecraft.net/texture/18813764b2abc94ec3c3bc67b9147c21be850cdf996679703157f4555997ea63a } catch (URISyntaxException | MalformedURLException exception) { - exception.printStackTrace(); + Logger.error(exception); return null; } textures.setSkin(urlObject); // Set the skin of the player profile to the URL diff --git a/Common/src/main/java/fr/maxlego08/menu/common/utils/ZUtils.java b/Common/src/main/java/fr/maxlego08/menu/common/utils/ZUtils.java index aa5f23d44..39204020e 100644 --- a/Common/src/main/java/fr/maxlego08/menu/common/utils/ZUtils.java +++ b/Common/src/main/java/fr/maxlego08/menu/common/utils/ZUtils.java @@ -477,7 +477,7 @@ protected void unRegisterBukkitCommand(Plugin plugin, PluginCommand command) { knownCommands.remove(plugin.getDescription().getName() + ":" + alias); } } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } } diff --git a/Common/src/main/java/fr/maxlego08/menu/common/utils/nms/Base64ItemStack.java b/Common/src/main/java/fr/maxlego08/menu/common/utils/nms/Base64ItemStack.java index c03cc1bb1..aa7ba4483 100644 --- a/Common/src/main/java/fr/maxlego08/menu/common/utils/nms/Base64ItemStack.java +++ b/Common/src/main/java/fr/maxlego08/menu/common/utils/nms/Base64ItemStack.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.common.utils.nms; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.common.utils.Base64; import org.bukkit.inventory.ItemStack; @@ -25,7 +27,7 @@ public static String encode(ItemStack item) { objectOutputStream.close(); return Base64.encode(byteArrayOutputStream.toByteArray()); } catch (IOException exception) { - exception.printStackTrace(); + Logger.error(exception); return null; } } @@ -39,7 +41,7 @@ public static ItemStack decode(String data) { objectInputStream.close(); return item; } catch (IOException | ClassNotFoundException exception) { - exception.printStackTrace(); + Logger.error(exception); return null; } } diff --git a/Common/src/main/java/fr/maxlego08/menu/common/utils/nms/ItemStackCompound.java b/Common/src/main/java/fr/maxlego08/menu/common/utils/nms/ItemStackCompound.java index 9c9fbabb1..290ab06ba 100644 --- a/Common/src/main/java/fr/maxlego08/menu/common/utils/nms/ItemStackCompound.java +++ b/Common/src/main/java/fr/maxlego08/menu/common/utils/nms/ItemStackCompound.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.common.utils.nms; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.nms.NMSHandler; import fr.maxlego08.menu.api.utils.version.MinecraftVersion; import fr.maxlego08.menu.common.utils.nms.ItemStackUtils.EnumReflectionItemStack; @@ -87,7 +89,7 @@ public ItemStack setString(ItemStack itemStack, String key, String value) { compoundObject.getClass().getMethod(this.reflection.getMethodSetString(), String.class, String.class).invoke(compoundObject, key, value); return this.applyCompound(itemStack, compoundObject); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } return null; @@ -108,7 +110,7 @@ public String getString(ItemStack itemStack, String key) { if (compoundObject == null) return null; return (String) compoundObject.getClass().getMethod(this.reflection.getMethodGetString(), String.class).invoke(compoundObject, new Object[]{key}); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } return null; @@ -130,7 +132,7 @@ public double getDouble(ItemStack itemStack, String key) { if (compoundObject == null) return 0; return (double) compoundObject.getClass().getMethod(this.reflection.getMethodGetDouble(), String.class).invoke(compoundObject, new Object[]{key}); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } return 0; @@ -151,7 +153,7 @@ public long getLong(ItemStack itemStack, String key) { if (compoundObject == null) return 0; return (long) compoundObject.getClass().getMethod(this.reflection.getMethodGetLong(), String.class).invoke(compoundObject, new Object[]{key}); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } return 0; } @@ -171,7 +173,7 @@ public int getInt(ItemStack itemStack, String key) { if (compoundObject == null) return 0; return (int) compoundObject.getClass().getMethod(this.reflection.getMethodGetInt(), String.class).invoke(compoundObject, new Object[]{key}); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } return 0; @@ -191,7 +193,7 @@ public float getFloat(ItemStack itemStack, String key) { Object compoundObject = this.getCompound(itemStack); return (float) compoundObject.getClass().getMethod(this.reflection.getMethodGetFloat(), String.class).invoke(compoundObject, new Object[]{key}); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } return 0; @@ -211,7 +213,7 @@ public boolean getBoolean(ItemStack itemStack, String key) { Object compoundObject = this.getCompound(itemStack); return (boolean) compoundObject.getClass().getMethod(this.reflection.getMethodGetBoolean(), String.class).invoke(compoundObject, new Object[]{key}); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } return false; @@ -233,7 +235,7 @@ public ItemStack setInt(ItemStack itemStack, String key, int value) { compoundObject.getClass().getMethod(this.reflection.getMethodSetInt(), String.class, int.class).invoke(compoundObject, key, value); return this.applyCompound(itemStack, compoundObject); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } return null; @@ -255,7 +257,7 @@ public ItemStack setLong(ItemStack itemStack, String key, long value) { compoundObject.getClass().getMethod(this.reflection.getMethodSetLong(), String.class, long.class).invoke(compoundObject, key, value); return this.applyCompound(itemStack, compoundObject); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } return null; @@ -277,7 +279,7 @@ public ItemStack setFloat(ItemStack itemStack, String key, float value) { compoundObject.getClass().getMethod(this.reflection.getMethodSetFloat(), String.class, float.class).invoke(compoundObject, key, value); return this.applyCompound(itemStack, compoundObject); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } return null; @@ -300,7 +302,7 @@ public ItemStack setBoolean(ItemStack itemStack, String key, boolean value) { compoundObject.getClass().getMethod(this.reflection.getMethodSetBoolean(), String.class, boolean.class).invoke(compoundObject, key, value); return this.applyCompound(itemStack, compoundObject); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } return null; @@ -322,7 +324,7 @@ public ItemStack setDouble(ItemStack itemStack, String key, double value) { compoundObject.getClass().getMethod(this.reflection.getMethodSetDouble(), String.class, double.class).invoke(compoundObject, key, value); return this.applyCompound(itemStack, compoundObject); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } return null; diff --git a/Common/src/main/java/fr/maxlego08/menu/common/utils/nms/ItemStackUtils.java b/Common/src/main/java/fr/maxlego08/menu/common/utils/nms/ItemStackUtils.java index ad48e2956..5be32e82e 100644 --- a/Common/src/main/java/fr/maxlego08/menu/common/utils/nms/ItemStackUtils.java +++ b/Common/src/main/java/fr/maxlego08/menu/common/utils/nms/ItemStackUtils.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.common.utils.nms; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.api.configuration.Configuration; import fr.maxlego08.menu.api.utils.version.MinecraftVersion; import fr.maxlego08.menu.common.utils.Base64; @@ -55,7 +57,7 @@ public static String serializeItemStack(ItemStack paramItemStack) { .invoke(null, localObject1, localByteArrayOutputStream); } catch (Exception localException) { if (Configuration.enableDebug) { - localException.printStackTrace(); + Logger.error(localException); } } String string = Base64.encode(localByteArrayOutputStream.toByteArray()); @@ -112,7 +114,7 @@ public static ItemStack deserializeItemStack(String paramString) { localItemStack = (ItemStack) EnumReflectionItemStack.CRAFTITEMSTACK.getClassz() .getMethod("asBukkitCopy", new Class[]{localClass2}).invoke(null, new Object[]{localObject2}); } catch (Exception localException) { - // localException.printStackTrace(); + // Logger.error(localException); } if (localItemStack != null && !itemStackSerialized.containsKey(localItemStack)) itemStackSerialized.put(localItemStack, paramString); @@ -187,7 +189,7 @@ public Class getClassz() { try { localClass = Class.forName(var3); } catch (ClassNotFoundException localClassNotFoundException) { - localClassNotFoundException.printStackTrace(); + Logger.error(localClassNotFoundException); } return localClass; } diff --git a/Common/src/main/java/fr/maxlego08/menu/common/utils/yaml/YamlParser.java b/Common/src/main/java/fr/maxlego08/menu/common/utils/yaml/YamlParser.java index 4459410f6..f5f20117f 100644 --- a/Common/src/main/java/fr/maxlego08/menu/common/utils/yaml/YamlParser.java +++ b/Common/src/main/java/fr/maxlego08/menu/common/utils/yaml/YamlParser.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.common.utils.yaml; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.api.configuration.Configuration; import fr.maxlego08.menu.api.utils.Placeholders; import fr.maxlego08.menu.common.utils.cache.YamlFileCache; @@ -34,7 +36,7 @@ public static YamlConfiguration parseConfiguration(@Nullable YamlConfiguration c return parsedConfig; } catch (Exception e) { if (Configuration.enableDebug) - e.printStackTrace(); + Logger.error(e); return configuration; } } @@ -56,7 +58,7 @@ public static YamlConfiguration loadAndParseFile(@NotNull File file, @NotNull Ma return parseConfiguration(yamlConfiguration.get(), placeholders); } catch (Exception exception) { if (Configuration.enableDebug) - exception.printStackTrace(); + Logger.error(exception); return new YamlConfiguration(); } } diff --git a/Common/src/main/java/fr/maxlego08/menu/inventory/VInventory.java b/Common/src/main/java/fr/maxlego08/menu/inventory/VInventory.java index 174902cbd..9be5f56dd 100644 --- a/Common/src/main/java/fr/maxlego08/menu/inventory/VInventory.java +++ b/Common/src/main/java/fr/maxlego08/menu/inventory/VInventory.java @@ -252,7 +252,7 @@ protected VInventory clone() { try { return this.getClass().newInstance(); } catch (InstantiationException | IllegalAccessException e) { - e.printStackTrace(); + Logger.error(e); } return null; } diff --git a/Hooks/HeadDataBase/src/main/java/fr/maxlego08/menu/hooks/headdatabase/HeadDatabaseLoader.java b/Hooks/HeadDataBase/src/main/java/fr/maxlego08/menu/hooks/headdatabase/HeadDatabaseLoader.java index 722effd2d..c1fe1f4ca 100644 --- a/Hooks/HeadDataBase/src/main/java/fr/maxlego08/menu/hooks/headdatabase/HeadDatabaseLoader.java +++ b/Hooks/HeadDataBase/src/main/java/fr/maxlego08/menu/hooks/headdatabase/HeadDatabaseLoader.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.hooks.headdatabase; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.api.annotations.AutoMaterialLoader; import fr.maxlego08.menu.api.annotations.RequiresPlugin; import fr.maxlego08.menu.api.loader.MaterialLoader; @@ -26,7 +28,7 @@ public ItemStack load(@NonNull Player player, @NonNull YamlConfiguration configu return api.getItemHead(materialString); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } return null; diff --git a/Hooks/Paper/src/main/java/fr/maxlego08/menu/config/ConfigManager.java b/Hooks/Paper/src/main/java/fr/maxlego08/menu/config/ConfigManager.java index ffcc9e3c7..7872be38e 100644 --- a/Hooks/Paper/src/main/java/fr/maxlego08/menu/config/ConfigManager.java +++ b/Hooks/Paper/src/main/java/fr/maxlego08/menu/config/ConfigManager.java @@ -199,7 +199,7 @@ public void openConfig(@NonNull String pluginName, @NonNull Player player) { } catch (Exception e) { if (Configuration.enableDebug) { Logger.info("Failed to open configuration dialog for player: " + player.getName() + " error: " + e.getMessage(), Logger.LogType.ERROR); - e.printStackTrace(); + Logger.error(e); } } } diff --git a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/ComponentMeta.java b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/ComponentMeta.java index ab38f166b..3f2789eea 100644 --- a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/ComponentMeta.java +++ b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/ComponentMeta.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.hooks; +import fr.maxlego08.menu.zcore.logger.Logger; + import com.google.common.base.Preconditions; import fr.maxlego08.menu.api.MenuPlugin; import fr.maxlego08.menu.api.utils.LoreType; @@ -107,7 +109,7 @@ private void updateDisplayName(ItemMeta itemMeta, String text) { try { this.nameMethod.invoke(itemMeta, component); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } } @@ -160,14 +162,14 @@ public void updateLore(@NonNull ItemMeta itemMeta, @NonNull List lore, @ } } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } } try { this.setLoreMethod.invoke(itemMeta, components); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } } @@ -180,7 +182,7 @@ private Inventory createInventoryInternal(String inventoryName, InventoryHolder return (Inventory) this.inventoryTypeMethod.invoke(null, inventoryHolder, inventoryType, component); } } catch (IllegalAccessException | InvocationTargetException exception) { - exception.printStackTrace(); + Logger.error(exception); } if (inventoryTypeOrSize instanceof Integer) { return Bukkit.createInventory(inventoryHolder, (int) inventoryTypeOrSize, inventoryName); diff --git a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/ZDialogManager.java b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/ZDialogManager.java index 4acf829b3..6a4f30612 100644 --- a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/ZDialogManager.java +++ b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/ZDialogManager.java @@ -12,8 +12,11 @@ import fr.maxlego08.menu.api.exceptions.InventoryException; import fr.maxlego08.menu.api.inventory.dialog.DialogInventory; import fr.maxlego08.menu.api.requirement.Requirement; +import fr.maxlego08.menu.api.utils.DialogFallback; import fr.maxlego08.menu.api.utils.Loader; +import fr.maxlego08.menu.api.utils.Message; import fr.maxlego08.menu.api.utils.Placeholders; +import fr.maxlego08.menu.api.utils.version.ClientVersionManager; import fr.maxlego08.menu.hooks.ComponentMeta; import fr.maxlego08.menu.hooks.dialogs.inventory.AbstractDialogInventory; import fr.maxlego08.menu.hooks.dialogs.loader.DialogLoader; @@ -22,6 +25,9 @@ import org.bukkit.Bukkit; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; @@ -31,15 +37,16 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; -public class ZDialogManager implements DialogManager { +public class ZDialogManager implements DialogManager, Listener { private final MenuPlugin menuPlugin; private final ConfigManagerInt configManager; - private final Set dialogNames = new HashSet<>(); - private final Map> dialogs = new HashMap<>(); - private final Map activeDialogs = new HashMap<>(); + private final Set dialogNames = ConcurrentHashMap.newKeySet(); + private final Map> dialogs = new ConcurrentHashMap<>(); + private final Map activeDialogs = new ConcurrentHashMap<>(); private final ComponentMeta paperComponent; @@ -108,7 +115,8 @@ public void deleteDialog(String name) { dialog.getFileName().equals(name) || dialog.getName().equals(name) ); } - this.dialogNames.removeIf(dname -> dname.equals(dname.toLowerCase(Locale.ROOT))); + String suffix = ":" + name.toLowerCase(Locale.ROOT); + this.dialogNames.removeIf(dialogName -> dialogName.endsWith(suffix)); } @Override @@ -204,6 +212,12 @@ public void openDialog(@NotNull Player player,@NotNull DialogInventory zDialog) @Override public void openDialog(Player player, DialogInventory dialogInventory, List oldInventories) { + if (!this.menuPlugin.getClientVersionManager().supportsDialogs(player)) { + if (!this.checkRequirement(dialogInventory.getOpenRequirement(), player)) return; + this.openFallbackInventory(player, dialogInventory, oldInventories); + return; + } + PlayerOpenInventoryEvent playerOpenInventoryEvent = new PlayerOpenInventoryEvent(player, dialogInventory, 1, oldInventories); if (Configuration.enableFastEvent) { this.menuPlugin.getInventoryManager().getFastEvents().forEach(event -> event.onPlayerOpenInventory(playerOpenInventoryEvent)); @@ -236,12 +250,39 @@ public void openDialog(Player player, DialogInventory dialogInventory, List oldInventories) { + DialogFallback fallback = dialogInventory.getFallbackInventory(); + + if (fallback == null || !fallback.isValid()) { + this.menuPlugin.getInventoryManager().sendMessage(player, Message.DIALOG_NOT_SUPPORTED, "%version%", ClientVersionManager.DIALOG_MINIMUM_VERSION.toString(), "%name%", dialogInventory.getFileName()); + return; + } + + Optional optional = this.menuPlugin.getInventoryManager().getInventory(fallback.plugin(), fallback.inventoryName()); + if (optional.isEmpty()) { + this.menuPlugin.getInventoryManager().sendMessage(player, Message.INVENTORY_NOT_FOUND, "%name%", dialogInventory.getFileName(), "%toName%", fallback.inventoryName(), "%plugin%", fallback.plugin()); + return; + } + + try { + this.menuPlugin.getInventoryManager().openInventory(player, optional.get(), fallback.page(), oldInventories); + } catch (Exception exception) { + Logger.info("Failed to open the fallback inventory " + fallback.inventoryName() + " of the dialog " + dialogInventory.getFileName() + " for " + player.getName() + ": " + exception.getMessage(), Logger.LogType.ERROR); + if (Configuration.enableDebug) Logger.error(exception); + } + } + /** * Gets the active dialog for a player */ @@ -256,6 +297,16 @@ public void removeActiveDialog(@NotNull Player player) { this.activeDialogs.remove(player.getUniqueId()); } + /** + * Drops the active dialog of a leaving player. Without this the map keeps one entry per + * player who ever opened a dialog, and a reconnecting player inherits the dialog they + * had open in a previous session. + */ + @EventHandler + public void onQuit(PlayerQuitEvent event) { + this.activeDialogs.remove(event.getPlayer().getUniqueId()); + } + public boolean openDialogByName(@NotNull Player player, String dialogName) { Optional dialog = this.getDialog(dialogName); if (dialog.isPresent()) { @@ -280,7 +331,7 @@ public ConfigManagerInt getConfigManager(){ @Override public Set getDialogNames() { - return Set.of(); + return Collections.unmodifiableSet(this.dialogNames); } protected boolean checkRequirement(Requirement requirement, Player player) { diff --git a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/inventory/AbstractDialogInventory.java b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/inventory/AbstractDialogInventory.java index a7df652b0..804d04d35 100644 --- a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/inventory/AbstractDialogInventory.java +++ b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/inventory/AbstractDialogInventory.java @@ -14,6 +14,7 @@ import fr.maxlego08.menu.api.requirement.ConditionalName; import fr.maxlego08.menu.api.requirement.Requirement; import fr.maxlego08.menu.api.utils.ClearInvType; +import fr.maxlego08.menu.api.utils.DialogFallback; import fr.maxlego08.menu.api.utils.InventoryReplacement; import fr.maxlego08.menu.api.utils.PaperMetaUpdater; import fr.maxlego08.menu.api.utils.Placeholders; @@ -36,6 +37,7 @@ public abstract class AbstractDialogInventory implements DialogInventory { private final String fileName; private File file; private InventoryReplacement inventoryReplacement; + private DialogFallback fallbackInventory; private final String name; private final String externalTitle; @@ -242,6 +244,16 @@ public void setInventoryReplacement(InventoryReplacement inventoryReplacement) { this.inventoryReplacement = inventoryReplacement; } + @Override + public @Nullable DialogFallback getFallbackInventory() { + return this.fallbackInventory; + } + + @Override + public void setFallbackInventory(@Nullable DialogFallback fallbackInventory) { + this.fallbackInventory = fallbackInventory; + } + public void setTargetPlayerNamePlaceholder(String targetPlaceholder) { this.targetPlayerNamePlaceholder = targetPlaceholder; } diff --git a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/loader/DialogLoader.java b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/loader/DialogLoader.java index f129a65d8..b3412731d 100644 --- a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/loader/DialogLoader.java +++ b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/dialogs/loader/DialogLoader.java @@ -9,6 +9,7 @@ import fr.maxlego08.menu.api.exceptions.InventoryButtonException; import fr.maxlego08.menu.api.exceptions.InventoryException; import fr.maxlego08.menu.api.requirement.Requirement; +import fr.maxlego08.menu.api.utils.DialogFallback; import fr.maxlego08.menu.api.utils.InventoryReplacement; import fr.maxlego08.menu.api.utils.Loader; import fr.maxlego08.menu.hooks.dialogs.ZDialogManager; @@ -82,6 +83,17 @@ public AbstractDialogInventory load(@NonNull YamlConfiguration configuration, @N InventoryReplacement inventoryReplacement = new InventoryReplacement(replacementName, replacementPlugin, replacementPages); dialogInventory.setInventoryReplacement(inventoryReplacement); } + if (configuration.isConfigurationSection("fallback-inventory")) { + String fallbackName = configuration.getString("fallback-inventory.name", ""); + String fallbackPlugin = configuration.getString("fallback-inventory.plugin", "zMenu"); + int fallbackPage = configuration.getInt("fallback-inventory.page", 1); + DialogFallback dialogFallback = new DialogFallback(fallbackName, fallbackPlugin, fallbackPage); + if (!dialogFallback.isValid()) { + Logger.info("The fallback-inventory of the dialog " + file.getName() + " has no name, it will be ignored.", Logger.LogType.WARNING); + } else { + dialogInventory.setFallbackInventory(dialogFallback); + } + } List bodyButtons = this.loadButtons(configuration, file, "body", BodyButton.class, null); dialogInventory.setBodyButtons(bodyButtons); diff --git a/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/paper/PaperProtocolClientVersionProvider.java b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/paper/PaperProtocolClientVersionProvider.java new file mode 100644 index 000000000..68d885ec2 --- /dev/null +++ b/Hooks/Paper/src/main/java/fr/maxlego08/menu/hooks/paper/PaperProtocolClientVersionProvider.java @@ -0,0 +1,44 @@ +package fr.maxlego08.menu.hooks.paper; + +import fr.maxlego08.menu.api.utils.PlatformType; +import fr.maxlego08.menu.api.utils.version.ClientVersionProvider; +import fr.maxlego08.menu.api.utils.version.MinecraftVersion; +import fr.maxlego08.menu.api.utils.version.ProtocolVersions; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +/** + * Reads the client version from the protocol number Paper records during the handshake, + * through {@code Player#getProtocolVersion()} (inherited from + * {@code com.destroystokyo.paper.network.NetworkClient}). Requires no third-party plugin. + *

+ * This is the last provider consulted on purpose. ViaVersion rewrites the handshake + * protocol number to the server's own before the server reads it, so on a ViaVersion + * server this reports the server version rather than the client's. Its value is only + * trustworthy when no protocol translation plugin answered first. + */ +public class PaperProtocolClientVersionProvider implements ClientVersionProvider { + + @Override + @NotNull + public String getName() { + return "Paper"; + } + + @Override + public boolean isAvailable() { + return PlatformType.isPaper(); + } + + @Override + @NotNull + public Optional getClientVersion(@NotNull Player player) { + try { + return ProtocolVersions.fromProtocol(player.getProtocolVersion()); + } catch (Throwable throwable) { + return Optional.empty(); + } + } +} diff --git a/Hooks/ProtocolSupport/build.gradle.kts b/Hooks/ProtocolSupport/build.gradle.kts new file mode 100644 index 000000000..e1cfd51e6 --- /dev/null +++ b/Hooks/ProtocolSupport/build.gradle.kts @@ -0,0 +1,6 @@ +group = "Hooks:ProtocolSupport" + +dependencies { + compileOnly(projects.api) + compileOnly(libs.paper.api) +} diff --git a/Hooks/ProtocolSupport/src/main/java/fr/maxlego08/menu/hooks/protocolsupport/ProtocolSupportClientVersionProvider.java b/Hooks/ProtocolSupport/src/main/java/fr/maxlego08/menu/hooks/protocolsupport/ProtocolSupportClientVersionProvider.java new file mode 100644 index 000000000..e89eb6d98 --- /dev/null +++ b/Hooks/ProtocolSupport/src/main/java/fr/maxlego08/menu/hooks/protocolsupport/ProtocolSupportClientVersionProvider.java @@ -0,0 +1,58 @@ +package fr.maxlego08.menu.hooks.protocolsupport; + +import fr.maxlego08.menu.api.utils.version.ClientVersionProvider; +import fr.maxlego08.menu.api.utils.version.MinecraftVersion; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Method; +import java.util.Optional; + +public class ProtocolSupportClientVersionProvider implements ClientVersionProvider { + + private Method getProtocolVersion; + private Method getVersionName; + private boolean resolved; + + @Override + @NotNull + public String getName() { + return "ProtocolSupport"; + } + + @Override + public boolean isAvailable() { + this.resolve(); + return this.getProtocolVersion != null && this.getVersionName != null; + } + + private void resolve() { + if (this.resolved) return; + this.resolved = true; + try { + Class apiClass = Class.forName("protocolsupport.api.ProtocolSupportAPI"); + this.getProtocolVersion = apiClass.getMethod("getProtocolVersion", Player.class); + this.getVersionName = this.getProtocolVersion.getReturnType().getMethod("getName"); + } catch (Throwable throwable) { + this.getProtocolVersion = null; + this.getVersionName = null; + } + } + + @Override + @NotNull + public Optional getClientVersion(@NotNull Player player) { + if (!this.isAvailable()) return Optional.empty(); + try { + Object protocolVersion = this.getProtocolVersion.invoke(null, player); + if (protocolVersion == null) return Optional.empty(); + + Object name = this.getVersionName.invoke(protocolVersion); + if (!(name instanceof String versionName) || versionName.isBlank()) return Optional.empty(); + + return Optional.of(MinecraftVersion.parse(versionName)); + } catch (Throwable throwable) { + return Optional.empty(); + } + } +} diff --git a/Hooks/ViaVersion/build.gradle.kts b/Hooks/ViaVersion/build.gradle.kts new file mode 100644 index 000000000..308ace1bf --- /dev/null +++ b/Hooks/ViaVersion/build.gradle.kts @@ -0,0 +1,11 @@ +group = "Hooks:ViaVersion" + +repositories { + maven("https://repo.viaversion.com") +} + +dependencies { + compileOnly(projects.api) + compileOnly(libs.paper.api) + compileOnly(libs.viaversion) +} diff --git a/Hooks/ViaVersion/src/main/java/fr/maxlego08/menu/hooks/viaversion/ViaVersionClientVersionProvider.java b/Hooks/ViaVersion/src/main/java/fr/maxlego08/menu/hooks/viaversion/ViaVersionClientVersionProvider.java new file mode 100644 index 000000000..e15cf0cfb --- /dev/null +++ b/Hooks/ViaVersion/src/main/java/fr/maxlego08/menu/hooks/viaversion/ViaVersionClientVersionProvider.java @@ -0,0 +1,44 @@ +package fr.maxlego08.menu.hooks.viaversion; + +import com.viaversion.viaversion.api.Via; +import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import fr.maxlego08.menu.api.utils.version.ClientVersionProvider; +import fr.maxlego08.menu.api.utils.version.MinecraftVersion; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public class ViaVersionClientVersionProvider implements ClientVersionProvider { + + @Override + @NotNull + public String getName() { + return "ViaVersion"; + } + + @Override + public boolean isAvailable() { + try { + return Via.getAPI() != null; + } catch (Throwable throwable) { + return false; + } + } + + @Override + @NotNull + public Optional getClientVersion(@NotNull Player player) { + try { + int protocol = Via.getAPI().getPlayerVersion(player.getUniqueId()); + if (protocol < 0) return Optional.empty(); + + ProtocolVersion protocolVersion = ProtocolVersion.getProtocol(protocol); + if (!protocolVersion.isKnown()) return Optional.empty(); + + return Optional.of(MinecraftVersion.parse(protocolVersion.getName())); + } catch (Throwable throwable) { + return Optional.empty(); + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5e8355bb8..2e5f4d8b0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -46,6 +46,7 @@ score = "5.25.6.9" craft-engine = "26.7.3" eco = "6.53.0" floodgate = "2.2.4-SNAPSHOT" +viaversion = "5.11.0" breweryx = "3.6.0" denizen = "1.3.1-SNAPSHOT" @@ -95,6 +96,7 @@ craft-engine-core = { group = "net.momirealms", name = "craft-engine-core", vers craft-engine-bukkit = { group = "net.momirealms", name = "craft-engine-bukkit", version.ref = "craft-engine" } eco = { group = "com.willfp", name = "eco", version.ref = "eco" } floodgate = { group = "org.geysermc.floodgate", name = "api", version.ref = "floodgate" } +viaversion = { group = "com.viaversion", name = "viaversion-api", version.ref = "viaversion" } breweryx = { group = "com.dre.brewery", name = "BreweryX", version.ref = "breweryx" } denizen = { group= "com.denizenscript", name = "denizen", version.ref = "denizen" } paperdispatch = { group = "com.github.1robie.paper-dispatch", name = "paper-dispatch", version.ref = "paper-dispatch" } diff --git a/src/main/java/fr/maxlego08/menu/ZClientVersionManager.java b/src/main/java/fr/maxlego08/menu/ZClientVersionManager.java new file mode 100644 index 000000000..bed127cdd --- /dev/null +++ b/src/main/java/fr/maxlego08/menu/ZClientVersionManager.java @@ -0,0 +1,72 @@ +package fr.maxlego08.menu; + +import fr.maxlego08.menu.api.configuration.Configuration; +import fr.maxlego08.menu.api.utils.version.ClientVersionManager; +import fr.maxlego08.menu.api.utils.version.ClientVersionProvider; +import fr.maxlego08.menu.api.utils.version.MinecraftVersion; +import fr.maxlego08.menu.zcore.logger.Logger; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +public class ZClientVersionManager implements ClientVersionManager, Listener { + + private final List providers = new CopyOnWriteArrayList<>(); + private final Map cache = new ConcurrentHashMap<>(); + + @Override + public void registerProvider(@NotNull ClientVersionProvider provider) { + this.providers.add(provider); + this.cache.clear(); + } + + @Override + @NotNull + public Collection getProviders() { + return Collections.unmodifiableList(this.providers); + } + + @Override + @NotNull + public MinecraftVersion getClientVersion(@NotNull Player player) { + return this.cache.computeIfAbsent(player.getUniqueId(), uuid -> this.resolve(player)); + } + + /** + * Asks each provider in turn. When none can answer, we assume the client matches the + * server, which keeps servers without any protocol translation plugin unaffected. + */ + private MinecraftVersion resolve(Player player) { + for (ClientVersionProvider provider : this.providers) { + if (!provider.isAvailable()) continue; + try { + Optional version = provider.getClientVersion(player); + if (version.isPresent()) { + if (Configuration.enableDebug) { + Logger.info(provider.getName() + " reported client version " + version.get() + " for " + player.getName()); + } + return version.get(); + } + } catch (Exception exception) { + Logger.info("Client version provider " + provider.getName() + " failed for " + player.getName() + ": " + exception.getMessage(), Logger.LogType.WARNING); + } + } + return MinecraftVersion.getCurrentVersion(); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + this.cache.remove(event.getPlayer().getUniqueId()); + } +} diff --git a/src/main/java/fr/maxlego08/menu/ZCommandManager.java b/src/main/java/fr/maxlego08/menu/ZCommandManager.java index 6ce22a8f2..01f94006d 100644 --- a/src/main/java/fr/maxlego08/menu/ZCommandManager.java +++ b/src/main/java/fr/maxlego08/menu/ZCommandManager.java @@ -120,7 +120,7 @@ public void loadCommands() { .filter(e -> e.getName().endsWith(".yml")) .forEach(file -> this.loadCommand(this.plugin, file)); } catch (IOException e) { - e.printStackTrace(); + Logger.error(e); } @@ -156,7 +156,7 @@ public void loadCommand(Plugin plugin, File file) { Command command = loader.load(configuration, "commands." + key + ".", file); this.registerCommand(command); } catch (InventoryException e) { - e.printStackTrace(); + Logger.error(e); } } } diff --git a/src/main/java/fr/maxlego08/menu/ZInventoryManager.java b/src/main/java/fr/maxlego08/menu/ZInventoryManager.java index 7a1344d9c..7684dd496 100644 --- a/src/main/java/fr/maxlego08/menu/ZInventoryManager.java +++ b/src/main/java/fr/maxlego08/menu/ZInventoryManager.java @@ -68,26 +68,27 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; public class ZInventoryManager extends ZUtils implements InventoryManager { private final PaginationManager paginationManager = new ZPaginationManager(); - private final Set inventoryNames = new HashSet<>(); - private final Map> inventories = new HashMap<>(); - private final Map>> buttonOptions = new HashMap<>(); - private final Map>> inventoryOptions = new HashMap<>(); + private final Set inventoryNames = ConcurrentHashMap.newKeySet(); + private final Map> inventories = new ConcurrentHashMap<>(); + private final Map>> buttonOptions = new ConcurrentHashMap<>(); + private final Map>> inventoryOptions = new ConcurrentHashMap<>(); private final List inventoryListeners = new ArrayList<>(); private final List loaders = new ArrayList<>(); private final ZMenuPlugin plugin; - private final Map currentInventories = new HashMap<>(); - private final Map fastEventMap = new HashMap<>(); - private final Map itemStackSimilarMap = new HashMap<>(); + private final Map currentInventories = new ConcurrentHashMap<>(); + private final Map fastEventMap = new ConcurrentHashMap<>(); + private final Map itemStackSimilarMap = new ConcurrentHashMap<>(); - private final Map playerPages = new HashMap<>(); - private final Map playerMaxPages = new HashMap<>(); - private final Map inventoryByName = new HashMap<>(); + private final Map playerPages = new ConcurrentHashMap<>(); + private final Map playerMaxPages = new ConcurrentHashMap<>(); + private final Map inventoryByName = new ConcurrentHashMap<>(); private final List inventoryLoadRequirements = new ArrayList<>(); @@ -507,12 +508,12 @@ public void loadInventories() { try { this.loadInventory(this.plugin, file); } catch (InventoryException exception) { - exception.printStackTrace(); + Logger.error(exception); } } } } catch (IOException exception) { - exception.printStackTrace(); + Logger.error(exception); } // Load specifies path inventories @@ -524,7 +525,7 @@ public void loadInventories() { try { this.loadInventory(this.plugin, file); } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } } } @@ -624,7 +625,7 @@ public void reloadInventory(Inventory inventory) { try { this.loadInventory(inventory.getPlugin(), inventory.getFile()); } catch (InventoryException e) { - e.printStackTrace(); + Logger.error(e); } } @@ -771,7 +772,7 @@ public void saveItem(CommandSender sender, ItemStack itemStack, String name, Str try { file.createNewFile(); } catch (IOException exception) { - exception.printStackTrace(); + Logger.error(exception); } } @@ -797,7 +798,7 @@ public void saveItem(CommandSender sender, ItemStack itemStack, String name, Str try { configuration.save(file); } catch (IOException exception) { - exception.printStackTrace(); + Logger.error(exception); } } else { @@ -811,7 +812,12 @@ public void saveItem(CommandSender sender, ItemStack itemStack, String name, Str @EventHandler public void onQuid(PlayerQuitEvent event) { - this.currentInventories.remove(event.getPlayer().getUniqueId()); + UUID uniqueId = event.getPlayer().getUniqueId(); + this.currentInventories.remove(uniqueId); + // Cleared here too: otherwise they only reset on reload, and a returning player + // inherits the page they were on in a previous session. + this.playerPages.remove(uniqueId); + this.playerMaxPages.remove(uniqueId); } @EventHandler(priority = EventPriority.HIGHEST) @@ -951,7 +957,7 @@ public void loadElement(InventoryRequirementType type, String value) { try { this.loadInventory(inventoryLoadRequirement.getPlugin(), inventoryLoadRequirement.getFile(), inventoryLoadRequirement.getClassz()); } catch (InventoryException exception) { - exception.printStackTrace(); + Logger.error(exception); } finally { iterator.remove(); } diff --git a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java index c7f9d6498..925147cc7 100644 --- a/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java +++ b/src/main/java/fr/maxlego08/menu/ZMenuPlugin.java @@ -27,6 +27,7 @@ import fr.maxlego08.menu.api.utils.EnumInventory; import fr.maxlego08.menu.api.utils.MetaUpdater; import fr.maxlego08.menu.api.utils.toast.ToastHelper; +import fr.maxlego08.menu.api.utils.version.ClientVersionManager; import fr.maxlego08.menu.api.utils.version.MinecraftVersion; import fr.maxlego08.menu.api.utils.version.VersionFilter; import fr.maxlego08.menu.api.website.WebsiteManager; @@ -44,6 +45,9 @@ import fr.maxlego08.menu.hooks.NexoTagResolverLoader; import fr.maxlego08.menu.hooks.bedrock.ZBedrockManager; import fr.maxlego08.menu.hooks.bedrock.listener.BedrockReplacementListener; +import fr.maxlego08.menu.hooks.paper.PaperProtocolClientVersionProvider; +import fr.maxlego08.menu.hooks.protocolsupport.ProtocolSupportClientVersionProvider; +import fr.maxlego08.menu.hooks.viaversion.ViaVersionClientVersionProvider; import fr.maxlego08.menu.hooks.dialogs.ZDialogManager; import fr.maxlego08.menu.hooks.packetevents.PacketEventPlayerInventoryManager; import fr.maxlego08.menu.hooks.packetevents.PacketUtils; @@ -110,6 +114,7 @@ public class ZMenuPlugin extends ZPlugin implements fr.maxlego08.menu.api.MenuPl private final ButtonManager buttonManager; private final InventoryManager inventoryManager; private final TitleAnimationManager titleAnimationManager; + private final ZClientVersionManager clientVersionManager; private final CommandManager commandManager; private final MessageLoader messageLoader; private final DataManager dataManager; @@ -150,6 +155,7 @@ public ZMenuPlugin(Boolean isMockBukkitServer) { this.buttonManager = new ZButtonManager(this); this.inventoryManager = new ZInventoryManager(this); this.titleAnimationManager = new ZTitleAnimationManager(); + this.clientVersionManager = new ZClientVersionManager(); this.commandManager = new ZCommandManager(this); this.messageLoader = new MessageLoader(this); this.dataManager = new ZDataManager(this); @@ -193,7 +199,7 @@ public void onEnable() { Configuration.getInstance().load(this.getConfig()); this.websiteManager = new ZWebsiteManager(this); // Create a website manager after loading config.yml, for API URL. Never change the URL, only for dev purposes - Configuration.HAS_DIALOG_SUPPORT = this.isPaperOrFolia() && MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.parse("1.21.7")) && Configuration.enableMiniMessageFormat && this.hasClass("io{}papermc{}paper{}registry{}data{}dialog{}action{}DialogAction"); + Configuration.HAS_DIALOG_SUPPORT = this.isDialogCapableServer() && Configuration.enableMiniMessageFormat; Configuration.HAS_BEDROCK_INVENTORY_SUPPORT = this.isActive(Plugins.GEYSER) || this.isActive(Plugins.FLOODGATE); OfflinePlayerCache.install(this); @@ -243,11 +249,29 @@ public void onEnable() { servicesManager.register(Enchantments.class, this.enchantments, this, ServicePriority.Highest); servicesManager.register(TitleAnimationManager.class, this.titleAnimationManager, this, ServicePriority.Highest); - if (this.isPaperOrFolia() && MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.parse("1.21.7")) && this.hasClass("io{}papermc{}paper{}registry{}data{}dialog{}action{}DialogAction")) { + // Order matters: translation plugins are asked first because they rewrite the + // handshake protocol number Paper reads, which would otherwise report the server + // version for every translated client. + if (this.isActive(Plugins.VIAVERSION)) { + Logger.info("ViaVersion detected, loading client version detection"); + this.clientVersionManager.registerProvider(new ViaVersionClientVersionProvider()); + } + if (this.isActive(Plugins.PROTOCOLSUPPORT)) { + Logger.info("ProtocolSupport detected, loading client version detection"); + this.clientVersionManager.registerProvider(new ProtocolSupportClientVersionProvider()); + } + if (this.isPaperOrFolia()) { + this.clientVersionManager.registerProvider(new PaperProtocolClientVersionProvider()); + } + this.addListener(this.clientVersionManager); + servicesManager.register(ClientVersionManager.class, this.clientVersionManager, this, ServicePriority.Highest); + + if (this.isDialogCapableServer()) { if (Configuration.enableMiniMessageFormat) { Logger.info("Paper server detected, loading Dialogs support"); ConfigManager configManager = new ConfigManager(this); this.dialogManager = new ZDialogManager(this, configManager); + this.addListener((ZDialogManager) this.dialogManager); servicesManager.register(DialogManager.class, this.dialogManager, this, ServicePriority.Highest); ConfigDialogBuilder configDialogBuilder = new ConfigDialogBuilder("zMenu Config", "zMenu Configuration"); configManager.registerConfig(configDialogBuilder, Configuration.class, this); @@ -405,7 +429,7 @@ private List getInventoriesFiles() { files.add("actions_patterns/default-actions.yml"); - if (this.isPaperOrFolia() && MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.parse("1.21.7"))) { + if (this.isDialogCapableServer()) { files.add("dialogs/confirmation-dialog.yml"); files.add("dialogs/default-dialog.yml"); files.add("dialogs/multi_action-dialog.yml"); @@ -442,8 +466,8 @@ public void onDisable() { YamlFileCache.clearCache(); - this.websiteManager.onDisable(); - + if (this.websiteManager != null) this.websiteManager.onDisable(); + if (!this.isMockBukkitServer) { NMSMenuPacketListener nmsMenuPacketListener = NMSMenuPacketListener.get(); if (nmsMenuPacketListener != null) { @@ -460,6 +484,29 @@ public void onDisable() { this.postDisable(); } + /** + * The oldest server version zMenu loads its dialog support on. + *

+ * Note this is not the same threshold as + * {@link fr.maxlego08.menu.api.utils.version.ClientVersionManager#DIALOG_MINIMUM_VERSION} + * (1.21.6), which is the oldest client able to render a dialog. The server gate is + * deliberately one patch higher; the consequence is that on a 1.21.6 server dialogs are + * not loaded at all, so neither dialogs nor their fallbacks exist there. + */ + private static final MinecraftVersion DIALOG_MINIMUM_SERVER_VERSION = MinecraftVersion.parse("1.21.7"); + + /** + * Whether this server can run the Paper Dialog API at all: right platform, new enough, + * and the Paper dialog classes actually present. + * + * @return true if dialog support can be loaded + */ + private boolean isDialogCapableServer() { + return this.isPaperOrFolia() + && MinecraftVersion.getCurrentVersion().isAtLeast(DIALOG_MINIMUM_SERVER_VERSION) + && this.hasClass("io{}papermc{}paper{}registry{}data{}dialog{}action{}DialogAction"); + } + /** * Returns the class that will manage the loading of the buttons * @@ -536,6 +583,11 @@ public BedrockManager getBedrockManager() { return this.bedrockManager; } + @Override + public ClientVersionManager getClientVersionManager() { + return this.clientVersionManager; + } + @Override public String[] getClickRequirementKeys() { return new String[]{"click_requirement.", "click-requirement.", "click_requirements.", "click-requirements.", "clicks_requirement.", "clicks-requirement.", "clicks_requirements.", "clicks-requirements."}; diff --git a/src/main/java/fr/maxlego08/menu/button/loader/SwitchLoader.java b/src/main/java/fr/maxlego08/menu/button/loader/SwitchLoader.java index f14f7a7df..1232a4a55 100644 --- a/src/main/java/fr/maxlego08/menu/button/loader/SwitchLoader.java +++ b/src/main/java/fr/maxlego08/menu/button/loader/SwitchLoader.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.button.loader; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.api.MenuPlugin; import fr.maxlego08.menu.api.annotations.AutoButtonLoader; import fr.maxlego08.menu.api.button.Button; @@ -39,7 +41,7 @@ public Button load(@NonNull YamlConfiguration configuration, @NonNull String pat var button = loader.load(configuration, path + "buttons." + key + ".", key, defaultButtonValue); switchCaseButtons.add(new SwitchCaseButton(key, button)); } catch (InventoryException exception) { - exception.printStackTrace(); + Logger.error(exception); } } } diff --git a/src/main/java/fr/maxlego08/menu/command/VCommand.java b/src/main/java/fr/maxlego08/menu/command/VCommand.java index 62142981c..c17ee427b 100644 --- a/src/main/java/fr/maxlego08/menu/command/VCommand.java +++ b/src/main/java/fr/maxlego08/menu/command/VCommand.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.command; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.ZMenuPlugin; import fr.maxlego08.menu.api.Inventory; import fr.maxlego08.menu.api.InventoryManager; @@ -410,7 +412,7 @@ public CommandType prePerform(ZMenuPlugin plugin, CommandSender commandSender, S return this.perform(plugin); } catch (Exception e) { if (Configuration.enableDebug) - e.printStackTrace(); + Logger.error(e); return CommandType.SYNTAX_ERROR; } } diff --git a/src/main/java/fr/maxlego08/menu/command/VCommandManager.java b/src/main/java/fr/maxlego08/menu/command/VCommandManager.java index f601e6b71..6be13e37d 100644 --- a/src/main/java/fr/maxlego08/menu/command/VCommandManager.java +++ b/src/main/java/fr/maxlego08/menu/command/VCommandManager.java @@ -209,7 +209,7 @@ public void registerCommand(Plugin plugin, String string,boolean consoleCanUse, Logger.info("Unable to add the command " + vCommand.getSyntax(), LogType.ERROR); } } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); } } diff --git a/src/main/java/fr/maxlego08/menu/dupe/DupeListener.java b/src/main/java/fr/maxlego08/menu/dupe/DupeListener.java index c7a94faa2..448fd9d2c 100644 --- a/src/main/java/fr/maxlego08/menu/dupe/DupeListener.java +++ b/src/main/java/fr/maxlego08/menu/dupe/DupeListener.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.dupe; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.api.dupe.DupeItem; import fr.maxlego08.menu.api.dupe.DupeManager; import org.bukkit.Material; @@ -128,7 +130,7 @@ private void sendInformation(DupeItem dupeItem) { try { discordWebhook.execute(dupeItem); } catch (IOException exception) { - exception.printStackTrace(); + Logger.error(exception); } }); }*/ diff --git a/src/main/java/fr/maxlego08/menu/inventory/VInventoryManager.java b/src/main/java/fr/maxlego08/menu/inventory/VInventoryManager.java index 61efaea1c..400f0ab2f 100644 --- a/src/main/java/fr/maxlego08/menu/inventory/VInventoryManager.java +++ b/src/main/java/fr/maxlego08/menu/inventory/VInventoryManager.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.inventory; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.ZMenuPlugin; import fr.maxlego08.menu.api.VInvManager; import fr.maxlego08.menu.api.configuration.Configuration; @@ -133,7 +135,7 @@ public void createInventory(int id, Player player, int page, Object... objects) } } catch (InventoryOpenException exception) { message(this.plugin, player, Message.VINVENTORY_ERROR, "%id%", id); - exception.printStackTrace(); + Logger.error(exception); } } diff --git a/src/main/java/fr/maxlego08/menu/loader/InventoryLoader.java b/src/main/java/fr/maxlego08/menu/loader/InventoryLoader.java index 394fd1023..cffbca98b 100644 --- a/src/main/java/fr/maxlego08/menu/loader/InventoryLoader.java +++ b/src/main/java/fr/maxlego08/menu/loader/InventoryLoader.java @@ -132,7 +132,7 @@ public Inventory load(@NonNull YamlConfiguration configuration, @NonNull String constructor.setAccessible(true); inventory = constructor.newInstance(pluginOwner, name, fileName, size, buttons); } catch (Exception e) { - e.printStackTrace(); + Logger.error(e); inventory = InventoryTypeRegistry.getInstance().get(inventoryType).orElseGet(EmptyContainerInventoryTypeLoader::new).load(this.plugin, pluginOwner, name, fileName, size, buttons, configuration, path, file); } } else { diff --git a/src/main/java/fr/maxlego08/menu/loader/MenuItemStackLoader.java b/src/main/java/fr/maxlego08/menu/loader/MenuItemStackLoader.java index 93879de7b..af0813d6d 100644 --- a/src/main/java/fr/maxlego08/menu/loader/MenuItemStackLoader.java +++ b/src/main/java/fr/maxlego08/menu/loader/MenuItemStackLoader.java @@ -140,7 +140,7 @@ public MenuItemStack load(@NonNull YamlConfiguration configuration, @NonNull Str } catch (Exception e) { if (Configuration.enableDebug) { Logger.info("An error occurred while loading the item component " + componentKey + " for file " + file.getAbsolutePath() + " with path " + path, Logger.LogType.WARNING); - e.printStackTrace(); + Logger.error(e); } } } @@ -287,7 +287,7 @@ private void loadEnchantements(ZMenuItemStack menuItemStack, YamlConfiguration c enchantments.put(optional.get().enchantment(), level); } catch (ItemEnchantException e) { - e.printStackTrace(); + Logger.error(e); } } menuItemStack.setEnchantments(enchantments); @@ -742,7 +742,7 @@ public void save(MenuItemStack item, @NonNull YamlConfiguration configuration, @ try { configuration.save(file); } catch (IOException exception) { - exception.printStackTrace(); + Logger.error(exception); } } diff --git a/src/main/java/fr/maxlego08/menu/loader/actions/DiscordLoader.java b/src/main/java/fr/maxlego08/menu/loader/actions/DiscordLoader.java index 6a9ff46fb..024d712a7 100644 --- a/src/main/java/fr/maxlego08/menu/loader/actions/DiscordLoader.java +++ b/src/main/java/fr/maxlego08/menu/loader/actions/DiscordLoader.java @@ -67,7 +67,7 @@ private boolean checkWebhookExists(String webhookUrl) { webhookUrlCache.put(webhookUrl, responseCode == 200); return responseCode == 200; } catch (Exception exception) { - exception.printStackTrace(); + Logger.error(exception); webhookUrlCache.put(webhookUrl, false); return false; } diff --git a/src/main/java/fr/maxlego08/menu/loader/actions/TakeItemLoader.java b/src/main/java/fr/maxlego08/menu/loader/actions/TakeItemLoader.java index 7b0cdb2b2..edbf2bc46 100644 --- a/src/main/java/fr/maxlego08/menu/loader/actions/TakeItemLoader.java +++ b/src/main/java/fr/maxlego08/menu/loader/actions/TakeItemLoader.java @@ -7,6 +7,7 @@ import fr.maxlego08.menu.api.loader.ActionLoader; import fr.maxlego08.menu.api.requirement.Action; import fr.maxlego08.menu.api.utils.TypedMapAccessor; +import fr.maxlego08.menu.api.utils.resolvable.lang.ResolvableInt; import fr.maxlego08.menu.requirement.actions.TakeItemAction; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -33,7 +34,7 @@ public TakeItemLoader(MenuPlugin menuPlugin) { } boolean useCache = accessor.getBoolean("use-cache", false); - int amount = accessor.getInt("amount", 1); + ResolvableInt amount = ResolvableInt.autoOrNull(accessor.getString("amount", "1")); ItemVerification itemVerification = ItemVerification.valueOf(accessor.getString("verification", ItemVerification.SIMILAR.name())); return new TakeItemAction(menuItemStack, useCache, amount, itemVerification); } diff --git a/src/main/java/fr/maxlego08/menu/loader/deluxemenu/InventoryDeluxeMenuLoader.java b/src/main/java/fr/maxlego08/menu/loader/deluxemenu/InventoryDeluxeMenuLoader.java index e2cb5fac1..7e0a6bdba 100644 --- a/src/main/java/fr/maxlego08/menu/loader/deluxemenu/InventoryDeluxeMenuLoader.java +++ b/src/main/java/fr/maxlego08/menu/loader/deluxemenu/InventoryDeluxeMenuLoader.java @@ -138,7 +138,7 @@ public Inventory load(@NonNull YamlConfiguration configuration, @NonNull String constructor.setAccessible(true); inventory = constructor.newInstance(pluginOwner, name, fileName, size, buttons); } catch (Exception e) { - e.printStackTrace(); + Logger.error(e); inventory = InventoryTypeRegistry.getInstance().get(inventoryType).orElseGet(EmptyContainerInventoryTypeLoader::new).load(this.plugin, pluginOwner, name, fileName, size, buttons, configuration, path, file); } } else { diff --git a/src/main/java/fr/maxlego08/menu/pattern/ZPatternManager.java b/src/main/java/fr/maxlego08/menu/pattern/ZPatternManager.java index b9a03e853..64e844592 100644 --- a/src/main/java/fr/maxlego08/menu/pattern/ZPatternManager.java +++ b/src/main/java/fr/maxlego08/menu/pattern/ZPatternManager.java @@ -145,11 +145,11 @@ private void loadFromFolder(String folderName, ThrowingFileLoader loader) { try { loader.load(file); } catch (InventoryException e) { - e.printStackTrace(); + Logger.error(e); } }); } catch (IOException exception) { - exception.printStackTrace(); + Logger.error(exception); } } } diff --git a/src/main/java/fr/maxlego08/menu/players/ZDataManager.java b/src/main/java/fr/maxlego08/menu/players/ZDataManager.java index 6c5a986e4..f5e12d1a7 100644 --- a/src/main/java/fr/maxlego08/menu/players/ZDataManager.java +++ b/src/main/java/fr/maxlego08/menu/players/ZDataManager.java @@ -227,7 +227,7 @@ public void convertOldDatas(@NonNull CommandSender sender) { datas.forEach((uuid, playerData) -> playerData.forEach(data -> manager.upsertData(uuid, data))); Logger.info("Loaded " + datas.size() + " players.", Logger.LogType.SUCCESS); } catch (IOException exception) { - exception.printStackTrace(); + Logger.error(exception); } } } diff --git a/src/main/java/fr/maxlego08/menu/requirement/actions/DiscordAction.java b/src/main/java/fr/maxlego08/menu/requirement/actions/DiscordAction.java index c15e5a91a..585d594f5 100644 --- a/src/main/java/fr/maxlego08/menu/requirement/actions/DiscordAction.java +++ b/src/main/java/fr/maxlego08/menu/requirement/actions/DiscordAction.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.requirement.actions; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.api.button.Button; import fr.maxlego08.menu.api.configuration.Configuration; import fr.maxlego08.menu.api.engine.InventoryEngine; @@ -30,7 +32,7 @@ protected void execute(@NonNull Player player, Button button, @NonNull Inventory discordWebhook.execute(); } catch (Exception exception) { if (Configuration.enableDebug) { - exception.printStackTrace(); + Logger.error(exception); } } }); diff --git a/src/main/java/fr/maxlego08/menu/requirement/actions/DiscordComponentAction.java b/src/main/java/fr/maxlego08/menu/requirement/actions/DiscordComponentAction.java index 4aa9f10e7..4b58b0fa8 100644 --- a/src/main/java/fr/maxlego08/menu/requirement/actions/DiscordComponentAction.java +++ b/src/main/java/fr/maxlego08/menu/requirement/actions/DiscordComponentAction.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.requirement.actions; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.api.button.Button; import fr.maxlego08.menu.api.configuration.Configuration; import fr.maxlego08.menu.api.engine.InventoryEngine; @@ -29,7 +31,7 @@ protected void execute(@NonNull Player player, Button button, @NonNull Inventory discordWebhook.execute(); } catch (Exception exception) { if (Configuration.enableDebug) { - exception.printStackTrace(); + Logger.error(exception); } } }); diff --git a/src/main/java/fr/maxlego08/menu/requirement/actions/TakeItemAction.java b/src/main/java/fr/maxlego08/menu/requirement/actions/TakeItemAction.java index 7fe326362..8388eca64 100644 --- a/src/main/java/fr/maxlego08/menu/requirement/actions/TakeItemAction.java +++ b/src/main/java/fr/maxlego08/menu/requirement/actions/TakeItemAction.java @@ -2,10 +2,13 @@ import fr.maxlego08.menu.api.MenuItemStack; import fr.maxlego08.menu.api.button.Button; +import fr.maxlego08.menu.api.context.ZBuildContext; import fr.maxlego08.menu.api.engine.InventoryEngine; import fr.maxlego08.menu.api.enums.ItemVerification; import fr.maxlego08.menu.api.requirement.Action; import fr.maxlego08.menu.api.utils.Placeholders; +import fr.maxlego08.menu.api.utils.resolvable.Resolvable; +import fr.maxlego08.menu.api.utils.resolvable.lang.ResolvableInt; import fr.maxlego08.menu.zcore.logger.Logger; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -19,9 +22,13 @@ public class TakeItemAction extends Action { private final ItemVerification itemVerification; private final MenuItemStack menuItemStack; private final boolean useCache; - private final int amount; + private final ResolvableInt amount; public TakeItemAction(MenuItemStack menuItemStack, boolean useCache, int amount, ItemVerification itemVerification) { + this(menuItemStack, useCache, ResolvableInt.of(amount), itemVerification); + } + + public TakeItemAction(MenuItemStack menuItemStack, boolean useCache, ResolvableInt amount, ItemVerification itemVerification) { this.menuItemStack = menuItemStack; this.useCache = useCache; this.amount = amount; @@ -29,58 +36,117 @@ public TakeItemAction(MenuItemStack menuItemStack, boolean useCache, int amount, } @Override - protected void execute(@NotNull Player player, - @Nullable Button button, - @NotNull InventoryEngine inventoryEngine, - @NotNull Placeholders placeholders) { - - if (this.menuItemStack == null) return; + protected void execute(@NotNull Player player, @Nullable Button button, @NotNull InventoryEngine inventoryEngine, @NotNull Placeholders placeholders + ) { + if (this.menuItemStack == null) { + this.debugLog("Menu item stack is null | player=%s", player.getName()); + return; + } - ItemStack targetItem = this.menuItemStack.build(player, this.useCache, placeholders); + ItemStack targetItem = this.menuItemStack.build( + player, + this.useCache, + placeholders + ); if (targetItem == null) { this.debugLog("Build failed - target item is null | player=%s", player.getName()); return; } - this.debugLog("Built target item | player=%s item=%s", player.getName(), targetItem); + ZBuildContext context = new ZBuildContext.Builder() + .player(player) + .placeholders(placeholders) + .build(); + + int requestedAmount = Resolvable.resolveOrDefault( + context, + this.amount, + 1 + ); + + if (requestedAmount <= 0) { + this.debugLog( + "Invalid amount=%d | player=%s", + requestedAmount, + player.getName() + ); + return; + } + + this.debugLog( + "Built target item | player=%s item=%s amount=%d", + player.getName(), + targetItem, + requestedAmount + ); - int remaining = this.amount; PlayerInventory inventory = player.getInventory(); + int remaining = requestedAmount; + for (int slot = 0; slot < 36 && remaining > 0; slot++) { ItemStack current = inventory.getItem(slot); - if (current == null) continue; + + if (current == null || current.getAmount() <= 0) { + continue; + } boolean matches = this.matches(current, targetItem); - this.debugLog("Slot %02d | item=%-30s match=%s", slot, current, matches); - if (!matches) continue; + + this.debugLog( + "Slot %02d | item=%s match=%s", + slot, + current, + matches + ); + + if (!matches) { + continue; + } int toRemove = Math.min(remaining, current.getAmount()); + current.setAmount(current.getAmount() - toRemove); remaining -= toRemove; if (current.getAmount() <= 0) { - this.debugLog("Slot %02d cleared | player=%s", slot, player.getName()); inventory.setItem(slot, null); + + this.debugLog( + "Slot %02d cleared | player=%s", + slot, + player.getName() + ); } } - this.logResult(player, remaining); + this.logResult(player, requestedAmount, remaining); } - private void logResult(@NotNull Player player, int remaining) { - if (!this.debug) return; + private void logResult( + @NotNull Player player, + int requestedAmount, + int remaining + ) { + if (!this.debug) { + return; + } + + int taken = requestedAmount - remaining; if (remaining > 0) { Logger.info(String.format( - "[TakeItem] Partial removal | player=%s taken=%d missing=%d", - player.getName(), this.amount - remaining, remaining + "[TakeItem] Partial removal | player=%s taken=%d missing=%d", + player.getName(), + taken, + remaining )); } else { Logger.info(String.format( - "[TakeItem] Success | player=%s taken=%d", - player.getName(), this.amount + "[TakeItem] Success | player=%s taken=%d", + player.getName(), + taken )); } } @@ -91,7 +157,6 @@ private void debugLog(String format, Object... args) { private boolean matches(@NotNull ItemStack item, @NotNull ItemStack target) { return switch (this.itemVerification) { - case SIMILAR -> item.isSimilar(target); case MODELID -> { diff --git a/src/main/java/fr/maxlego08/menu/requirement/permissible/ZPlaceholderPermissible.java b/src/main/java/fr/maxlego08/menu/requirement/permissible/ZPlaceholderPermissible.java index b253dd005..f35dab938 100644 --- a/src/main/java/fr/maxlego08/menu/requirement/permissible/ZPlaceholderPermissible.java +++ b/src/main/java/fr/maxlego08/menu/requirement/permissible/ZPlaceholderPermissible.java @@ -16,6 +16,7 @@ import org.jspecify.annotations.NonNull; import java.util.List; +import java.util.Objects; /** * Implementation of the {@link PlaceholderPermissible} interface that checks player permissions @@ -65,8 +66,18 @@ public boolean hasPermission(@NonNull Player player, Button button, @NonNull Inv valueAsString = plugin.parse(player, placeholders.parse(this.placeholder)); resultAsString = plugin.parse(player, placeholders.parse(this.value)); } else { + String parsedTargetPlayer = plugin.parse(player, placeholders.parse(this.targetPlayer)); - OfflinePlayer offlinePlayer = OfflinePlayerCache.get(plugin.parse(player, placeholders.parse(this.targetPlayer))); + java.util.UUID uuid; + try { + uuid = java.util.UUID.fromString(parsedTargetPlayer); + } catch (IllegalArgumentException e) { + uuid = null; + } + + OfflinePlayer offlinePlayer = uuid != null + ? Objects.requireNonNull(fr.robie.paperdispatch.cache.OfflinePlayerCache.getGlobalInstance()).get(uuid) + : OfflinePlayerCache.get(parsedTargetPlayer); valueAsString = plugin.parse(offlinePlayer.hasPlayedBefore() ? offlinePlayer : player, placeholders.parse(this.placeholder)); resultAsString = plugin.parse(offlinePlayer.hasPlayedBefore() ? offlinePlayer : player, placeholders.parse(this.value)); } @@ -115,7 +126,7 @@ public boolean hasPermission(@NonNull Player player, Button button, @NonNull Inv } catch (Exception exception) { if (Configuration.enableDebug) { - exception.printStackTrace(); + Logger.error(exception); } return false; } diff --git a/src/main/java/fr/maxlego08/menu/save/MessageLoader.java b/src/main/java/fr/maxlego08/menu/save/MessageLoader.java index 7ca9b3470..394d7d718 100644 --- a/src/main/java/fr/maxlego08/menu/save/MessageLoader.java +++ b/src/main/java/fr/maxlego08/menu/save/MessageLoader.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.save; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.ZMenuPlugin; import fr.maxlego08.menu.api.enums.MessageType; import fr.maxlego08.menu.api.utils.Message; @@ -32,7 +34,7 @@ public void save() { try { file.createNewFile(); } catch (IOException exception) { - exception.printStackTrace(); + Logger.error(exception); } } @@ -68,7 +70,7 @@ public void save() { try { configuration.save(file); } catch (IOException exception) { - exception.printStackTrace(); + Logger.error(exception); } this.loadMessages(configuration); diff --git a/src/main/java/fr/maxlego08/menu/website/ZWebsiteManager.java b/src/main/java/fr/maxlego08/menu/website/ZWebsiteManager.java index ded346d11..7ade8ddf7 100644 --- a/src/main/java/fr/maxlego08/menu/website/ZWebsiteManager.java +++ b/src/main/java/fr/maxlego08/menu/website/ZWebsiteManager.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.website; +import fr.maxlego08.menu.zcore.logger.Logger; + import com.google.gson.JsonObject; import fr.maxlego08.menu.ZMenuPlugin; import fr.maxlego08.menu.api.configuration.Configuration; @@ -136,10 +138,10 @@ private DownloadResult performDownload(String baseUrl, boolean force, CommandSen return DownloadResult.SUCCESS; } catch (DisallowedHostException exception) { - exception.printStackTrace(); + Logger.error(exception); return DownloadResult.ERROR_HOST_NOT_ALLOWED; } catch (IOException | URISyntaxException exception) { - exception.printStackTrace(); + Logger.error(exception); return DownloadResult.ERROR_IO; } } diff --git a/src/main/java/fr/maxlego08/menu/website/request/HttpRequest.java b/src/main/java/fr/maxlego08/menu/website/request/HttpRequest.java index 072cfdfcb..40c6ca822 100644 --- a/src/main/java/fr/maxlego08/menu/website/request/HttpRequest.java +++ b/src/main/java/fr/maxlego08/menu/website/request/HttpRequest.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.website.request; +import fr.maxlego08.menu.zcore.logger.Logger; + import com.google.gson.Gson; import com.google.gson.JsonObject; import fr.maxlego08.menu.ZMenuPlugin; @@ -93,7 +95,7 @@ public void submit(ZMenuPlugin plugin, Consumer consumer) { } catch (Exception exception) { if (Configuration.enableDebug) { - exception.printStackTrace(); + Logger.error(exception); } } @@ -200,7 +202,7 @@ private Attempt downloadOnce(File fileOut) { } catch (Exception exception) { if (Configuration.enableDebug) { - exception.printStackTrace(); + Logger.error(exception); } // Connect/read timeout, connection reset, DNS failure, etc. - a retryable transport error. return new Attempt(false, -1, 0L, Collections.emptyMap()); diff --git a/src/main/java/fr/maxlego08/menu/website/sync/LiveSyncManager.java b/src/main/java/fr/maxlego08/menu/website/sync/LiveSyncManager.java index e7779add6..e22079769 100644 --- a/src/main/java/fr/maxlego08/menu/website/sync/LiveSyncManager.java +++ b/src/main/java/fr/maxlego08/menu/website/sync/LiveSyncManager.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.website.sync; +import fr.maxlego08.menu.zcore.logger.Logger; + import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -540,7 +542,7 @@ public void onError(Exception ex) { LiveSyncManager.this.connecting = false; LiveSyncManager.this.severe("Live connection error: " + ex.getMessage() + "."); if (Configuration.enableDebug) { - ex.printStackTrace(); + Logger.error(ex); } } }; @@ -558,7 +560,7 @@ public void onError(Exception ex) { this.severe("Failed to open the live connection: " + throwable.getMessage() + "."); message(this.plugin, sender, Message.WEBSITE_SYNC_CONNECT_ERROR); if (Configuration.enableDebug) { - throwable.printStackTrace(); + Logger.error(throwable); } } } @@ -1115,7 +1117,7 @@ private void reloadOnMainThread(String fileName, String subPath, File target, Fi applied = false; this.severe("zMenu failed to load the synced inventory '" + label + "': " + exception.getMessage() + "."); if (Configuration.enableDebug) { - exception.printStackTrace(); + Logger.error(exception); } } @@ -1173,7 +1175,7 @@ private void openForPlayer(InventoryManager inventoryManager, File target, Strin } catch (Exception exception) { this.severe("Failed to open '" + label + "' for " + player.getName() + ": " + exception.getMessage() + "."); if (Configuration.enableDebug) { - exception.printStackTrace(); + Logger.error(exception); } } }); diff --git a/src/main/java/fr/maxlego08/menu/zcore/ZPlugin.java b/src/main/java/fr/maxlego08/menu/zcore/ZPlugin.java index 77b42207d..ea825aade 100644 --- a/src/main/java/fr/maxlego08/menu/zcore/ZPlugin.java +++ b/src/main/java/fr/maxlego08/menu/zcore/ZPlugin.java @@ -221,7 +221,7 @@ public void saveResource(String resourcePath, String toPath, boolean replace) { } } catch (IOException var10) { Logger.info("Could not save " + outFile.getName() + " to " + outFile, LogType.ERROR); - var10.printStackTrace(); + Logger.error(var10); } } diff --git a/src/main/java/fr/maxlego08/menu/zcore/utils/loader/ItemStackLoader.java b/src/main/java/fr/maxlego08/menu/zcore/utils/loader/ItemStackLoader.java index 198ce451c..8efcd7338 100644 --- a/src/main/java/fr/maxlego08/menu/zcore/utils/loader/ItemStackLoader.java +++ b/src/main/java/fr/maxlego08/menu/zcore/utils/loader/ItemStackLoader.java @@ -126,7 +126,7 @@ public ItemStack load(@NonNull YamlConfiguration configuration, @NonNull String } else meta.addEnchant(enchantment, level, true); } catch (ItemEnchantException e) { - e.printStackTrace(); + Logger.error(e); } } @@ -149,7 +149,7 @@ public ItemStack load(@NonNull YamlConfiguration configuration, @NonNull String meta.addItemFlags(flag); } catch (ItemFlagException e) { - e.printStackTrace(); + Logger.error(e); } } diff --git a/src/main/java/fr/maxlego08/menu/zcore/utils/players/ActionBar.java b/src/main/java/fr/maxlego08/menu/zcore/utils/players/ActionBar.java index c8fcbffc4..c1f773bef 100644 --- a/src/main/java/fr/maxlego08/menu/zcore/utils/players/ActionBar.java +++ b/src/main/java/fr/maxlego08/menu/zcore/utils/players/ActionBar.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.zcore.utils.players; +import fr.maxlego08.menu.zcore.logger.Logger; + import fr.maxlego08.menu.common.utils.nms.NMSUtils; import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.chat.TextComponent; @@ -63,7 +65,7 @@ public static void sendActionBar(Player player, String message) { Method packetMethod = packet.getClass().getDeclaredMethod("sendPacket", packetClass); packetMethod.invoke(packet, packetContent); } catch (Exception error) { - error.printStackTrace(); + Logger.error(error); } } } \ No newline at end of file diff --git a/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java b/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java index 43e0f6ba3..451d48209 100644 --- a/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java +++ b/src/main/java/fr/maxlego08/menu/zcore/utils/plugins/Plugins.java @@ -31,6 +31,8 @@ public enum Plugins { PACKETEVENTS("packetevents"), GEYSER("Geyser-Spigot"), FLOODGATE("floodgate"), + VIAVERSION("ViaVersion"), + PROTOCOLSUPPORT("ProtocolSupport"), MMOITEMS("MMOItems"); private final String name; diff --git a/src/main/java/fr/maxlego08/menu/zcore/utils/storage/DiscUtils.java b/src/main/java/fr/maxlego08/menu/zcore/utils/storage/DiscUtils.java index 8111a0aaa..7325e7fcf 100644 --- a/src/main/java/fr/maxlego08/menu/zcore/utils/storage/DiscUtils.java +++ b/src/main/java/fr/maxlego08/menu/zcore/utils/storage/DiscUtils.java @@ -1,5 +1,7 @@ package fr.maxlego08.menu.zcore.utils.storage; +import fr.maxlego08.menu.zcore.logger.Logger; + import java.io.*; import java.net.URL; import java.nio.channels.Channels; @@ -82,7 +84,7 @@ public static boolean downloadUrl(String urlstring, File file) { fos.getChannel().transferFrom(rbc, 0, 1 << 24); return true; } catch (Exception e) { - e.printStackTrace(); + Logger.error(e); return false; } } diff --git a/src/main/java/fr/maxlego08/menu/zcore/utils/storage/Persist.java b/src/main/java/fr/maxlego08/menu/zcore/utils/storage/Persist.java index e8afbdd63..7b5066954 100644 --- a/src/main/java/fr/maxlego08/menu/zcore/utils/storage/Persist.java +++ b/src/main/java/fr/maxlego08/menu/zcore/utils/storage/Persist.java @@ -130,7 +130,7 @@ public boolean save(Object instance, File file) { } catch (Exception e) { Logger.info("cannot save file " + file.getAbsolutePath(), LogType.ERROR); - e.printStackTrace(); + Logger.error(e); return false; } diff --git a/src/main/resources/dialogs/default-dialog.yml b/src/main/resources/dialogs/default-dialog.yml index 9fdbb35ff..d53952021 100644 --- a/src/main/resources/dialogs/default-dialog.yml +++ b/src/main/resources/dialogs/default-dialog.yml @@ -13,6 +13,13 @@ can-close-with-escape: true # Allow players to close with ESC key pause: false # Pause the game while dialog is open after_action: CLOSE # What to do after dialog: CLOSE, PAUSE, or NONE +# Inventory opened instead of this dialog when the player's client is below Minecraft 1.21.6 +# and therefore cannot render dialogs. +# fallback-inventory: +# name: my_menu # inventory file name, without .yml +# plugin: zMenu # optional, defaults to zMenu +# page: 1 # optional, defaults to 1 + # Dialog body content - can contain multiple sections body: plain_message: diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 36ce39a28..c4a6f9e37 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -150,6 +150,16 @@ dependencies: required: false join-classpath: true + ViaVersion: + load: BEFORE + required: false + join-classpath: true + + ProtocolSupport: + load: BEFORE + required: false + join-classpath: true + # -- Economy plugins Vault: required: false